Context
The process of reversing a game can seem overwhelming the first time you attach a debugger to a game. The best way to start reversing a game is to figure out what you want to look at and then find where it is. Once you establish this context, you can step through only those instructions that actually matter to you.
There are many ways to establish a context. In some cases, you may want to search for text that is displayed when the game does a certain action. Any locations that load this text must eventually be related to the action that you are interested in. In other cases, you can use memory addresses found in the memory editor to find the code that you are interested in. Regardless of which approach you take, you will use a breakpoint.
Breakpoints
Breakpoints allow the debugger to pause execution of the game at a specific instruction. With the game paused, you can then step through individual instructions and view the game’s memory. You can set breakpoints on any type of memory. This includes memory found using a memory scanner.
Breakpoints can be set to trigger both non-conditionally and conditionally. Conditional breakpoints will only trigger if their conditions are met. These conditions can be things like registers having a certain value or the memory (that the breakpoint is set on) changing. When a breakpoint is triggered, it’s also known as popping.
Memory Breakpoints
The best way to illustrate the use of breakpoints is through an example. In this section, we will examine how a memory breakpoint can be used to establish a context.
Back in the Memory Hack lesson, we found the memory location of our gold. We can use this memory location to find the game logic responsible for lowering our gold. We do this by setting a conditional breakpoint on the memory location of our gold and then going into the game to recruit a unit. When we recruit the unit, the breakpoint will pop and execution will be paused at the function responsible for lowering the player’s gold. This function may look something like below, with the highlighted line (->) representing our paused location.
mov eax, dword ptr ds:[0x05500ABC]
mov ebx, dword ptr ds:[0x12345678]
sub eax, ebx
-> mov dword ptr ds:[0x05500ABC], eax
mov esi, ebx
The first instruction moves the value stored at 0x05500ABC
into the register eax. This value was the location we found in our previous lesson for gold. The next instruction moves a hypothetical value for the unit’s cost into the register ebx. The game then subtracts the unit’s cost from our gold value. Our paused location is responsible for moving the new value of gold back into the memory location that stores our gold value.
You may notice that the game did not pause on the subtraction operation. This is because this operation only modifies the value in the register and not the actual value of the memory we set the breakpoint on. Breakpoints will always pause on the instruction immediately after the affected memory.
Code Breakpoints
Sometimes it may be difficult or impossible to find a memory value to set a breakpoint on. In these cases, you can set a breakpoint on a section of code. A common example of this is setting a breakpoint on a text reference and then using that to find the top-level function we are interested in.
Consider a game for which we want to write a wallhack. The game’s main loop may look something like:
void main_loop(){
draw_players();
draw_walls();
...
}
And the game’s draw_wall function may look something like:
void draw_walls(){
bool succeeded = load_texture("wall_texture");
if(succeeded == false){
print_error();
}
}
Finally, the print_error function may look something like:
void print_error(){
print_to_log("Couldn't find wall texture");
}
One method of writing a wallhack is to remove this game’s draw_wall function. Since there is no variable to use as a memory breakpoint, we will instead use a code breakpoint.
Debuggers allow you to view all the text in a game and all the locations that use that text. For example, with a debugger, we could find the Couldn’t find wall texture text and where it is referenced. It may look something like:
mov eax, dword ptr ds:[0x23456789]
push eax
call print_to_log
...
This section of code is responsible for loading the string into a register and then calling the print_to_log function. By setting a breakpoint on this code and then finding a missing texture in the game, our breakpoint would pop. We could then continue to step through the code until it returned us to the function that called this code. This is known as stepping out of a function. After we have stepped out, we would be in the draw_wall function and could then remove the function.
The nop Instruction
A nop (opcode 0x90
) stands for no operation. When encountering this instruction, a CPU will do nothing and continue on to the next instruction. This behavior can be used to modify game logic.
For example, in Memory Breakpoints section above, we found the portion of code responsible for subtracting our gold. The code looked like:
mov eax, dword ptr ds:[0x05500ABC]
mov ebx, dword ptr ds:[0x12345678]
sub eax, ebx
mov dword ptr ds:[0x05500ABC], eax
By replacing the sub operation with a nop, the game will no longer subtract our gold.