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. LoadLibrary will 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 LoadLibrary.
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 LoadLibrary.
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 LoadLibrary in your own malicious process (which will be the same as the address of LoadLibrary in the victim process), and store that memory address. I explain how this works in the next section.
Use CreateRemoteThread to create a remote thread starting at the memory address from step 3 (which means this will execute LoadLibrary in the remote process). Besides the memory address of the remote function you want to call, CreateRemoteThread also allows you to provide an argument for the function if it requires one. LoadLibrary wants the memory address of where you wrote that DLL path from earlier, so provide CreateRemoteThread that address as well.
Kernel32.dll and LoadLibrary
Kernel32.dll is loaded into every Windows process, and within it is a useful function called LoadLibrary. 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 LoadLibrary.
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 strcmp.
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 char arrays.
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.