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).
Implementing this was a fun exercise and something more people will find interesting. Thanks to Nick Harbour for fielding my questions.
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.
- 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.
Kernel32.dll and LoadLibrary
Kernel32.dll is loaded into every Windows process, and within it is a useful function called
LoadLibrary loads a DLL into the process that called it. It accepts as an argument the string in memory containing the path to the DLL you want to load.
LoadLibrarywill then read in the string at that memory address, find the DLL at that path, and load that DLL into memory for you.
We can call any function we want in the remote process using
CreateRemoteThread from our current malicious process.
CreateRemoteThread needs to know where to start executing in the victim process; in our case, it needs the address of the
LoadLibrary function in the victim process.
Finding the location of
LoadLibrary in the remote process is easy. This is because Windows 7 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
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.