Click here to Skip to main content
15,890,512 members
Articles / Containers / Virtual Machine

Remote Threads Basics. Part 1

Rate me:
Please Sign up or sign in to vote.
4.71/5 (7 votes)
9 Jun 2010CPOL6 min read 39.7K   15   7
In this part of the tutorial, general information about remote thread is described: what are remote threads, how they can be created and used.

For my virtual machine communication engine, I needed to create permanently executing thread in the address space of another process. I solved the problem using remote threads and in this series of articles, I am going to describe this in detail.

I split my series of articles about remote thread into 3 parts:

  • In this part, I'll describe general information about remote threads: what are they and how they could be used in Windows.
  • In the second part, I will write how to make a clear solution with correct cleanup of the above mentioned problem (creating permanently executing thread in another process)
  • In the last part, I will describe how remote threads could be debugged.

To understand all the presented information, the reader should be familiar with processes, threads, DLLs. Also some basic knowledge about DLL injection could be useful. I can recommend ‘Windows via C/C++’ book as a source of huge amount of information about all the mentioned topics.

What are Remote Threads

Each Windows process could be characterized as a group of several threads which are running in the same virtual address space. Another process with all its threads is running inside another virtual address space. In the context of any process thread can be called remote if it is executed inside the address space of another process.

The following picture demonstrates the mentioned concepts:

Processes and threads in windows

In the picture, there are two processes: A and B. Process A has two own threads: A1 and A2, and process B has three threads: B1, B2, B3. Threads B1-B3 in context of process A are remote threads because they are running inside another address space.

Usually the concept of remote thread is used in situations when it is required to communicate with threads inside another process or create threads inside another process.

Family of Windows OSes have capabilities to operate and communicate with remote threads.

Creating Remote Threads

Remote threads could be created with WinAPI function CreateRemoteThread:

C++
HANDLE WINAPI CreateRemoteThread(
  __in   HANDLE hProcess,
  __in   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in   SIZE_T dwStackSize,
  __in   LPTHREAD_START_ROUTINE lpStartAddress,
  __in   LPVOID lpParameter,
  __in   DWORD dwCreationFlags,
  __out  LPDWORD lpThreadId
);

The most important arguments of the function are as follows:

  • hProcess – handle of process into which we want to create remote thread
  • lpStartAddress – entry point of remote thread inside target process address space
  • lpParameter – parameter which should be passed to remote thread

Rest of the arguments can be specified as NULL or 0.

So to create a remote thread, you at least should obtain a handle to target process and get entry address of the thread you are going to create. Also you may want to pass some information to remote thread function.

1. Obtaining Handle to Target Process

Handle to target process – hProcess could be obtained in the following way:

C++
DWORD targetPid = /* PID of target process */;
hProcess = OpenProcess(PROCESS_CREATE_THREAD | 
	PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, targetPid);

The first argument of OpenProcess function specifies desired access to target process. For our purpose, we need to specify at least PROCESS_CREATE_THREAD. If we want to allocate and write some memory inside target process, we also need to specify PROCESS_VM_OPERATION and PROCESS_VM_WRITE flags.

The second argument is connected with handle inheritance. We do not need this, so just pass FALSE.

And the last argument – is target process ID. It could be obtained with the help of toolhelp functions, concise article on the topic is here. Also we can just look into Windows Task Manager for PID and via UI tell our program about it.

Remember to close handle to the process when it becomes not necessary:

C++
CloseHandle(hProcess);

2. Entry Point of Remote Thread

We should pass address to remote thread entry point as the fourth argument to CreateRemoteThread function. The prototype of remote thread entry point is as follows:

C++
unsigned int __stdcall RemoteThreadMethod(void *parameter)
{
    // place code here
}

Note that the calling convention of the remote method is stdcall.

Also it should be noted that RemoteThreadMethod should be placed into virtual address space of target process. Details about placing arbitrary function inside another process via DLL-injection will be covered in the second part of tutorial. But anyways, nothing stops us to call functions from ‘Kernel32.dll’ remotely or any other system DLL which is loaded to target application.

There are at least two functions from ‘Kernel32.dll’ which prototypes are very similar to prototype of remote thread entry point: GetModuleHandle and LoadLibrary. Actually the following functions are exported: GetModuleHandleA, GetModuleHandleW, LoadLibraryA and LoadLibraryW (‘A’ – ANSI version, ‘W’ – Unicode version). So depending on the encoding of passed argument, we should choose ANSI or Unicode version.

You should remember that the same DLL could be mapped at different addresses in different processes, so in general case we can’t just use the address of the function. However in the case of ‘Kernel32.dll’, it maps into the same address in all processes in the system, so we can just use the following code to obtain address (inside target process) of mentioned functions:

C++
PTHREAD_START_ROUTINE pThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress
			(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pThreadRtn, argument, 0, NULL);

But of course, in general case, the code will be as follows:

C++
PTHREAD_START_ROUTINE pThreadRtn = /* somehow obtain pointer to desired function*/;
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pThreadRtn, argument, 0, NULL);

