This article is the second part of my series about remote threads. First part could be found here.
As mentioned in the first part of the tutorial, I needed to create permanently executing thread in the address space of another process. So my problem is following:
- I have a piece of code written by me
- I want to create thread inside another process which will execute this piece of code.
I decided to solve the problem with help of DLL injection, i.e., put my code into a separate DLL, inject DLL inside target process and somehow run my code remotely.
While general principles of DLL injection are well-described (for example, this article is very good), all the sources I found describe only how to inject DLL, and sometimes how to use injected DLL to subclass window control. So I did not find much information about my case and decided to write this series of articles.
In this article, I will provide the solution of my problem and in the next part of the series, I am going to describe how to debug such applications with the help of Visual Studio.
General Structure of the Solution
I split all my code into 2 parts:
- DLL with code which will be injected and executed remotely. In addition to the code which will be injected, I decide to put all the code which performs injection and cleanup into the DLL.
- Client application which will use this DLL.
So my DLL will have three exported functions:
- Function which will inject DLL into the target process and start remote thread. It is called
InjectAndStartThread
. - Function which ejects DLL from target process and performs cleanup. It is called
EjectLibrary
. - Function with the code which should be executed in the remote thread. It is called
RemoteMethod
.
Here are the prototypes of these functions and some variables which will be used in the code lately:
HMODULE hThisModule = NULL;
char StartingMethod[] = "_RemoteMethod@4";
extern "C" __declspec(dllexport) HANDLE InjectAndStartThread(DWORD targetPid)
{
}
extern "C" __declspec(dllexport) void EjectLibrary(DWORD targetPid)
{
}
extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
}
BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
hThisModule = (HMODULE)hinstDLL;
return TRUE;
}
Injection and Starting of the Remote Thread
The process of creating remote thread with my code I split into 2 parts:
- Inject my DLL into the address space of the target process.
- Execute method of the injected DLL in the address space of target process remotely.
Inject Library into Address Space of Another Process
To inject my DLL into target process, I used a well-described technique with CreateRemoteProcess
and LoadLibrary
:
- Allocate memory region in address space of the target process with
VirtualQueryEx
function - Write name of the desired DLL to the allocated region with
WriteProcessMemory
function - Call
CreateRemoteThread
and pass it handle to target process, address of LoadLibrary
as called function and a pointer to the memory region obtained at step 1 as the argument to the function. CreateRemoteThread
will return handle to remote thread. - Call
WaitForSingleObject
with the handle of remote thread obtained at step 3. Once this step is completed, our DLL is loaded into target process. - Call
GetExitCodeThread
and pass it handle to remote thread. This function will get return value of the remote thread. This value will be value returned by LoadLibrary
, i.e., base address of our DLL in the address space of the target process. We will need it lately. - Do some clean up: free memory which was allocated at step 1, close handle to the remote thread which was obtained at step 3.
The source code of the algorithm is the following (error handling is excluded for brevity):
HANDLE hTargetProcess = NULL;
wchar_t moduleName[MAX_PATH];
LPVOID pRemoteMem = NULL;
HANDLE hRemoteThread = NULL;
HANDLE result = NULL;
hTargetProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE, FALSE, targetPid);
memset(moduleName, 0, sizeof(wchar_t) * MAX_PATH);
GetModuleFileNameW(hThisModule, moduleName, MAX_PATH);
moduleName[MAX_PATH - 1] = 0;
int remoteMemSize = sizeof(wchar_t) * (lstrlenW(moduleName) + 1);
pRemoteMem = VirtualAllocEx(hTargetProcess, NULL, remoteMemSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hTargetProcess, pRemoteMem, moduleName, remoteMemSize, NULL);
PTHREAD_START_ROUTINE pThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress
(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
hRemoteThread = CreateRemoteThread(hTargetProcess, NULL, 0, pThreadRtn, pRemoteMem, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
DWORD injectedDllBase = 0;
GetExitCodeThread(hRemoteThread, &injectedDllBase);
VirtualFreeEx(hTargetProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
After this code executed, injectedDllBase
variable will store base address of the loaded library inside address space of the target process.
Execute Method in the Injected DLL
To execute method remotely, we need to obtain its address inside address space of the target process. In the first part, I showed how this could be done, so the code will be following (it is assumed that hThisModule
stores base address of the DLL):
HMODULE hThisModule;
char StartingMethod[] = "_RemoteMethod@4";
FARPROC localMethodAddr = GetProcAddress(hThisModule, StartingMethod);
DWORD methodDelta = (DWORD) localMethodAddr - (DWORD)hThisModule;
DWORD remoteMethodAddr = injectedDllBase + methodDelta;
When we obtain address, we just can create remote thread:
hWorkingRemoteThread = CreateRemoteThread(hTargetProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)remoteMethodAddr, NULL, 0, NULL);
Here you can call WaitForSingleObject(hWorkingRemoteThread, INFINITE)
if you want to wait until created thread will terminates.
And remote method should have the following prototype (if you use Visual C++):
extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
}
Here, I want to emphasize the following things:
- When you create thread with help of
CreateRemoteThread
function, thread start proc should have stdcall calling convention. That’s why I declare RemoteMethod
with __stdcall
prefix. - Visual Studio decorates exported names of functions with
stdcall
calling convention. That’s why I declare variable StartingMethod
as "_RemoteMethod@4"
.
Tips about Creating Thread into Another Process
At the point we have already injected DLL into another process and can call methods of this DLL by creating remote thread into target process. Called function can do anything, so actually the goal of creating thread (which will do what we want) inside another process is achieved.
But there is one more tip. In “Windows via C/C++” book, there is a chapter about threads and C++. In this chapter, it was noted that if you use C/C++ runtime library in your thread, you should create this thread via _beginthread
/_beginthreadex
function. It is required for correct cleanup when thread terminates.
And because we want to write reliable software without memory leaks, we should write the following code:
unsigned int __stdcall WorkingThread(void *)
{
}
extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
HANDLE hAnotherThread = (HANDLE)_beginthreadex(NULL, 0, ActualWorkingThread, NULL, 0, NULL);
WaitForSingleObject(hAnotherThread, INFINITE);
CloseHandle(hAnotherThread);
return 0;
}
Clean Up and Unload our DLL from Target Process
To cleanup and unload our DLL from target process, we should do following:
- Somehow tells out thread that is should terminate. In demo application, I perform all communications with remote thread via named pipe.
- Wait until our remote thread will be terminated. To achieve this, we use
hWorkingRemoteThread
handle obtained at section 2 and call WaitForSingleObject(hWorkingRemoteThread, INFINITE)
. If we try to unload our DLL until working remote thread will be terminated, we can crash the application. - Call
GetModuleHandle
remotely to obtain base address of our DLL inside target process. - Call
FreeLibrary
remotely and pass it handle obtained at step 3.
So the following code should be executed when we are sure that the remote thread is terminated:
HANDLE hTargetProcess = NULL;
wchar_t moduleName[MAX_PATH];
LPVOID pRemoteMem = NULL;
HANDLE hRemoteThread = NULL;
hTargetProcess = OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, targetPid);
memset(moduleName, 0, sizeof(wchar_t) * MAX_PATH);
GetModuleFileNameW(hThisModule, moduleName, MAX_PATH);
moduleName[MAX_PATH - 1] = 0;
int remoteMemSize = sizeof(wchar_t) * (lstrlenW(moduleName) + 1);
pRemoteMem = VirtualAllocEx(hTargetProcess, NULL, remoteMemSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hTargetProcess, pRemoteMem, moduleName, remoteMemSize, NULL));
PTHREAD_START_ROUTINE pThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetModuleHandleW");
hRemoteThread = CreateRemoteThread(hTargetProcess, NULL, 0, pThreadRtn, pRemoteMem, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
DWORD injectedDllBase = 0;
GetExitCodeThread(hRemoteThread, &injectedDllBase);
VirtualFreeEx(hTargetProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
pThreadRtn =
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "FreeLibrary");
hRemoteThread = CreateRemoteThread
(hTargetProcess, NULL, 0, pThreadRtn, (LPVOID)injectedDllBase, 0, NULL);
CloseHandle(hRemoteThread);
VirtualFreeEx(hTargetProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hTargetProcess);
Notes About Creating Threads in the DllMain: Do Not Do This!
My first goal was to create permanently running thread inside another process. So you may ask why I did a lot of stuff and not just inject DLL and create thread from the DllMain
function.
Here is the answer. There is a document from Microsoft about writing DLLs: “Dll best practices”. Process of loading DLLs is described in this document. There are special things about this process which impose several restrictions on what you can do inside DllMain
function. The list is quite long, but among other things, it is not recommended to create other threads and synchronize with other threads because of possible deadlocks. Actually, it is stated that creating threads from DllMain
may work, but I decided not to rely on luck.
Demo Application
Demo application allows you to select target process (by entering its PID) and injects thread into it. Injected thread could read memory of the process into which it is injected by demand of the main part of the demo application:
CodeProject