An Introduction To DLL Injection

What is DLL injection

DLL injection is a way of inserting a dynamic link library into some software whose source code you have no access to, so you can run custom code.

Writing an injector

Let’s begin with with importing some necessary libraries:

1#include <cstdio>
2#include <Windows.h>
3#include <tchar.h>
4#include <psapi.h>

I will be introducing a few Windows API functions, along with an explanation and the MSDN documentation.

Obtaining the PID of the process

We first need to find what the PID of the process we want is, we can do that by enumerating through every process.

Microsoft provides an helpful example

We can modify it slightly to find the process we want:

 1int FindTargetProcessPID(TCHAR* processName) {
 2    TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
 3    DWORD aProcesses[1024], cbNeeded, cProcesses;
 4    unsigned int i;
 5
 6    if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
 7    {
 8        return -1;
 9    }
10
11    cProcesses = cbNeeded / sizeof(DWORD);
12
13    for (i = 0; i < cProcesses; i++)
14    {
15        if (aProcesses[i] != 0)
16        {
17            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
18                PROCESS_VM_READ,
19                FALSE, aProcesses[i]);
20            if (NULL != hProcess)
21            {
22                HMODULE hMod;
23                DWORD cbNeeded;
24
25                if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
26                    &cbNeeded))
27                {
28                    GetModuleBaseName(hProcess, hMod, szProcessName,
29                        sizeof(szProcessName) / sizeof(TCHAR));
30                }
31                if (_tcscmp(szProcessName, processName) == 0)
32                {
33                    CloseHandle(hProcess);
34                    return aProcesses[i];
35                }
36            }
37            CloseHandle(hProcess);
38        }
39    }
40    return -1;
41}
42
43int main(int argc, int** argv)
44{
45    const TCHAR* process = L"notepad.exe";
46    int PID = FindTargetProcessPID((TCHAR*)process);
47    if (PID != -1) {
48        printf("%d", PID);
49    }
50}

We begin by enumerating each process, we obtain an handle to each one, and check if the base module’s name matches what we want.

An handle is an unique reference to a resource, in this case a process.

After we have the PID, we want to request an handle to the process with enough permissions to read and write its memory and create a thread in it.

Requesting an handle to the process

The signature of the function we need:

HANDLE OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);

Its documentation is available here

The documentation for the available flags for dwDesiredAccess is available here

 1HANDLE processHandle = OpenProcess(PROCESS_CREATE_THREAD |
 2    PROCESS_QUERY_INFORMATION |
 3    PROCESS_VM_OPERATION |
 4    PROCESS_VM_WRITE |
 5    PROCESS_VM_READ,
 6    FALSE,
 7    PID);
 8
 9if (processHandle == NULL)
10{
11    printf("%s\n", "Couldn't obtain an handle to the process.");
12    exit(1);
13}

Creating a DLL

For the next step we will need an actual DLL we can inject. You can create a Dynamic Link Library project with Visual Studio. Make sure its built for the same architecture as the target process and the DLL Injector. Replace the generated code with this:

 1BOOL APIENTRY DllMain( HMODULE hModule,
 2                       DWORD  ul_reason_for_call,
 3                       LPVOID lpReserved
 4                     )
 5{
 6    switch (ul_reason_for_call)
 7    {
 8    case DLL_PROCESS_ATTACH:
 9        MessageBox(NULL, L"Hello, World", L"Hello, World", MB_OK);
10        break;
11    case DLL_THREAD_ATTACH:
12    case DLL_THREAD_DETACH:
13    case DLL_PROCESS_DETACH:
14        break;
15    }
16    return TRUE;
17}

When the DLL is injected, it will display a message box. After you build the DLL, place it anywhere you’d like.

Allocating memory on the process

The next function we will use is VirtualAllocEx.

This function lets you allocate memory in another process, and will return the address where it was allocated. We need this because later on we will create a thread in the target process, and we will need a place to store the argument to the function we will start the thread with.

The function we will be starting in the target process is LoadLibrary. This function loads a DLL into the current process and since we will be starting it from a thread we create on the target process, it really means it will load a DLL into the target process. Its signature is as follows:

HMODULE LoadLibraryW(LPCWSTR lpLibFileName);

This means we will need to allocate enough space on the target process for a string with the path of the DLL we want to inject, and then copy the path string to that memory. I placed my DLL in “C:/Dll.dll”, you will want to adjust the path to where you put yours.

1const char* path = "C:\\Dll.dll";
2LPVOID allocatedMemoryLocation = VirtualAllocEx(processHandle, 0, strlen(path) + 1,
3    MEM_RESERVE | MEM_COMMIT,
4    PAGE_READWRITE);
5
6if (allocatedMemoryLocation == NULL) {
7    printf("%s", "Couldn't allocate memory on the process.\n");
8    exit(1);
9}

The documentation for flProtect argument is available here

Writing into the process

Now, we need to write the path string into the memory we just allocated, we can use WriteProcessMemory for that.

1if (!WriteProcessMemory(processHandle, allocatedMemoryLocation, path, strlen(path) + 1, NULL)) {
2    printf("%s", "Couldn't write into the process.\n");
3    exit(1);
4}

Running the DLL

We are almost at the end.

The only thing left to do is create a thread on the target process, with the LoadLibrary function, and with the DLL path as an argument.

First we need the address of the LoadLibrary function. Even though Windows has ASLR, this doesn’t appear to affect where addresses of functions in the kernel32.dll library are located, so we can obtain the function’s address from our own process.

We will use GetProcAddress to obtain the function’s address. The function signature is as follows:

FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);

To obtain the value for the first argument, we will need an handle to the kernel32.dll module.

We can use GetModuleHandle for that, its signature:

HMODULE GetModuleHandleW(LPCSTR lpModuleName);

We will also need to start a thread on the remote process, we can use CreateRemoteThread for that. Its signature:

HANDLE CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );

There’s a lot of arguments, but we can safely ignore most of them by using 0 or NULL and the default values for thread creation will be used. The most important one is dwCreationFlags, which we want to set to 0 so the thread runs immediately once created.

1HMODULE kernel32Module = GetModuleHandle(L"kernel32.dll");
2LPVOID loadLibraryAddress = GetProcAddress(kernel32Module, "LoadLibraryA");
3
4if (CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddress, allocatedMemoryLocation, 0, NULL) != NULL)
5{
6    printf("%s", "DLL successfully injected.\n");
7}
8
9CloseHandle(processHandle);

If all worked correctly, you should see a message box pop up saying “Hello, World”.

Conclusion

This is only one of several methods on DLL Injection, the Wikipedia article contains a few more.

https://en.wikipedia.org/wiki/DLL_injection#Approaches_on_Microsoft_Windows

While writing this article, Windows Defender kept preventing me from running the injector, so you might need to briefly disable it.

I hope this article was helpful, there are lot more methods of DLL injection, but this is one of the simplest ones, while still being interesting. The code for this is available on my GitHub.

Built with Hugo
Theme Stack designed by Jimmy