Lately I’ll show how this could be achieved.

3. Passing Arguments to Remote Thread Function

In some cases, we do not need to pass an argument or we are satisfied with one 4-bytes (8 bytes in 64-bit mode) parameter. But also it could be required to pass more than 4 bytes to the remote thread. In such a case, we can allocate memory buffer, write all the data into this buffer and pass buffer’s pointer to the remote thread as an argument. The important thing to remember is that we should put our memory buffer into the address space of target process. This could be done with the help of functions VirtualAllocEx and WriteProcessMemory; cleanup is performed with VirtualFreeEx function:

C++
BYTE[remoteMemSize] dataToWrite = /* ... */;

// ...

// allocating memory
void* pRemoteMem = VirtualAllocEx(hProcess, NULL, remoteMemSize, 
		MEM_COMMIT, PAGE_READWRITE);
// writing memory
WriteProcessMemory(hProcess, pRemoteMem, dataToWrite, remoteMemSize, NULL);

// ...

// pass pointer to remote memory as an argument of the remote thread function
hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, pThreadRtn, pRemoteMem, 0, NULL);

// ...

// cleanup

VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);

4. Determine Whether Remote Thread is Terminated and Obtaining its Return Value

In section 2, after creating a remote thread, we obtained handle hRemoteThread. We can use it to determine whether the thread is terminated and to get return value of thread’s function.

The following code will check whether the thread is terminated:

C++
DWORD result = WaitForSingleObject(hRemoteThread, 0);
if (result == WAIT_OBJECT_0)
{
    // thread is terminated
}

And the following code will wait until remote thread termination:

C++
WaitForSingleObject(hRemoteThread, INFINITE);

Return value of the remote thread could be obtained with help of GetExitCodeThread function:

C++
DWORD threadRetval = 0;
GetExitCodeThread(hRemoteThread, &threadRetval);

If this code will be executed for thread which is not terminated, then STILL_ACTIVE will be written into the variable threadRetval. So actually you may use GetExitCodeThread to test whether thread is terminated, however in such case you should be sure that the thread function can’t return value equals STILL_ACTIVE.

Example of Usage: Obtaining Address of any DLL via Remote Threads

As an example, I provide a simple application which can determine the address of any DLL loaded into the target process and address of any function in this DLL. DLL address is obtained via remote call to GetModuleHandle. The following picture demonstrates how the address of any function in the DLL could be determined:

Dll mapping

For any DLL library, address of any function can be determined as a sum of DLL Image base address and function offset. Function offset is a constant for given DLL and function in it. Image base is the DLL loading address, it could be determined as the result of calling GetModuleHandle or LoadLibrary.

So if we want to determine base address of any DLL loaded into target process, we can just call GetModuleHandle remotely. And to calculate address of any exported function in the target process, we can calculate offset of the function from the beginning of the DLL and then add this offset to DLL base address:

DWORD result = /* DLL address inside target process, 
		obtained earlier as remote call to GetModuleHandle */;
HMODULE hModule = LoadLibrary("DllName.Dll");
FARPROC functionAddr = GetProcAddress(hModule, "FunctionName");
DWORD functionOffs = (DWORD)functionAddr - (DWORD)hModule;
DWORD remoteFunctionAddr = functionOffs + result;
FreeLibrary(hModule);

An example application could be downloaded here. It allows you to specify PID of target process, DLL name and function name. Then it calls GetModuleHandle remotely and outputs information about DLL base and function offset as follows:

This article was originally posted at http://www.avk.name/feeds/posts/default

License

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


Written By
Russian Federation Russian Federation
My name is Alexey.

I am PhD student at MIPT in the area of Computer Vision. In 2009, I am obtained MS degree in the area of Data Mining and Machine Learning. Besides my scientific interests, I am fond of creating complicated software and solve difficult technical problems.

I am experienced C# developed. Also I have skills in Matlab, C++, Assembler and some other programming languages.

Comments and Discussions

 
GeneralMy vote of 5 Pin
gndnet23-Sep-12 5:49
gndnet23-Sep-12 5:49 
GeneralMy vote of 5 Pin
Member 43208445-Jul-10 19:46
Member 43208445-Jul-10 19:46 
GeneralASLR + non-exported functions Pin
S.H.Bouwhuis21-Jun-10 22:20
S.H.Bouwhuis21-Jun-10 22:20 
GeneralRe: ASLR + non-exported functions Pin
Alexey Kurakin23-Jun-10 7:29
Alexey Kurakin23-Jun-10 7:29 
GeneralRe: ASLR + non-exported functions Pin
S.H.Bouwhuis23-Jun-10 10:11
S.H.Bouwhuis23-Jun-10 10:11 
GeneralUsing offset from DLL base is not portable Pin
Thandermax15-Jun-10 22:29
Thandermax15-Jun-10 22:29 
GeneralRe: Using offset from DLL base is not portable Pin
Alexey Kurakin15-Jun-10 23:23
Alexey Kurakin15-Jun-10 23:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.