Using CreateRemoteThread for DLL injection on Windows
Note: all the code examples can be found on my Github profile under visual-studio-projects accessible here: https://github.com/proteansec .
Become a certified reverse engineer!
In this tutorial, we'll talk about how to inject a custom DLL into the process's address space by using the CreateRemoteThread function call. The CreateRemoteThread function creates a thread in the virtual address space of an arbitrary process. Let's take a look at the parameters we must pass to the functions, which we can see on the picture below (the picture was taken from [1]):
It's immediately clear that we must pass a value in the first six parameters, but the function writes some value into the last parameter. Let's describe the parameters a little bit more [1]:
- hProcess: handle to the process where we'll create a new thread
- lpThreadAttributes: a pointer to the SECURITY_ATTRIBUTES structure, which specifies the security attributes of the new thread: if NULL, the thread will have default security attributes and the handle cannot be inherited by the child process
- dwStackSize: initial size of the stack
- lpStartAddress: a pointer to the LPTHREAD_START_ROUTINE, which is a function that will be executed by the new thread. It's needless to say that the function must exists in the remote process.
- lpParameter: a pointer to a variable to be passed to the thread function
- dwCreationFlags : a value that controls the creation of the thread
- lpThreadId: a pointer to a variable that receives the thread ID
If the function succeeds, the returned value is a handle to the new thread. Otherwise, the function returns NULL.
We've just seen that the CreateRemoteThread function can be used to start a new thread in the address space of some process.
Now it's time to present the whole process we'll be using to inject a DLL into the process' address space. To get a clear indication of what we're going to do, take a look at the picture below, where the process we'll be injecting a DLL into is marked with purple color and has a name victim.exe. But, there are two other pieces of the puzzle we need to clarify. First, we need to establish that if we want to inject a DLL into some process, we must first have the DLL we would like to inject. The DLL is presented with the green color and has a name inject.dll. But we must also have a program that will do the injection of the DLL into the victim's address space. That program is presented in blue and has a name program.exe.
The program.exe must call the presented functions sequentially in order to be able to inject a DLL into the victim's address space. First, it must call OpenProcess to get a handle to the victim's process. Afterwards it must call GetProcAddress function to get the address of the LoadLibraryA function inside the kernel32.dll library; here we can run any function we like, but it must be present in a DLL, which is already loaded in the process's address space. We know that every program uses kernel32.dll library, so the best way to inject a DLL into the process's address space is looking for the LoadLibraryA function and calling that. In order for our DLL to be loaded, we must pass a DLL path to the LoadLibraryA function, but the name needs to be stored somewhere inside the processes address space. Obviously, it's highly unlikely for the path to our DLL to already be present somewhere in the process's address space, which is why we need the next two functions: VirtualAllocEx and WriteProcessMemory. The first function allocates a new memory range inside the process's address space. The size of that memory region needs to be only as large to fit the name of the DLL inside it; usually the size is rounded up to occupy at least one page. The WriteProcessMemory is the function that actually writes the path of our DLL to the victim's address space. At last, the CreateRemoteThread is called that calls the LoadLibraryA function inside the victim's address space to inject a DLL into it.
Creating the inject.dll
The first step when injecting the DLL into some process's address space is creating the DLL itself. We won't go into the details on how to do that, since it's pretty much self-explanatory. We need to start a new project inside Visual Studio and select DLL when creating it. After that, we can change the dllmain.c source code into something that looks like the following:
[cpp]
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) {
/* open file */
FILE *file;
fopen_s(&file, "C:temp.txt", "a+");
switch(Reason) {
case DLL_PROCESS_ATTACH:
fprintf(file, "DLL attach function called.n");
break;
case DLL_PROCESS_DETACH:
fprintf(file, "DLL detach function called.n");
break;
case DLL_THREAD_ATTACH:
fprintf(file, "DLL thread attach function called.n");
break;
case DLL_THREAD_DETACH:
fprintf(file, "DLL thread detach function called.n");
break;
}
/* close file */
fclose(file);
return TRUE;
}
[/cpp]
We can see that we have a pretty simple DLL. The DllMain() function is called when the DLL is loaded into the process's address space. Upon that, one of the four messages is written to the C:temp.txt file based on the reason why the function was called.
Creating the program.exe
In this section of the article, we'll take a look at all the functions we'll be using when injecting a DLL into the process's address space. Let's first present a complete source code of the program we'll be using, which can be seen below:
[cpp]
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[]) {
char* buffer = "C:driversdllinject.dll";
/*
* Get process handle passing in the process ID.
*/
int procID = 1056;
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);
if(process == NULL) {
printf("Error: the specified process couldn't be found.n");
}
/*
* Get address of the LoadLibrary function.
*/
LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if(addr == NULL) {
printf("Error: the LoadLibraryA function was not found inside kernel32.dll library.n");
}
/*
* Allocate new memory region inside the process's address space.
*/
LPVOID arg = (LPVOID)VirtualAllocEx(process, NULL, strlen(buffer), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if(arg == NULL) {
printf("Error: the memory could not be allocated inside the chosen process.n");
}
/*
* Write the argument to LoadLibraryA to the process's newly allocated memory region.
*/
int n = WriteProcessMemory(process, arg, buffer, strlen(buffer), NULL);
if(n == 0) {
printf("Error: there was no bytes written to the process's address space.n");
}
/*
* Inject our DLL into the process's address space.
*/
HANDLE threadID = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)addr, arg, NULL, NULL);
if(threadID == NULL) {
printf("Error: the remote thread could not be created.n");
}
else {
printf("Success: the remote thread was successfully created.n");
}
/*
* Close the handle to the process, becuase we've already injected the DLL.
*/
CloseHandle(process);
getchar();
return 0;
}
[/cpp]
Now we'll explain various functions in the program so we can understand what the program actually does. First, let's take a look at the OpenProcess function, which syntax can be seen below [2]:
We can see that we must pass three parameters to the functions, where the parameters are the following:
If the function succeeds it returns a value to the open handle to the specified process, otherwise it returns NULL.
The next functions we need to look at are GetModuleHandle and GetProcAddress. We won't describe those functions, since they are so well known and were described in many of my tutorials. Let's just present the exact line, which we'll use:
[cpp]
LPVOID addr = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
[/cpp]
That line basically stores the address of the LoadLibraryA function inside kernel32.dll library into a variable addr.
The next function is VirtualAllocEx, into which we need to take a closer look. Let's present the syntax of the function, which can be seen below [3]:
The function takes five parameters [3]:
- hProcess: a handle to the process in which the virtual space will be allocated.
- lpAddress: a pointer to the starting address of the memory region that we want to allocate. If we pass a NULL value as this parameter, the function will determine the address by itself.
- dwSize: the size of the region of the memory that we want to allocate.
- flAllocationType: the type of memory location, where we can choose from the following values:
- MEM_COMMIT
- MEM_RESERVE
- MEM_RESET
- MEM_RESET_UNDO
- flProtect: memory protection for the memory region to be allocated.
If the function succeeds it returns the base address of the allocated memory region, otherwise it returns NULL.
The next function is WriteProcessMemory, which syntax can be seen below [4]:
The parameters passed to the function are the following:
If the function succeeds, it returns a non-zero number, otherwise it returns zero.
Seeing everything in action
Here we'll see whether our injection program works. First, start OllyDbg and load putty.exe program. Upon starting, the putty.exe will load the modules presented on the picture below:
After that, we need to check the PID of the putty.exe process, which we can see in the Task Manager as seen in the picture below:
The PID of the putty.exe is 2720. This is also the PID we must put into the code of our previously presented program. We must change the code to look like this:
Notice that we changed the procID variable to 2720, which is the PID of the process. If this is not correct, then our program will try to inject the DLL into some other process, which might not exist. In such case, the program will most certainly fail. When changing the PID of the program, we must recompile our program and run it. The program will inject the DLL into the putty.exe process and display the following if everything is done successfully:
If we check the loaded modules after that, we can see that the dllinject.dll module was successfully loaded into the process's address space, which we can see below:
After that, we also need to check whether appropriate messages have been written to the C:temp.txt. We can see the contents of that file on the picture below:
Everything looks ok and the DLL was successfully injected into the putty's address space.
Conclusion
We've seen how we can inject a DLL into the process's address space with using the CreateRemoteThread function. The attacker can use this method to hook certain function the process's IAT import table to gather useful information about the process/user.
Become a certified reverse engineer!
Sources
[1] CreateRemoteThread function
[4] WriteProcessMemory function
[5] A More Complete DLL Injection Solution Using CreateRemoteThread,