✅Prep: Speed Hack

Motivation

We really need speed hack to save time, this should the very first step in our journey. If there is no anti-cheat system on the server-side, we should be able to change character's speed by patching game client binary. For our case the game logic is implemented in GameLogic.dll.

GameLogic.dll

In game client's folder, we can find two interesting files GameLogic.dll and GameLogic.pdb at C:\PwnAdventure3\PwnAdventure3\Binaries\Win32.

Dropping GameLogic.dll into IDA, we figure out that this file is indeed what it is called: all the C++ game logic implementations are stored here. What is GameLogic.pdb?

Program database (PDB) is a file format (developed by Microsoft) for storing debugging information about a program (or, commonly, program modules such as a DLL or EXE). PDB files commonly have a .pdb extension. A PDB file is typically created from source files during compilation. It stores a list of all symbols in a module with their addresses and possibly the name of the file and the line on which the symbol was declared. This symbol information is not stored in the module itself, because it takes up a lot of space.

Loading GameLogic.pdb in IDA could give us the actual function names, which helps a lot in reverse engineering. If DLL file and PDB file are in the same folder, IDA is going to detect PDB file automatically:

Speed Hack

"Speed" should be a property of the main character object. Usually the main character is called something like "pawn", "player" or "actor". Here I used a trick: searching for Player:: in the symbols. It returns some interesting properties. Among them we find a Player::GetSprintMultiplier function:

Double click on it to view its content:

Press F5 to decompile it into pseudocode:

Currently the multiplier is 3.0. If we modify it to 30.0, our speed should be 10 times faster. Back to the flow chart and double click on __real@40400000:

Why 0x40400000 stands for float 3.0 in C++? Here is a nice video explaining how floating point numbers work:

Here is an online converter:

I am tempted to change this multiplier to max float. Write a simple C++ program to print out max float:

#include <float.h>
#include <math.h>
#include <stdio.h>

int main()
{
    printf("FLT_MAX = %e\n", FLT_MAX);
    return 0;
}

Use an online compiler such as https://www.onlinegdb.com/online_c++_compiler to run it. The output is:

FLT_MAX = 3.402823e+38

3.402823e+38 converts to 0x7f7ffffd in hex, so we want to patch the binary and change the following bytes:

00 00 40 40 -> FD FF 7F 7F

To change bytes in IDA, select .rdata:10078B34 with mouse and go to "Edit -> Patch code -> Change Byte":

After patching it, we have to "apply" the patch so that it overwrites the old binary. Go to "Edit -> Patch code -> Apply patches to input file":

Don't forget to select the "Create backup" option. Click "OK" and log into the game. Enjoy the speed of light 😄

Appendix: Ghidra edition

In 2025 I revisited this hack and chose to use Ghidra this time. Go to Symbol Tree -> Player:

Scroll down and you will see GetSprintMultiplier:

A few things to discuss here.

Cross References (XREF)

?GetSprintMultiplier@Player@@UAEMXZ  
?GetSpreadAngle@Pistol@@UAEMXZ  
?GetCooldownTime@RubicksCube@@UAEMXZ  

Ghidra lists several cross references to this function:

  • XREF[3]:

    • ?GetSprintMultiplier@Player@@UAEMXZ

    • ?GetSpreadAngle@Pistol@@UAEMXZ

    • ?GetCooldownTime@RubicksCube@@UAEMXZ

    These references indicate that other functions (or methods) in the project call this function, providing insight into how it is used in the larger codebase.

Identical implementation

Pistol::GetSpreadAngle  
Player::GetSprintMultiplier  
RubicksCube::GetCooldownTime

The functions Player::GetSprintMultiplier, Pistol::GetSpreadAngle, and RubicksCube::GetCooldownTime all return the same constant value (3.0 in this case). Because they are defined (probably inline) to do nothing more than return a constant, their compiled machine code is identical.

Floating point number

FLD     dword ptr [__real@40400000]

FLD instruction stands for "floating-point load". The __real stands for real number (I think).

IEEE-754 Representation:

  • IEEE-754 is the standard for representing floating-point numbers. In single precision (32 bits), a floating-point number is divided into:

    • 1 bit for the sign

    • 8 bits for the exponent (with a bias of 127)

    • 23 bits for the fraction (mantissa)

  • The hexadecimal number 40400000h (01000000010000000000000000000000 in binary format) represents the value 3.0 in this format. Here's how:

    • Sign Bit (1 bit): 0 (indicating a positive number)

    • Exponent (8 bits): 10000000 (or 128 in decimal). After subtracting the bias (127), the actual exponent is 1.

    • Fraction (23 bits): The bits represent 0.5 (because the fraction starts with 1 in the binary significand for normalized numbers, making it 1 + 0.5 = 1.5).

  • Thus, the number is calculated as: value=1.5×21=3.0value=1.5×2^1=3.0

The formula can be found here: https://en.wikipedia.org/wiki/Single-precision_floating-point_format. An example:

Patch the binary

Double click on __real@40400000:

Location 10078b34 is what we are looking for. Here you just need to find a suitable multiplier, turn it into floating number in hex and patch the binary. Go to Window -> Bytes: GameLogic.dll and look for location 10078b34:

Click the pencil icon on the top right to enter edit mode. Here I change the multiplier to 100 (0x42c80000):

Press ctrl+s to save. Finally, export the patched binary in File -> Export Program. The "format" should be "Original File":

Before overwriting GameLogic.dll, don't forget to save a copy of the original file as backup. Speed hack done.

Last updated