Tutorials on Windows DLL injections in C have noticable gaps in what they explain. This blog post plus the comments on my implementation should address most questions a newcomer might have. Here’s my code on GitHub. Note that most of my code is directly taken from the Microsoft Developer Network (MSDN).
If you find this post useful, I encourage you to follow my Twitter account, where I post more tutorials and low-level explanations.
You are designing a malicious process that can “inject” a DLL into a victim process using
CreateRemoteThread. There are two approaches we can take:
Allocate enough space in the remote process for just the DLL’s pathname (e.g. “C:\Windows\System32\NotMalicious.dll”), and write only the pathname to that process’s memory. Have the remote process then load the DLL by calling
LoadLibrary, which accepts a path to a DLL as an argument.
LoadLibrarywill then do the work of mapping the DLL into the process’s address space for you.
Allocate enough space in the remote process for the actual contents of the DLL. Write the entire contents of the DLL into the remote process manually, bypassing the need to call
The benefit of option 2 is that it is quieter. When a process calls
LoadLibrary to load a DLL, a data structure within that process gets updated to reflect that the new DLL has been loaded. Thus, anyone monitoring processes on the system can see the names of every DLL loaded into every process when done with
The drawback of option 2 is that it is more complicated. You can’t just copy and paste the bytes of the DLL into the remote process’s memory and interact with it the way you would expect. Manually dealing with the relative offsets within the DLL can be tricky when the process has no idea a DLL exists in its memory.
Tools exist to abstract some of these issues away from option 2. Since we want to implement a basic injection from scratch, we examine option 1 in this post. First, let’s examine the workflow involved in a basic DLL injection.
- Allocate memory in the remote process big enough for the DLL path name.
- Write the DLL path name to the space you just allocated in the remote process.
- Find the address of
LoadLibraryin your own malicious process (which will be the same as the address of
LoadLibraryin the victim process), and store that memory address. I explain how this works in the next section.
CreateRemoteThreadto create a remote thread starting at the memory address from step 3 (which means this will execute
LoadLibraryin the remote process). Besides the memory address of the remote function you want to call,
CreateRemoteThreadalso allows you to provide an argument for the function if it requires one.
LoadLibrarywants the memory address of where you wrote that DLL path from earlier, so provide
CreateRemoteThreadthat address as well.
Now that you’ve seen the general workflow, let’s break some of these steps down.
Get a Handle
There are two processes involved in this attack: your DLLInjector process (Process A), and the remote process you want to inject with a DLL (Process B). To interact with the remote process, Process A must call
OpenProcess() while passing the remote process’s process ID as an argument.
OpenProcess will then return to Process A a
Handle to Process B.
Handle, defined in
Windows.h, is a
typedef (or alias) for a
void*, or a pointer to anything. Having a
Handle to the remote process allows Process A to interact with it in powerful ways. Process A can allocate memory, write memory, and create an execution thread in Process B by calling functions like
CreateRemoteThread and passing the
Handle to Process B as an argument to those functions.
Kernel32.dll and LoadLibrary
Kernel32.dll is loaded into every Windows process, and within it is a useful function called
LoadLibrary is called in a certain process, it maps a DLL into that process.
LoadLibrary needs to know what DLL to load, so you need to provide it the path to the DLL on your system.
LoadLibrary will then find the DLL at that path and load that DLL into memory for you.
We can call any function we want in Process B by calling
CreateRemoteThread in Process A and passing the
Handle to Process B as an argument.
CreateRemoteThread needs to know what function to execute in Process B; in our case, it needs the address of the
LoadLibrary function in Process B.
Finding the location of
LoadLibrary in Process B is easy. This is because Windows guarantees that all the core DLLs get loaded in the same spot in the same boot session. This means every time you boot your computer, and you check where
Kernell32.dll is loaded in a process, it will be at the same location within any other running process. That goes the same for any functions inside
Kernell32.dll, such as
GetModuleHandle will return the base address of a DLL that is loaded into your process. By passing
kernell32.dll as an argument to
GetModuleHandle in Process A, we now know where
kernell32.dll resides in memory for both Process A. Since
kernell32.dll is a core DLL, it will reside at the same virtual address in Process B. We can then use
GetProcAddress to find the
LoadLibrary function inside
kernell32.dll within Process A. Again, this function will have the same virtual address in Process B.
LoadLibraryA is the function name. “A” means you provide the DLL path as an ASCII string.
Allocating Memory for the DLL Path
Why do we write the DLL path to Process B using
VirtualAllocEx and then
WriteRemoteMemory? Because, as you just saw,
LoadLibrary needs to know what DLL you want to inject. The string it accepts as a parameter needs to be present in Process B’s memory so it somewhere so it can actually use it.
This is why, from the workflow above, we first allocate memory in Process B large enough for that string, and then write that string to that block of memory.
This function creates a snapshot of every process currently running on the system, and it requires you to
#include "tlhelp32.h". Documentation and examples here.
You can iterate through the list of processes returned from the snapshot and compare it against a certain process name you’re looking for, like so:
WCHAR and char discrepancies
Let me save you some Googling.
pe32, declared just above this code block, is a
PROCESSENTRY struct. Its member
szExeFile is an array of
WCHAR, which means each index in the array refers to a UTF-16 value. If you design your program (like mine) to accept a process name from the command line, and your IDE interprets command-line arguments as UTF-8 strings, you will need to first convert that command-line argument into an array of
WCHAR. From here, you can use use
wcscmp, which compares two UTF-16 values. Alternatively, you could convert the
pe32.szExeFile value to a
const char array and use
I stuck with the
WCHAR data type, which is why I used
wmain instead of
main to start my program.
wmain interprets the command-line arguments passed in as
WCHAR arrays rather than
My program accepts two arguments, as in
DLL_Injector.exe <Executable_Name> <Path_to_DLL_to_Inject>. I had no reason for the DLL path to be Unicode, so I converted it back to a
const char like so:
Confirming it worked
Since we mapped the DLL to the remote process using
LoadLibrary, we will see the DLL registered in the victim process when viewed in ProcessExplorer.
Have fun with this – I know I did. Once again, here’s my code on GitHub.