Hi, it’s about time to drop some info on this blog outside of my excellent cooking guide. I’ll apologize beforehand that I’m not the best at writing long posts neither am I the best at English as it isn’t my first language. At first it was going to be a blog post on reversing methodology when approaching a new game there isn’t much documentation for, but it turned out the developers made things easier than they should be or usually are, so this turned out to be a bit more of an info dump post rather than going deep in to how one should approach hacking a game.

The following tools were used to gather information in my efforts to hack this game:

I will not be covering things like finding Directx device pointers here, the methods for that are so well documented at this point I feel like it would be a waste of lines.

At the bottom there are some partial game structures with everything necessary for what one could consider basic cheat functionality. I’ll leave finding most of the game function locations as an exercise for the reader, but that won’t be too difficult due to the things explained coming up.

How did they end up making things easy?

They did the old mistake companies such as Valve have done in the past, the Linux version was shipped without stripped symbols. That means I could load up the Linux binaries in Ghidra and see most of the interesting function names.

Even though I wasn’t hacking the game on Linux, the symbols are an extremely valuable reference for finding functions on the Windows build. Two of the major differences you will see between the Linux and Windows binaries is the fact that the Linux build is an x64 version of the game while the Windows build is x86 and differences in what the compiler decides to inline.

The difference between the x86 and x64 builds is pretty much only seen in variable and structure sizes as one would imagine, this means we most likely won’t make it entirely with just static analysis.

The inlining differences do make things a bit more annoying at times, as it can make it so if you try to find references to a function like get_weapon_item on Windows you likely wont get the same results as on Linux, due to the function being inlined sometimes.

In the next few sections I’ll explain the principle of how you can easily use these symbols to your advantage.

The explanation will not be very technical, so you can likely follow along easily with little to no reverse engineering experience, though some familiarity with Ghidra’s UI may be of use.

We’ve established that it’s going to be easy, where to start?

When first getting started on a game, one of the first things you’ll want to find is either the local player (you) or a list of players.

As we already established the symbols are present in the binary, let’s try and figure out what class would be used for characters in the game. Let’s open the mb_warband_linux binary in Ghidra and analyze it, after the analysis is done let’s have a try at searching the symbol list for some function names that could be expected for a game character. Good bets to go for usually are things like health, position etc. similar to if you were trying to go through the strings to find things related to characters.

In this case the filter “position” gave us a few interesting looking options.

Let’s remove the filter and see the other methods in those classes. It should be painfully obvious once you see the method names under Agent that it’s likely going to be the character class. The next step is to try and find out where the Agents are stored. I had a look at the method get_number_of_enemies_following as it sounded like it would be iterating other entities in the function (it is), but other valid ways to find how the entities are stored would be looking at references to any methods in the class and seeing how the code comes up with the first parameter of the method, which is the pointer to the object.

The method is not long, but here’s a small snippet

