I was reading a discussion about dumping a processes part of a process's memory and someone suggested using DLL injection to do this. I'll be honest in that I don't really understand. How does DLL injection work and what kinds of reversing tasks can you do with it?
DCoder's answer is a good one. To expand somewhat, I most often use DLL injection in the context of forcing an existing process to load a DLL through CreateRemoteThread. From there, the entrypoint of the DLL will be executed by the operating system once it is loaded. In the entrypoint, I will then invoke a routine that performs in-memory patching of all of the locations within the original binary that interest me, and redirects their execution into my DLL via a variety of modifications. If I am interested in modifying or observing the process' interaction with some imported function, then I will overwrite the IAT (Import Address Table) entry for that function and replace it with a pointer to something that I control. If I want to do the same with respect to some function that exists within the binary, I will make some sort of detours-style patch at the beginning of the function. I can even do very surgical and targeted hooks at arbitrary locations, akin to old-school byte patching. My DLL does its business within the individual hooks, and then is programmed to redirect control back to the original process.
DLL injection provides a platform for manipulating the execution of a running process. It's very commonly used for logging information while reverse engineering. For example, you can hook the IAT entry for a given imported operating system library function, and then log the function arguments onto disk. This provides you a data source that can assist in rapidly reverse engineering the target.
DLL injection is not limited to logging, though. Given the fact that you have free reign to execute whatever code that you want within the process' address space, you can modify the program in any way that you choose. This technique is frequently used within the game hacking world to code bots.
Anything that you could do with byte patching, you can do with DLL injection. Except DLL injection will probably be easier and faster, because you get to code your patches in C instead of assembly language and do not have to labor over making manual modifications to the binary and its PE structure, finding code caves, etc. DLL injection almost entirely eliminates the need for using assembly language while making modifications to a binary; the only assembly language needed will be small pieces of code nearby the entrance and exit to a particular hook to save and restore the values of registers / the flags. It also makes binary modification fast and simple, and does not alter cryptographic signatures of the executable that you are patching. (The comment about cryptographic signatures applies to the executable on disk, not in memory; of course, altering the contents in memory would affect a cryptographic signature computed on the altered memory contents.)
DLL injection can be employed to solve highly non-trivial reverse engineering problems. The following example is necessarily vague in some respects because of non-disclosure agreements.
I had a recurring interest in a program that was updated very frequently (sometimes multiple times daily). The program had a number of sections in it that were encrypted on disk after compilation time and had to be decrypted at run-time. The software included a kernel module which performed the run-time encryption/decryption. To request encryption or decryption of a given section, the program shipped with a DLL that exported a function which took as arguments the number of the section and a Boolean that indicated whether the section should be encrypted or decrypted. All of the components were digitally signed.
I employed a DLL injection-based solution that worked as follows:
- Create the process suspended.
- Inject the DLL.
- DLL hooks GetProcAddress in the program's IAT.
- GetProcAddress hook waits for a specific string to be supplied and then returns its own hooked version of that function.
- The hooked function inspects the return address on the stack two frames up to figure out the starting address of the function (call it Func) that called it.
- The hooked function then calls Func for each encrypted section, instructing it to decrypt each section. To make this work, the hooked function has to pass on the calls to the proper function in the DLL for these calls.
- After having done so, for every subsequent call to the hooked function, it simply returns 1 as though the call was successful.
- Having decrypted all the sections, the DLL now dumps the process' image onto the disk and reconstructs the import information.
- After that it does a bunch of other stuff neutralizing the other protections.
Initially I was doing all of this by hand for each new build. That was way too tedious. One I coded the DLL injection version, I never had to undertake that substantial and manual work ever again.
DLL injection is not widely known or used within reverse engineering outside of game hacking. This is very unfortunate, because it is an extremely powerful, flexible, and simple technique that should be part of everyone's repertoire. I have used it dozens of times and it seems to find a role in all of my dynamic projects. The moment my task becomes too cumbersome to do with a debugger script, I switch to DLL injection.
In the spectrum of reverse engineering techniques, every capability of DLL injection is offered by dynamic binary instrumentation (DBI) tools as well, and DBI is yet more powerful still. However, DBI is not stealthy and incurs a serious overhead in terms of memory consumption and possibly performance. I always try to use DLL injection before switching to DBI.
DLL Injection works by tricking/forcing the target process into loading a DLL of your choice. After that, the code in that DLL will get executed as part of the target process and will be able to do anything the process itself can. The fun part will be to figure out how to get your code called by the target process.
DLLs can be injected by:
- simply substituting your DLL for one the process typically uses - e.g., if you name your DLL
ddraw.dll, a lot of games will happily load it instead of the real Direct Draw DLL. I've seen this done to force the game to use Direct Draw in software emulation mode only, to accelerate it on specific GPUs.
- tricking the loader into loading a known DLL from a different folder - see The Old New Thing.
- replacing some of the process code with instructions to load your DLL.
- using plenty of other ways.
The next step would be getting your DLL code to actually execute. But if you want to do something meaningful, this will be hard - you need to know what the process does, what data structures it uses, etc., so you'll most likely need to disassemble it.
- You can create a new thread in the target process to invoke a function from your DLL. Suspend the existing threads first to preserve your sanity and avoid funky multithreading bugs.
- If you replaced a known DLL with your own, the process will expect your DLL to respond to specific function calls - you better know what those functions are and provide their replacements in your DLL.
- If you changed the executable to call your DLL in addition to known DLLs, you had to take the executable apart already. Now go find some places of interest, and insert calls to your DLL functions there. See code cave.
I have performed DLL injection by launching the target process as a debuggee process, overwriting some bytes in its startup code with a custom code sequence that calls
LoadLibrary("mydll.dll"); GetProcAddress(myLib, "myFunc");, and rewriting some code in the executable to jump to functions in the DLL instead.
Using this method some friends and I wrote a pretty big unofficial bugfix/enhancement DLL for Command & Conquer: Red Alert 2 - nowadays that DLL is about 15% the size of the original game executable. As a result, later official updates of the game were limited to only things their staff could do without recompiling the binary, which was uncharacteristically nice of EA.