Target
Our target in this lesson will be Wesnoth 1.14.9.
Identify
To recruit units in Wesnoth, you right-click on a tile and choose Recruit. In Wesnoth, you can only recruit units on specific tiles. Our goal for this lesson is to change this behavior so that we can recruit units anywhere on the map.
Understand
This hack will require us to modify the game’s code using a debugger. To conduct this hack, we will first need to find the code executed when right-clicking on a tile and choosing an option. The original game code probably looks something like:
switch( option_selected ) {
case "Terrain Description":
show_terrain_description(location);
break;
case "Recruit":
recruit_unit(location);
break;
case ...
}
A switch statement allows you to execute different branches depending on the state of a variable. We want to modify this statement so that clicking on Terrain Description instead calls the code for recruiting a unit.
Bubbling
In the previous lesson, we found the code responsible for subtracting gold when we recruited a unit. We will use this code to bubble up to the right-click menu code location. To illustrate the concept of bubbling up, imagine that the code in Wesnoth for recruiting units looks like:
function handle_context_menu() {
...
case "Recruit":
recruit_unit(location);
break;
...
}
...
function recruit_unit(location) {
...
check_location(location);
find_unit_in_unit_list();
...
}
...
function find_unit_in_unit_list() {
...
get_unit();
get_unit_cost();
subtract_unit_cost();
...
}
...
function subtract_unit_cost() {
...
check_player_gold();
subtract_gold();
...
}
...
function subtract_gold() {
player_money = player_money - cost_of_unit;
}
A good way to visualize the interactions between all these functions is through the use of a function chain. The function chain for this example would look like:
handle_context_menu()
recruit_unit()
find_unit_in_unit_list()
subtract_unit_cost()
subtract_gold()
The code we found in the previous lesson was in the subtract_gold function. By bubbling up from this code, we will eventually locate the handle_context_menu function.
To bubble up in our debugger, we will make use of two features: Execute till return and Step Over. The execute till return feature executes instructions until reaching a return statement. The step over feature executes a line of code. Unlike the Step Into feature, step over does not enter a function if the instruction being executed is a call. We will elaborate on this later, but first we need to cover how functions are translated into assembly.
Calls and Returns
The call instruction is used to invoke a function in assembly. At the end of the called function, the retn (return) instruction is used to go back to the code that called the function. For example, the code below uses a call to increase the register eax by 1:
main:
mov eax, 0
call increase_eax
mov ebx, eax
increase_eax:
add eax, 1
mov ecx, eax
retn
Imagine we set a breakpoint on the add eax, 1 instruction. Once it pops, using the execute till return feature would cause the debugger to continue executing code until the first retn instruction is reached. Once on the retn instruction, the step over feature would then execute the retn instruction and arrive at the mov ebx, eax instruction. This is a good illustration of bubbling up to a higher function.
To understand stepping in versus stepping over, imagine we set a breakpoint on the call increase_eax instruction. Stepping into this instruction would cause our debugger to go to the first line of the function (add eax, 1) and wait there. Stepping over this function would cause our debugger to continue execution until reaching the mov ebx, eax instruction. When dealing with lots of low-level code, it is often convenient to step over functions to not waste time.
Locating the Menu
Unlike variables, code locations within a game will usually not change. Because of this, we can use the same location we found in the previous lesson to begin reversing. After attaching x64dbg to Wesnoth, navigate to the location we found in the last lesson (0x007ccd9e
) and click on the dot to the instruction’s left to set a breakpoint. The address will turn red to indicate that a breakpoint has been set. This breakpoint will pop whenever this instruction is executed.
Next, go back into Wesnoth and recruit a unit. Upon doing so, the debugger will pop at the same location we saw in the last lesson.
Click the Execute till return button once to execute until the first retn instruction.
Once on it, click the Step over button to go to the calling code.
You should be sent to the following location:
The call instruction above the highlighted line is the call we were just inside of. The code we are currently at was responsible for calling this function. We can use this technique to keep bubbling up to the function we care about.
We know that the function for handling the right-click menu will have many branches and calls. We can guess that when translated into assembly, the game’s switch statement will most likely look something like:
call some_address
jmp to_end
call some_address
jmp to_end
call some_address
jmp to_end
There will most likely be other instructions, but this is the format we are looking for. Keep following the cycle of executing until a return statement and then stepping out. After several times, you should land in the following code:
This pattern looks similar to what we were expecting. We can verify that this is the correct code by nop’ing out the call we just stepped out of, like so:
If you go back into the game and try to recruit a unit, nothing will happen. This is good verification that we found the function responsible for handling the right-click menu event of recruiting. Go back into x64dbg and right-click on the code we just changed and choose Restore selection. This will restore the original instruction.
Locating Other Events
Now that we have found the call for the recruit event, we can use its structure to figure out how the other events in the game are called. The call looks like:
call dword ptr ds:[eax+0x54]
This call is not calling a static location. Instead, it is calling the location held in memory at eax+0x54. If we look at the other calls in the function, we see that they all have a similar form, with only the last number changing.
Due to this structure, we have to revise our original code model that had a switch statement. In the screenshot above, we can see that the last number is always a multiple of 4. Therefore, we can assume that these functions are most likely stored in some type of list or array. The original’s game code probably looks something like:
void* context_menu_functions[MAX_FUNCTIONS] = {
terrain_description,
recruit_unit,
...
}
context_menu_functions[option_selected]();
This code stores a pointer to each function in an array. The option_selected variable can then be used to retrieve the correct function from the array and execute it. We will cover pointers in a future lesson. It’s important to note that even though we had the wrong original code in mind, the overall structure of branching will always be obvious in a game’s code.
We know that the offset for recruiting is 0x54
. To determine other offsets, we can change the recruiting call to other values and note the result when we use the Recruit entry on the context menu. Starting at eax, we can try each multiple of 4 and log their result (eax + 4, eax + 8, eax + 0xc, eax + 0x10, and so forth). For example, by changing the value to 0x28
, a terrain description will show up when we try to recruit a unit.
Far more interesting is when we change the value to 0x68
. In this case, a Debug menu to spawn units will appear.
Change
We can use the two values we found above to create our hack. First, we will locate the menu item code responsible for showing the terrain description. Then we will change this value to call the debug menu instead.
We know that the value for the terrain description is 0x28
. By observing the area around the recruit call, we will eventually find the code responsible for the terrain description event.
Next, we will change this value to 0x68
. This will invoke the debug menu anytime we select Terrain Description. Since the terrain description is available on any tile, this will allow us to recruit units anywhere.
Once this change is made, go back into Wesnoth, select a random tile, and choose Terrain Description. Select a unit from the debug menu and verify that the hack works.