uVar8 = 0 ; uVar1 = * ( uint * )( cur_mission + 8 ); if ( 0 < ( int ) uVar1 ) { plVar3 = * ( long ** )( cur_mission + 0x28 ); uVar6 = 0 ; lVar5 = * plVar3 ; iVar2 = * ( int * )( lVar5 + 8 ); while ( iVar2 == 0 ) { uVar6 = uVar6 + 1 ; if ( uVar6 == uVar1 ) { uVar8 = 0 ; goto LAB_0107f744 ; } lVar5 = plVar3 [ uVar6 >> 4 ]; iVar2 = * ( int * )( lVar5 + 8 + ( ulong )( uVar6 & 0xf ) * 0x94c0 ); } uVar8 = 0 ; while ( true ) { pAVar7 = ( Agent * )(( ulong )( uVar6 & 0xf ) * 0x94c0 + lVar5 ); if ((( * ( int * )( pAVar7 + 0x2c ) != 1 ) && ( cVar4 = is_enemy ( this , pAVar7 ), cVar4 != '\0' )) && ( * ( int * )( pAVar7 + 0xb48 ) == * ( int * )( this + 0xc ))) { uVar8 = uVar8 + 2 + ( * ( int * )( pAVar7 + 0x120 ) >> 0x1f ); } uVar6 = uVar6 + 1 ; if (( int ) uVar1 <= ( int ) uVar6 ) break ; // removed below this

What we can determine from that, is that the entity count seems to be at cur_mission + 8 (uVar1) and there’s an array at cur_mission + 0x28. (plVar3)

In the loop the Agent pointer is at the address

( uVar6 & 0xf ) * 0x94c0 + lVar5

so it would be fair to assume 0x94c0 is the size of an Agent object, uVar6 is the index. Now let’s look closer at lVar5, it seems like the value of lVar5 is the value held at plVar3[uVar6 » 4].

It seems that the uVar6 variable is used as an index in two separate places. Once shifted 4 bits to the right, and once using only the 4 least significant bits. From playing the game we obviously know that there can be much more than 16 Agents in a match. Meaning the way the Agent list works in Warbands is that at cur_mission + 0x28 we have an array of pointers that point to arrays of 16 Agents.

It also seems that (Agent + 0x8) is 0 if the Agent is not valid as the arrays of 16 are not always filled up.

So something like this could be our function for getting an agent for an id.

Agent * get_agent ( int id ) { size_t * agentlist_array = * ( size_t ** )( cur_mission + 0x28 ); size_t agentlist_size = * ( size_t * )( cur_mission + 0x8 ); if ( id > agentlist_size ) return nullptr ; if ( ! agentlist_array ) return nullptr ; size_t agentlist = agentlist_array [ id >> 4 ]; Agent * agent = ( Agent * )(( size_t )( id & 0xf ) * 0x94c0 + agentlist ); if ( agent && agent -> is_valid != 0 ) return agent ; else return nullptr ; }

But hey! You said that structure sizes are different on Windows!

Now that we’ve figured out how and where the Agents are stored, it’s time to figure out the same on the Windows version of the binary.

The method we’re going for in this case is easy.

Find a string in a method of the Agent class. Check the functions calling that method for code similar to what we went through above. Use the string to find the same function in the windows binary. See if you find what you want.

In my case I found the string

Warning: Invalid ragdoll status!

in update_entity_parameters, then I picked a function called set_player_position_client from the Function Call Trees Incoming references list.

At the top we see some similar code so we better check this one out on the Windows side.

if (( - 1 < param_1 ) && ( param_1 < * ( int * )( cur_mission + 0xc ))) { lVar20 = ( ulong )( param_1 & 0xf ) * 0x94c0 ; if ( * ( int * )( * ( long * )( * ( long * )( cur_mission + 0x28 ) + ( ulong )(( uint ) param_1 >> 4 ) * 8 ) + 8 + lVar20 ) != 0 ) { this_00 = ( float * )( lVar20 + * ( long * )( * ( long * )( this + 0x28 ) + ( ulong )(( uint ) param_1 >> 4 ) * 8 ));

Let’s search for the string above, go to the function, rename in this case FUN_004ba650 to update_entity_parameters and let’s start going through the entries in the Incoming references list of the call tree. Compare the code around the function call to make sure you’ve found set_player_position_client and rename the function you found to it.

Assuming you found the right function. At the top there should be something like this:

if ((-1 < (int)param_1_00) && ((int)param_1_00 < *(int *)(CUR_MISSION + 8))) { //CUR_MISSION = DAT_008b829c iVar5 = (param_1_00 & 0xf) * 0x6200; iVar7 = (param_1_00 >> 4) * 4; if (*(int *)(*(int *)(*(int *)(CUR_MISSION + 0x20) + iVar7) + 4 + iVar5) != 0) { iVar5 = *(int *)(*(int *)(param_1 + 0x20) + iVar7) + iVar5;

Which should seem similar with the bit shifting an index to the right and getting the 4 least significant bits of the index.

It looks like this code is checking if the Agent index (param_1_00) is between 0 and the amount of Agents (Which is still cur_mission + 8). After this it gets the offset from the beginning of the Agent array (index * 0x6200) which means that the size of the Agent object is 0x6200 on Windows, and at last it adds everything together with the array of Agent arrays being (cur_mission + 0x20).

Meaning our earlier code could be modified to this

Agent * get_agent ( int id ) { size_t * agentlist_array = * ( size_t ** )(( uintptr_t ) cur_mission + 0x20 ); // Array of pointers is now at 0x20 size_t agentlist_size = * ( size_t * )(( uintptr_t ) cur_mission + 0x8 ); if ( id > agentlist_size ) // some safety check, arguably useless assuming you are looping from 0 -> agentlist_size anyway, but if you want to use outside a loop. return nullptr ; if ( ! agentlist_array ) return nullptr ; size_t agentlist = agentlist_array [ id >> 4 ]; Agent * agent = ( Agent * )(( size_t )( id & 0xf ) * 0x6200 + agentlist ); // Agent size is now 0x6200 if ( agent && agent -> is_valid != 0 ) // Valid is now agent + 0x4 return agent ; else return nullptr ; }

We now know how to iterate entities by looping from 0 -> agentlist_size using this function. You can use this same method to find pretty much anything. I’ll leave it up as a task for the reader to find out how to get the index of the local agent (you).

Not everything makes sense to be done with static analysis

Even if static reversing has been made super easy for us, it’s not the only tool we have. Now that you can get Agents easily, you can print out the pointers for valid agents when iterating them, meaning you can just use one of the addresses in ReClass to find some variables of the object to use for actual cheating. You can identify pointers to other objects very easily due to the RTTI names being visible.

I’ll leave this as a task to the reader too, if you want more than what the structures below contain.

Info dumping

Now that we’re done with the methodology of using linux symbols to find anything on the Windows binaries easily.

points of interest for the reader to find: Scene_widget classes function get_screen_pos_from_world_pos for world to screen.

//Everything created with ReClass.NET by KN4CK3R //Some of the position comments might be off due to editing outside of reclass tactical_window: 0xdd9b18 cur_mission: 0x8b829c class rglBone { public: char pad_0000[0xB4]; //0x0000 D3DXVECTOR3 bone_pos; //0x00B4 Translate with rglStrategic_Entity->transform_matrix char pad_00C0[344]; //0x00C0 int N00002134; //0x0218 }; //Size: 0x021C class Bones { public: int bone_count; //0x0000 DWORD bone_list; //0x0004 // Array of 0x21c sized bones void* skeleton_model; //0x0008 char pad_000C[52]; //0x000C }; //Size: 0x0040 class rglStrategic_Entity { public: char pad_0000[24]; //0x0000 D3DXMATRIX transform_matrix; //0x0018 char pad_0058[48]; //0x0058 D3DXVECTOR3 ent_origin; //0x0088 char pad_0094[4]; //0x0094 void* ptr_rglCapsule; //0x0098 D3DXVECTOR3 ent_min; //0x009C char pad_00A8[4]; //0x00A8 D3DXVECTOR3 ent_max; //0x00AC char pad_00B8[540]; //0x00B8 Bones* ptr_Bones; //0x02D4 }; //Size: 0x02D8 class Tactical_Window { public: char pad_0000[336]; //0x0000 void* pScene_widget; //0x0150 char pad_0154[584]; //0x0154 D3DXVECTOR3 camera_pos; //0x039C char pad_03A8[4]; //0x03A8 D3DXVECTOR2 camera_angle; //0x03AC }; //Size: 0x03B8 class Agent { public: char pad_0000[4]; //0x0000 int is_valid; //0x0004 char pad_0008[64]; //0x008 D3DXVECTOR3 position; //0x0040 char pad_004C[604]; //0x004C int unk_specifics; //0x02A8 // Horses are always 0xFFFFFFFF, using for aimbot checks char pad_02AC[136]; //0x02AC void* ptr_unknown; //0x0334 char pad_0338[1328]; //0x0338 rglStrategic_Entity* prgl_strat_ent; //0x0868 char pad_086C[22416]; //0x086C float max_fhealth; //0x5FFC float fhealth; //0x6000 int ihealth; //0x6004 char pad_6008[504]; //0x6008 }; //Size: 0x6200

Worried about your favorite game Mount & Blade: Warband being overrun by cheaters? Don’t worry. Sadly a plain old “dumb” aimbot or a wallhack won’t do much in the chaos that is Mount & Blade, considering shields are OP.

Feel free to send any concerns to atte@reversing.games

I might make a new post with new findings if I find anything fun/gamebreaking.