This blog post will demonstrate how to go about reverse engineering structures and functions of interest in a binary. Once this has been accomplished, I will show how to modify the program at runtime to alter the program flow in our favor.
For our example, we will be using Microsoft's Halo for the PC, using build number 01.00.00.0564. Because this is a first person shooter, classic examples of functions of interest would be the ammo counter, health counter, etc. If we are able to modify those functions' functionality, we would be able to gain unlimited ammo, health, and more.
finding the target instruction
Using a program like Cheat Engine, we can find the functions of interest. The basic process involves searching the program's memory for a value, such as the current ammunition count, then changing the target value (in our case by using up some ammo), searching for the new value out of the current set of previous matching addresses, and so on. Eventually, this process can be used to narrow down where the value is currently stored.
Once we have the target value's address, we can modify it for the current session all we want it, but it will possibly change the next time the program is run. So what we need to do is find out what value writes to the target value's address. We would attach a debugger and set a break on write breakpoint. Cheat engine provides this functionality, as well as WinDbg.
Using Cheat Engine, I have found that for this instantiation, the amount of ammo is stored at address
0x401001F4. Using WinDbg, we can verify this:
I currently have 58 in my ammo counter and according we can see the same value in WinDbg (
0x3a in hex =
58 in decimal). To see what instruction writes to this address, I'll set a breakpoint on
0x401001f4 and fire the weapon to decrease the ammo count:
The "r;g" tells the debugger to print out the registers and then go (continue) each time the breakpoint is hit. When the breakpoint is hit, WinDbg prints out the following line:
This is the line that follows the instruction that wrote to our target value. To provide a little context, we look a few bytes back and see the instruction that writes to our target address:
If we set a breakpoint on that address so it will stop before it is hit, then we can examine what
esi's value is. We expect
esi to be
8 (0x401001ec), and we expect
ax to be the current ammo count.
esi's value is
0x401001ec as expected and the lower byte of
0x37, which is our current ammo count. At this point, we have successfully found the instruction that modifies the ammo count of the player.
modifying the target process
We can use a memory analysis framework like nouzuru to patch the program at runtime:
For values like ammo, grenades, and flashlight power, we can stop decrementing their value by simply overriding the modifying instruction with
nop instructions (the "no operation" instruction). However, for values such as invisibility or shields, simply
noping out instructions won't suffice.
In the case of invisibility, we want to be able to enable invisibility at all times, so simply nullifying the invisibility decrementer won't do the trick. In the case of shields, if we nullify the shield decrementer, then other characters like Elites, who also have shields, will never have their shields decremented. That would end up in an endless firefight, because we have enabled unlimited ammo.
Therefore, I reverse engineered the Master Chief structure so that these values could be set at specific values. To keep the values at a certain setting, I used a value freezing routine:
This routine gets the player object's current base address, calculates the invisibility and shield offsets, and sets them to the desired values. Then, it sleeps for a pre-determined amount of time and repeats. This allows the player's shields to be set high enough for no damage to be done.
master chief structure
For reference, the results of my reverse engineering efforts revealed the following important members of the Master Chief data structure:
To find the base address of the Master Chief, we need to find the base address of the current level. To find the base level, look at the two bytes at the static address
0x4000000b. These can be used to identify which level the player is on. With this information, we can calculate the base address of the player using the following level offset dictionary:
Using these offsets, we simply read them depending on which level the player is on and add
0x5C to get the Master Chief's base structure address:
try it yourself
If you would like to try out my Halo trainer, feel free to clone it from github at https://github.com/carterjones/halo-trainer.
If you would just like the precompiled binaries, you can download them from here.
If you would like to mess around with nouzuru, the platform I wrote for making memory analysis/modification/debugging software, feel free to check it out at https://github.com/carterjones/nouzuru. Or to get started right away, just issue this command: