Skip to main content
Evert's website

DLL Injection and Function Hooking

For my Pandora’s Box reverse engineering project, I’m exploring methods of re-implementing the game. An interesting strategy employed by projects like OpenRCT2 (Rollercoaster Tycoon 2), re3 (GTA III, unfortunately taken down by Take-Two), and ZeldaRET (various Zelda games) involves decompiling and gradually replacing the original game’s code. Unfortunately, I couldn’t find any detailed write-ups on what the initial stages of these projects looked like. Last week however I stumbled upon Thyme, a re-implementation of Command & Conquer Generals: Zero Hour. The approach they have taken is quite clear from the initial commit on their GitHub, so I decided to do a writeup of their approach myself to determine if it could be useful for my Pandora’s Box project.

Please note that I’m far from an expert on reverse engineering, C/C++, assembly, the Win32 API, DLLs, PE and so forth, so take this post with a grain of salt, check with the original material and if you find any mistakes, please let me know 😁

Analyzing Thyme’s initial commit #

My analysis began by examining the initial commit of Thyme. Their setup consists of two parts:

The flow of the injector is as follows (information based on the initial commit of Thyme and this post on DLL injection on process start):

Next, let’s take a look at the DLL:

Thymes hooker DLL implements additional (convenient) features, like the ability to call an original function, and a way to access global variables from the game if the address of the variable is known, but these are out of scope for now

Minimal implementation of the injector and DLL #

To better understand the theory above we will implement an injector and DLL for a demo application.

The demo application prints ‘Hello World!’ to the screen, calculates the sum of two integers, and then prints this result as well:

Demo output
Output of the demo application

If you’re following along, do not forget to build the application (and later the injector and DLL) in x86/32-bit mode (as we’re going to patch in a 32-bit relative jump, which might not be enough in a 64-bit address space), with optimizations disabled (to prevent the compiler from inlining our very simple functions) and with ASLR disabled (for obvious reasons).

After building the application we can analyze it in Ghidra to find the address of the entry point. Because our application is built with debugging symbols and without optimizations this is a lot easier than when analyzing ‘real’ software 😊 From the Symbol Tree window we can immediately browse to the main function. In the listing window we can then see that the function starts at address 0x00412510:

Ghidra entry point
Finding the entry point using Ghidra

Next, let’s implement the injector based on our research on Thyme’s initial commit. Again, this is all heavily based on the work of the Thyme developers, so all credit goes to them!

Finally, create an empty DLL project. Before implementing the hooking itself we can build the application, injector and DLL and verify that the application still works as intended after injecting the DLL:

DLL injection (1)
Successful injection (of an nonfunctional DLL)

Now, on to the DLL. We’re going to make the following modifications to the program:

Let’s start by marking the .text segment as writeable so we can set up our hooks. In Ghidra, we can find the start of the .text segment from the Program Trees window:

Ghidra .text segment
Finding the start of the .text segment in Ghidra

We can see the segment starts at 0x00411000. While the .text segment is still selected, scroll all the way down to find the end of the .text segment at 0x004185ff. We can then calculate the size of the segment as 0x004185ff – 0x00411000 = 0x000075ff. We will pass the start address and size of the segment as parameters to VirtualProtectEx when the DLL is loaded (reason DLL_PROCESS_ATTACH when DllMain is called):

.text segment writeable
Marking the .text segment as writeable

Next we will write our replacement Add and PrintHello functions:

.text segment writeable
Re-implemented functions in our DLL

In Ghidra, we will then look up the addresses of the original functions by finding them through the Symbol Tree window. The PrintHello function starts at 0x00412250, and the Add function starts at 0x00412200.

We can now setup our hook by replacing the bytes at the original function start by 0xE9 (the 32-bit relative jump instruction) followed by the relative offset to the re-implemented function (re-implemention function address – original function address – 5 (the size of the jump instruction and the offset itself)):

Finally, build the DLL and run the injector:

Original output
Original program output
Hooked output
Hooked program output

The full source code for the Demo Application, Injector and DLL is available on GitHub here.