In a previous blog post I gave a high level overview of DLL injection, what it is used for and how it might be achieved.
More than one method exists to get our code into a process and have it execute. A quick scan around the web gives us quite a few ideas. It boils down to two steps:
- The first step is to get our code into the memory of the target process.
- The second step is to run that code.
I’ve written this post assuming that the reader has some C or C++ Windows API programming expertise.
Opening a process
All of the methods presented in this blog will require a handle to another process in order to perform the injection.
The executable that is performing DLL injection, the “injector” usually requires debug privileges in order to be able to successfully open a handle to another process. This can be achieved by enabling the debug privilege token:
[cpp]
BOOL Inject_SetDebugPrivilege()
{
BOOL bRet = FALSE;
HANDLE hToken = NULL;
LUID luid = { 0 };
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
{
TOKEN_PRIVILEGES tokenPriv = { 0 };
tokenPriv.PrivilegeCount = 1;
tokenPriv.Privileges[0].Luid = luid;
tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bRet = AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
}
}
return bRet;
}
[/cpp]
To obtain a handle to a process we need to call the OpenProcess function with the process id of the target. The process id can be obtained from windows TaskManager and passed to the injector application on the command line, or the Microsoft Tool Help library could be used to enumerate all processes and locate a process by name. There are most likely other applicable methods.
[csharp]
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD|
PROCESS_QUERY_INFORMATION|
PROCESS_VM_OPERATION|
PROCESS_VM_WRITE|
PROCESS_VM_READ,
FALSE,
ProcessId );
[/csharp]
In the next sections, I will assume that we already know the process id and have obtained a debug token.
LoadLibrary remote thread
The simplest method of injecting a DLL is to make the target process use the Windows API LoadLibrary call to load the DLL from disc for us.
LoadLibrary calls the DLLMain function in your DLL after it loads the DLL, so this is ideal for bootstrapping your own code. (Note that it is unadvisable to call any thread synchronisation functions from DLLMain because a deadlock can occur.)
The LoadLibrary function takes a pointer to a filename as the parameter:
[csharp]
HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
[/csharp]
And by a stroke of luck, when you create a thread, the thread entry point that you have to provide LPTHREAD_START_ROUTINE also takes a single parameter:
[csharp]
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
[/csharp]
We can exploit this happy coincidence by setting the entry point of our remote thread to be LoadLibrary instead of a ThreadProc and then pass in the DLL filename pointer as the thread parameter.
First we need to open a handle to the process then we can allocate some memory in the target process which will contain the filename of the DLL we want to inject:
[csharp]
const char* pszFileName = "C:injectinject.dll";
//add one to the length for the NULL terminator
const size_t fileNameLength = strlen(pszFileName) + 1;
void* pProcessMem = VirtualAllocEx( hProcess,
NULL,
fileNameLength,
MEM_COMMIT,
PAGE_READWRITE );
WriteProcessMemory( hProcess,
pProcessMem,
pszFileName,
fileNameLength,
NULL );
[/csharp]
We now need to get the address, in the target process of the “LoadLibrary” function. Another happy coincidence helps us out here, is that for most (not all) processes Kernel32.dll is always loaded at the same virtual location, even when ASLR is on. We can therefore get the address of this function in the injector and it will map across to the same virtual address in the target process.
This of course is not always the case so we should perhaps use the Microsoft Tool Help library to enumerate the loaded DLL’s in the target process and obtain the Kernel32 base address. We could the use an offset from this base address to get the address of LoadLibrary. I’ve left this as an exercise for the reader. Here is how we create the thread in the target process:
[csharp]
HMODULE hKernel32 = GetModuleHandle( "Kernel32.dll" );
void* pLoadLib = GetProcAddress( hKernel32, "LoadLibraryA" );
//
// Create a remote thread starting at LoadLibrary
//
DWORD dwThreadId = 0;
HANDLE hThread = CreateRemoteThread( hProcess,
NULL,
0,
pLoadLib, //entry point (LoadLibrary)
pProcessMem, //filename
0,
&dwThreadId );;
[/csharp]
LoadLibrary NTCreateThreadEx variation
A variation on the LoadLibrary technique is to use the undocumented function NTCreateThreadEx. This allows injection across session boundaries, so it’s possible to inject into a process running in a different user’s session.
An example of NtCreateThreadEx usage is given on securityxploded.com
We can simply replace the call to CreateRemoteThread in the previous example with a call to NtCreateThreadEx:
[csharp]
HMODULE hKernel32 = GetModuleHandle( "Kernel32.dll" );
void* pLoadLib = GetProcAddress( hKernel32, "LoadLibraryA" );
struct NtCreateThreadExBuffer
{
ULONG Size;
ULONG Unknown1;
ULONG Unknown2;
PULONG Unknown3;
ULONG Unknown4;
ULONG Unknown5;
ULONG Unknown6;
PULONG Unknown7;
ULONG Unknown8;
};
//
// Obtain NTCreateThreadEx function pointer
//
typedef NTSTATUS (WINAPI *fpNtCreateThreadEx)
(
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
);
HMODULE hNtDLL = GetModuleHandle("Ntdll.dll");
fpNtCreateThreadEx pNtCreateThreadEx =
(fpNtCreateThreadEx)GetProcAddress(hNtDLL,"NtCreateThreadEx");
NtCreateThreadExBuffer ntbuffer = {0};
DWORD temp1 = 0;
DWORD temp2 = 0;
ntbuffer.Size = sizeof(NtCreateThreadExBuffer);
ntbuffer.Unknown1 = 0x10003;
ntbuffer.Unknown2 = 0x8;
ntbuffer.Unknown3 = &temp2;
ntbuffer.Unknown4 = 0;
ntbuffer.Unknown5 = 0x10004;
ntbuffer.Unknown6 = 4;
ntbuffer.Unknown7 = &temp1;
ntbuffer.Unknown8 = 0;
HANDLE hThread = NULL;
pNtCreateThreadEx( &hThread,
0x1FFFFF,
NULL,
hProcess,
pLoadLib,
pProcessMem,
FALSE,
NULL,
NULL,
NULL,
&ntbuffer
);
[/csharp]
The downside to this method is that the function is undocumented so it may change in the future.
LoadLibrary QueueUserAPC variation
If we don’t want to start our own thread, we can hijack an existing thread in the target process, by using the QueueUserAPC function.
[csharp]
DWORD WINAPI QueueUserAPC(
_In_ PAPCFUNC pfnAPC,
_In_ HANDLE hThread,
_In_ ULONG_PTR dwData
);
[/csharp]
Calling this function will queue an asynchronous procedure call on the specified thread. As with the previous methods, it just so happens that the APC callback function prototype more or less matches that of LoadLibrary:
[csharp]
VOID CALLBACK APCProc(
_In_ ULONG_PTR dwParam
);
[/csharp]
So we can simply substitute LoadLibrary instead of a real APC callback function and the parameter can be a pointer to the filename of the dll we wish to inject. One issue with this method revolves around how Windows executes APC’s. Windows has no overarching scheduler looking at the APC queue so the queue is only examined when the thread becomes alertable. This happens when a thread synchronisation call is made such as WaitForSingleObject or SleepEx (and others).
So a ‘hack’ we can employ is to queue the APC on every single thread and hope that at least one of them will become alertable, a prime candidate is the windows message queue thread of a UI application. Another potential ‘hack’ that could be employed would be to use SetThreadContext to set EIP to point at SleepEx, however we may crash the thread by doing this.
The CreateRemoteThread call from previous examples can be replaced by the following:
[csharp]
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot)
{
THREADENTRY32 thEntry = { 0 };
thEntry.dwSize = sizeof(THREADENTRY32);
DWORD processId = GetProcessId(hProcess);
BOOL bEntry = Thread32First(hSnapshot, &thEntry);
//try and open any thread
while (bEntry)
{
if (processId == thEntry.th32OwnerProcessID)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, thEntry.th32ThreadID);
if (hThread )
{
QueueUserAPC((PAPCFUNC)pLoadLib, hThread, ((ULONG_PTR)pProcessMem);
CloseHandle(hThread);
}
}
bEntry = Thread32Next(hSnapshot, &thEntry);
}
CloseHandle(hSnapshot);
}
[/csharp]
SetWindowsHookEx
Another method SetWindowsHookEx, can be used in two ways. It can either inject a DLL into every running process or can be targeted at a specific thread in a process.
[csharp]
HHOOK WINAPI SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
[/csharp]
As Microsoft notes in its documentation, if you call this function from a 32 bit application then it only injects into other 32bit applications. Conversely a 64bit application calling this method only injects into other 64bit applications.
It may be possible to work around this limitation and craft your 32bit injector code to switch into 64bit mode and then call SetWindowsHookEx, the technique is detailed in ReWolf’s blog.
Or, simply create a 64bit and 32bit injector application. Note though that you would need to inject a 64bit DLL into a 64bit process.
Another downside of using SetWindowsHookEx is that if you want to play nicely with Windows, you will need to call UnhookWindowsHookEx from the injector when you have finished with the hook, requiring that your injector application continues to run after hooking and sets up some sort of communication, for example a named pipe or mutex with the hook DLL so that hook removal can be negotiated.
Other methods
There are still further injection methods to investigate, an interesting one exploits shared sections and example of which is the System Tray injection method used by the Win32.Gapz virus, a Metasploit version of this can be found here:
https://github.com/0vercl0k/stuffz/blob/master/gapz_code_injection.cpp
This involves writing some shell code and exploiting a security weakness in Windows so is not as legitimate as the other techniques discussed.
Conclusion
It’s relatively simple to load a DLL into another process by causing LoadLibrary to be invoked on a remote thread as shown above. The QueueUserAPC technique is an interesting one, however it suffers from two problems the first being that it requires an alertable thread and the second is that it’s difficult to determine if the APC has been called so that the memory can be released. A possible solution would be to use a named event in the injected DLL.
Using SetWindowsHookEx has the downside that it can only inject into either 32bit or 64 bit processes, depending on which type of process it’s being called from.
Download the source code
To contact Nettitude’s editor, please email media@nettitude.com.