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.

Wesnoth's Context Menus When Selecting a Tile

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.

Setting a Breakpoint in x64dbg on the Player Gold Subtraction Code

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.

x64dbg Displaying the Code Called When Recruiting a Unit

Click the Execute till return button once to execute until the first retn instruction.

x64dbg's Execute till return Feature

Once on it, click the Step over button to go to the calling code.

x64dbg's Step Over Feature

You should be sent to the following location:

x64dbg Displaying Wesnoth Code That Called the Recruiting Code

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:

x64dbg Displaying Wesnoth Code Responsible for Branching Operations

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:

Code After NOPing out the Recruiting Operation

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.

x64dbg's Restore Selection Feature

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.

x64dbg Displaying More of Wesnoth Conditional Operations

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.

Changing the Recruiting Conditional to the Terrain Description Conditional

Far more interesting is when we change the value to 0x68. In this case, a Debug menu to spawn units will appear.

Wesnoth's Debug Menu That Allows Players to Spawn Units

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.

The Terrain Description Conditional

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.

Changing the Terrain Description Conditional to the Debug Menu Conditional

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.

Wesnoth Now Allows Players To Spawn Units on Any Tile

 

results matching ""

    No results matching ""