Coding A CS:GO Hack

June 19, 2019
reverse-engineering c++ binary

This post covers creating a multihack for the game Counter Strike: Global Offensive. The created hack works with the Linux version of the game - coding a windows-based hack can however be done with the same methodology and tools. Below you can find a demonstration of the finished cheat:

Bananabot Demo Bananabot Demo Bananabot Demo Bananabot Demo

The features therefore include:

Tooling & Setup

Linux lacks of good tools to perform the kind of analysis tasks required to code a cheat like this. There are pince and scanmem available but they only provide a limited set of the required features. In the Windows world, there’s handy tool called Cheat Engine that is capable of all the required tasks, like:

Fortunately, Cheat Engine is split into two parts: ceserver and the GUI. It’s therefore possible to launch the Linux version of ceserver natively and launch the GUI using wine. The GUI can then be connected to the server using a local socket. Of course, ceserver has to be run as root in order to read and write memory of arbitrary target processes. This setup is semi-stable but still the best for Linux at the moment.

Basics

Once Cheat Engine is attached to the CS:GO process, it can access the whole memory region of that process. This also includes the shared objects that are present in the target memory. The memory layout can be inspected with cat /proc/<PID>/maps.

Once a game client is connected to a network game, it doesn’t only know the local player’s health and location but also the respective values of all team mates and enemies. The game wouldn’t show it of course, but the information is present in the process memory. Objects and structures in this memory are structured according to the game engine’s source code. The developers of CS:GO however applied some changes in regard to the released SDK, so some things may still be different in the actual game memory. For example, the class CBaseEntity is, among other things, responsible to manage the data of player objects. All values present in that memory structure can be found in the SDK source code.

A general therefore is to find the exact location of these structures in the game memory and analyze it accordingly. For example, enemy locations can be read and other values can be written in order to achieve a certain goal. Please note that it’s not as simple as finding a memory location and using the address of the memory structure in the hack because this value changes upon restarting the game. To get around this, a static pointer to the address or a static pointer to a pointer to the address are required. The game executable and the loaded libraries may contain static pointers that lead to the start of a structure in game memory at runtime.

For example, the library that manages the players in the game is called client_panorama_client.so. This library, at a specific offset, contains a static pointer to a structure in game memory that in turn contains the CBaseEntity object of the current player at a specific offset:

Using Static Pointers

Using this so called multi level pointer, all required memory structures can be found by using a single static pointer and by following it to the desired location.

The pointer scan feature of Cheat Engine helps in this analysis step by searching for all pointers that point to a given address, like the beginning of the CBaseEntity. Upon restarting the game, some of these addresses should still point to valid data and can therefore be used in the cheat.

After the game is being updated, some offsets and addresses may be changed. Because of this, dumpers such as tuxdump may be used. These tools are able to read the offsets to various in-game structures using pattern matching approches.

Bunnyhop Bot

With the CBaseEntity, or sometimes called local player, being found it’s possible to detect whether the player is on the ground or not. When analyzing the memory region of the local player structure when playing, one specific bit can be determined that holds the information to the current players status in regard to touching the floor. This can be checked in the hack - the only thing required for bunnyhopping is invoking jumps. Of course, sending a key event may be a working solution. However, the cheat can’t be sure which key the player actually uses for jumping. Because of this, an other approach has been implemented.

The game uses certain memory addresses as interrupts. As soon as a special value is in this address, the game invokes a jump all by itself. The jump interrupt address can be found with the following approach:

By reverse engineering the game, it can be found that writing the value 0x06 to the correct interrupt address will cause the player to perform a jump. The remaining addresses can be filtered by writing this value to them. If no jump has been invoked, it’s the wrong address.

After finding a static pointer to this interrupt address, bunny hopping can be implemented easily.

No Flash

It turns out that the game keeps an internal counter that’s being set to a high value once the player is flashed. Each frame this counter is being decremented and the flash effect will keep being present as long as this counter isn’t zero. To stop the flash effect it’s therefore required to zero this specific address.

The following animation illustrates this:

NoFlash Demo

Cheat engine once again helps in finding the correct address and static pointer to this value.

Aimbot

There are quite some steps required in order to get this work. The local player’s location, as in X, Y and Z coordinates can be found in the CBaseEntity structure that has been found before. Every additional required step is covered in its own section.

Getting Enemy Locations

The enemy locations can be found via various methods. In the cheat the GlowObjectManager has been used. This is a structure in memory that, for whatever reason, holds structures that include pointers to all entities on the map (CBaseEntity structures). Each sub-structure is 0x40 bytes large and starts with the entity pointer element. The manager structure also contains a counter value that can be used in order to loop through all available entities in a for loop.

After filtering out dead enemies and team mates from all available entities by making use of other member values of CBaseEntity, their location can be read directly from memory. Determining the offset of the entity locations can be done by pushing players around and watching the memory change:

Getting Enemy Entity Location

Of course, the determined memory regions have to be converted to floats before processing this data any further.

Getting the Nearest Enemy

With the local player’s and all enemy coordinates ready, it’s quite easy to determine the nearest enemy using mad math skills:

return std::sqrt(std::pow(entity_x - own_x, 2) + std::pow(entity_y - own_y, 2) + std::pow(entity_z - own_z, 2));

Setting the Camera Angle

Using the in-game console command cl_showpos 1, the current camera angle can bee seen. By moving the mouse and searching for changed values in Cheat Engine, multiple addresses can be found that hold the current camera angle. However, only one can be used to actually set this angle programmatically.

Calculating the Camera Angle

The only thing that’s still required is the calculating the camera angle in order to automatically aim at the enemy. The following function takes the local player’s and the enemy coordinates and magically calculates this angle:

// Slightly modified standard `CalculateAngle` function.
std::vector<float> MadMath::calcAngle(std::vector<float> src, std::vector<float> dst)
{
    std::vector<float> angles;
    std::vector<double> delta = {src[0] - dst[0], src[1] - dst[1], src[2] - dst[2]};
    double hyp = std::sqrt(delta[0] * delta[0] + delta[2] * delta[2]);

    angles.push_back((float)(asinf(delta[1] / hyp) * RADIANT));
    angles.push_back((float)(atanf(delta[2] / delta[0]) * RADIANT));
    angles.push_back(0);

    if (delta[0] >= 0)
    {
        angles[1] += 180.0f;
    }

    return angles;
}

This works because of reasons and it’s great.

Playing Online

It definitively works but you will get banned because the aimbot just aims through walls, no matter if the enemy can actually be seen or not :D

References

Bypassing ASLR and DEP for 32-Bit Binaries With r2

May 1, 2019
exploiting r2 radare2 reverse-engineering ret2libc

Random Note #092345: Passing binary input via GDB

October 26, 2018
randomnote gdb exploiting reverse-engineering

Car Hacking: A Short Overview

October 23, 2018
automotive car-hacking reverse-engineering