Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Remote Threads Basics - Part 2

5.00/5 (4 votes)
28 Jun 2010CPOL6 min read 30.4K  
This article is the second part of my series about remote threads. In this part, I describe how you can create permanently executing thread with any of your code inside address space of another process.

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:

C++
// in this variable DLL loading address will be stored
HMODULE hThisModule = NULL;

// exported name of the method which will be called remotely
// Visual Studio decorates names of exported functions with stdcall calling convention
char StartingMethod[] = "_RemoteMethod@4";

/**
 * method injects dll into target process and starts remote thread
 *
 * @param targetPid - target process id
 * @return handle to thread which will exit after remote thread terminates
 */
extern "C" __declspec(dllexport) HANDLE InjectAndStartThread(DWORD targetPid)
{
    // ...
}

/**
 * method ejects dll from specified process
 * this function should be called only when remote thread terminates,
 *  otherwise you can crash target application
 *
 * @param targetPid - target process id
 */
extern "C" __declspec(dllexport) void EjectLibrary(DWORD targetPid)
{
    // ...
}

/**
 * this is entry point of the remote thread
 * it exits after remote thread finished
 */
extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
    // ...
}

BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
    // in DllMain hThisModule variable is initialized
    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:

  1. Inject my DLL into the address space of the target process.
  2. 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:

  1. Allocate memory region in address space of the target process with VirtualQueryEx function
  2. Write name of the desired DLL to the allocated region with WriteProcessMemory function
  3. 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.
  4. Call WaitForSingleObject with the handle of remote thread obtained at step 3. Once this step is completed, our DLL is loaded into target process.
  5. 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.
  6. 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):

C++
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):

C++
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:

C++
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++):

C++
extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
    // do something
}

Here, I want to emphasize the following things:

  1. 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.
  2. 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:

C++
unsigned int __stdcall WorkingThread(void *)
{
    // here the real work is done
}

extern "C" __declspec(dllexport) unsigned int __stdcall RemoteMethod(void *)
{
    // This function just creates another thread with help of _beginthreadex function
    // and waits until the thread terminates
    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:

  1. Somehow tells out thread that is should terminate. In demo application, I perform all communications with remote thread via named pipe.
  2. 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.
  3. Call GetModuleHandle remotely to obtain base address of our DLL inside target process.
  4. 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:

C++
HANDLE hTargetProcess = NULL;
wchar_t moduleName[MAX_PATH];
LPVOID pRemoteMem = NULL;
HANDLE hRemoteThread = NULL;

// open process
hTargetProcess = OpenProcess(PROCESS_CREATE_THREAD | 
                 PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, targetPid);

// run first remote thread - GetModuleHandle
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);

// run second remote thread - FreeLibrary
pThreadRtn = 
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "FreeLibrary");
hRemoteThread = CreateRemoteThread
                (hTargetProcess, NULL, 0, pThreadRtn, (LPVOID)injectedDllBase, 0, NULL);

// cleanup
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:

Memory of target process from VS debugger
Demo application

Image 4

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)