Target
Our target for this lesson will be Assault Cube 1.2.0.2.
Identify
Our goal in this lesson is to create a no recoil hack, a type of hack that eliminates recoil when firing. Recoil is defined as the automatic upward motion of your player’s view when firing a weapon.
Understand
When firing a weapon in an FPS, different effects are applied by most games:
- Recoil (up and down movement)
- Spread (crosshair widening, random distribution of shots)
- Pushback (player pushed in the opposite direction they are firing)
Our focus in this lesson is to remove recoil only.
In most games, these effects are applied consecutively after each shot is fired. Recoil generally works by increasing the player’s up and down view angle by adding a certain value to it. Because view angles are usually floating point numbers, this operation will typically take the following assembled form:
fld recoil_amount ; load recoil amount into st(0)
fadd st(0), players_y_view_angle ; add recoil_amount to view angle
fstp players_y_view_angle, st(0) ; store result in view angle
Unlike integers, float values must be pushed on a special register stack to be operated on known as the FPU stack. However, like normal instructions, if this code is nop’d out, recoil will not be applied to the player.
When firing a weapon, games execute several functions, including playing a sound, displaying a firing animation, and decreasing the player’s ammo. These functions are often located near the function that applies recoil to the player’s view. We can use these functions to help locate the recoil code. We have multiple approaches that we can use to locate this code. In this lesson, we will use the code responsible for decreasing the player’s ammo, as this value is easy to search for.
Locating Firing Function
Start a game of Assault Cube and use Cheat Engine to locate your current ammo count, using the same approach discussed in the Memory Hack lesson. Once that is identified, attach x64dbg to Assault Cube and set a hardware breakpoint on write on the identified address. When you go back to Assault Cube and fire, the breakpoint should pop at the following location:
We can see that this code is responsible for decreasing the ammo count. If we step out of this code using execute until return/step, we see that the calling location is here:
Next, let’s determine our context in the code. We want to determine if we are in the code responsible only for setting the ammo count or if we are in the general firing code. We can do this by setting a breakpoint on the call edx instruction. After that, we can see that this code is called constantly, whether we are firing or not. This means that we are too high-level and we will need to dig into this function.
If we step into the call after the breakpoint is triggered, we can see that the function has several branches:
If we step through the code, we can see that the jmp at
0x46363A
is not taken if we are not firing:
If we change this to a jmp and go back in the game, we will notice that our player now fires constantly, even if we are holding down the mouse button. This jmp appears to be responsible for checking if the player is firing. If we follow this jmp, we can see that it jumps past a return statement and to the following instruction:
When we set a breakpoint on this instruction, we see that this code is only called when we are actively firing.
Locating Recoil
We have now found the beginning of the weapon firing code. We could also see that one of the final instructions in the weapon firing code is responsible for decreasing the ammo. Therefore, somewhere between these two instructions is the code responsible for adding recoil.
While we could step through this code to identify the instruction, we can use a quicker approach. We know that the recoil instruction must modify the player’s yaw value. After we hit our breakpoint on our weapon firing, if we then set a breakpoint on the yaw value, we can continue execution and wait for the breakpoint to pop. This prevents us from stepping through a large amount of code.
It’s important that we only set the breakpoint on the yaw value after the firing code is started. Assault Cube, like many other games, constantly writes to the yaw value. If we just set a breakpoint on it without being in the firing code, we will end up in another section of code.
We can locate the address of the yaw value using the same approach
discussed in the previous lesson or by searching for it in Cheat Engine.
After that, set a breakpoint on the start of the firing code at
0x46366C
. Then, fire a single shot so the breakpoint pops.
When it does, set a breakpoint on write on the address of the yaw value.
Continue execution and the write breakpoint should pop at the following
code:
We can see that this code matches the pattern we expected. In this particular code, dword ptr ds:[ebx+0x44] is responsible for holding the player’s yaw value. The recoil value is held on the top of the FPU stack, which is pointed to by st0.
The operation to calculate recoil appears to be composed of several instructions. While we could investigate the exact way in which the recoil is set, we can skip that process to make a no recoil hack and simply prevent the recoil value from being placed in the player’s yaw value.
The fstp instruction is responsible for popping the top value off the FPU stack into the provided address. Since we do not want to corrupt the stack, we do not want to nop this instruction, as the stack would then have an extra value on it. Instead, we will just pop the value off the top of the stack into st0. Since st0 is then set in the next instruction, this will result in the value effectively disappearing:
With this change made, you will notice that you no longer have any recoil in the game.
Changing Recoil
Finally, we can write a DLL to make this change automatically. Since this
hack only requires us to change bytes at an instruction, we can use the
same template we covered in the Map Hack lesson.
For this hack, we will change the code for editing the bytes at
0x45BAAD
to the identical values we observed in x64dbg:
#include <Windows.h>
unsigned char new_bytes[3] = { 0xDD, 0xD8, 0x90 };
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
DWORD old_protect;
unsigned char* hook_location = (unsigned char*)0x45BAAD;
if (fdwReason == DLL_PROCESS_ATTACH) {
VirtualProtect((void*)hook_location, 3, PAGE_EXECUTE_READWRITE, &old_protect);
for (int i = 0; i < sizeof(new_bytes); i++) {
*(hook_location + i) = new_bytes[i];
}
}
return true;
}
This code for this hack is also available on github.