RCE Endeavors 😅

July 5, 2022

Creating an Aimbot: Conclusion (4/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:30 PM

Table of Contents:

This concludes the series of how to create an aimbot. The techniques that were presented here are generally applicable to any FPS game. The way to obtain positions and angles may be different across game engines, but the vector math to create a view angle from a distance vector is valid anywhere.

The reverse engineering in this series was greatly simplified due to the availability of the Source SDK. Being able to map the code and data structures to the assembly code made debugging much easier – usually you won’t be so lucky! Hopefully this series cleared up some mysteries around how aimbots work and – for better or worse – showed that they are not too difficult to create.

The full source code for the aimbot is available on GitHub; feel free to try it out.

Creating an Aimbot: Building the Aimbot (3/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:29 PM

Table of Contents:

The previous post covered reverse engineering the Half-Life 2 executable and finding addresses of functions that would allow us to get entity eye positions and eye angles. This was done by means of initially getting a pointer to a global CServerTools interface and tracing through its member functions. This interface will also be useful as it allows for iteration of entities in the game’s entity list. In order to build the aimbot, we need to do the following:

  • Iterate through the entities
    • If the entity is an enemy, find the distance between the entity and the player
    • Keep track of the closest entity
  • Calculate the eye-to-eye vector between the player and the closest enemy
  • Adjust the player’s eye angle to follow this vector

From the previous post, we know that we can call GetPlayerPosition to get the player’s position. To loop through the entity list, we can call FirstEntity and NextEntity, which return a pointer to a CBaseEntity instance. To see whether an entity is an enemy, we can compare the entity name against a set of hostile NPC entity names. If we have an enemy entity, then we calculate the distance between the player and it, and save the position of the entity if it is the closest that we’ve seen so far.

After iterating through the entire entity list, we take the closest enemy, calculate the eye-to-eye vector, and adjust the player’s eye angles with the aid of the VectorAngles function.

Putting it into code, we get the following

auto* serverEntity{ reinterpret_cast<IServerEntity*>(
    GetServerTools()->FirstEntity()) };

if (serverEntity != nullptr) {
    do {
        if (serverEntity == GetServerTools()->FirstEntity()) {

            SetPlayerEyeAnglesToPosition(closestEnemyVector);
            closestEnemyDistance = std::numeric_limits<float>::max();
            closestEnemyVector = GetFurthestVector();
        }

        auto* modelName{ serverEntity->GetModelName().ToCStr() };
        if (modelName != nullptr) {
            auto entityName{ std::string{GetEntityName(serverEntity)} };

            if (IsEntityEnemy(entityName)) {
                Vector eyePosition{};
                QAngle eyeAngles{};

                GetServerTools()->GetPlayerPosition(eyePosition, eyeAngles);

                auto enemyEyePosition{ GetEyePosition(serverEntity) };

                auto distance{ VectorDistance(enemyEyePosition, eyePosition) };
                if (distance <= closestEnemyDistance) {
                    closestEnemyDistance = distance;
                    closestEnemyVector = enemyEyePosition;
                }
            }
        }

        serverEntity = reinterpret_cast<IServerEntity*>(
            GetServerTools()->NextEntity(serverEntity));

    } while (serverEntity != nullptr);
}

There are a few helper functions here that were not covered earlier: the GetFurthestVector function returns a vector with max float values in the x, y, and z fields; GetEntityName returns the entity name as a string by getting the m_iName member of the CBaseEntity instance; and IsEntityEnemy just checks the entity name against a set of enemy NPCs.

The vector math and new view angle calculation happens in SetPlayerEyeAnglesToPosition, which is shown below:

void SetPlayerEyeAnglesToPosition(const Vector& enemyEyePosition) {

    Vector eyePosition{};
    QAngle eyeAngles{};
    GetServerTools()->GetPlayerPosition(eyePosition, eyeAngles);

    Vector forwardVector{ enemyEyePosition.x - eyePosition.x,
        enemyEyePosition.y - eyePosition.y,
        enemyEyePosition.z - eyePosition.z
    };

    VectorNormalize(forwardVector);

    QAngle newEyeAngles{};
    VectorAngles(forwardVector, newEyeAngles);

    SnapEyeAngles(GetLocalPlayer(), newEyeAngles);
}

This function calculates the eye-to-eye vector by subtracting the enemies’ eye position vector with the player’s eye position vector. This new vector is then normalized and passed to the VectorAngles function to calculate the new view angles. Lastly, the player’s eye angles get adjusted to these new angles, which should have the effect of tracking to the entity.

What does this look like in action?

You can see the (very faint) crosshair follow the NPCs head as it walks across the room. When the NPC walks far enough away, the code snaps to a closer NPC. It works!

Creating an Aimbot: Reverse Engineering & Source SDK (2/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:26 PM

Table of Contents:

This post will go over what is required to build an aimbot, and show how to reverse engineer our target executable to find what we need. In order to make something that automatically aims at a target, there are a few things that need to be known:

  • The player’s eye position
  • The nearest enemies’ eye position
  • The vector from the player’s eye to the enemies’ eye (derived from the previous two points)
  • How to adjust the player’s view such that the player’s eye is looking down the vector to the enemies’ eye

All but the third step require getting information from the running state of the game. This information is obtained by reverse engineering the game to find the classes that contain the relevant fields. Usually, this would be an extremely time consuming effort full of trial an error; however, in this case, we have access to the Source SDK, which we will use to guide the reverse engineering effort.

We will start the search by looking for references to eye position from within their repository. After clicking through a few pages of search results, we land on the gigantic CBaseEntity class. Inside this class, there are two functions:

virtual Vector EyePosition(void);
virtual const QAngle &EyeAngles(void);

Since CBaseEntity is the base class that all entities derive from, and it contains members for eye position and eye viewing angles, it seems like the class that we want to work with. The next step is to see where these functions are referenced from. Again, after searching in the Source SDK GitHub for a bit, we come across the IServerTools interface, which has a handful of very promising functions:

virtual IServerEntity *GetIServerEntity(IClientEntity *pClientEntity) = 0;
virtual bool SnapPlayerToPosition(const Vector &org, const QAngle &ang, IClientEntity *pClientPlayer = NULL) = 0;
virtual bool GetPlayerPosition(Vector &org, QAngle &ang, IClientEntity  *pClientPlayer = NULL) = 0;

// ...

virtual CBaseEntity *FirstEntity(void) = 0;
virtual CBaseEntity *NextEntity(CBaseEntity *pEntity) = 0;

What is really nice about this interface is that it provides access to the local player’s position, allows for snapping the player to another position and viewing angle, and also provides the ability to iterate over entities. Add to the fact that it is globally instantiated and tied to a hardcoded string, and it doesn’t get much better.

#define VSERVERTOOLS_INTERFACE_VERSION_1	"VSERVERTOOLS001"
#define VSERVERTOOLS_INTERFACE_VERSION_2	"VSERVERTOOLS002"
#define VSERVERTOOLS_INTERFACE_VERSION		"VSERVERTOOLS003"
#define VSERVERTOOLS_INTERFACE_VERSION_INT	3

// ...

EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerTools, IServerTools001, VSERVERTOOLS_INTERFACE_VERSION_1, g_ServerTools);
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerTools, IServerTools, VSERVERTOOLS_INTERFACE_VERSION, g_ServerTools);

We can begin the aimbot development by finding this class in memory. After launching Half-Life 2 and attaching a debugger, we can search for string references to VSERVERTOOLS.

We can see where these are referenced:

7BCAB090 | 68 88FA1F7C              | push server.7C1FFA88                    | 7C1FFA88:"VSERVERTOOLS001"
7BCAB095 | 68 00C4087C              | push server.7C08C400                    |
7BCAB09A | B9 B02A337C              | mov ecx,server.7C332AB0                 |
7BCAB09F | E8 8CCA3F00              | call server.7C0A7B30                    |
7BCAB0A4 | C3                       | ret                                     |
7BCAB0A5 | CC                       | int3                                    |
7BCAB0A6 | CC                       | int3                                    |
7BCAB0A7 | CC                       | int3                                    |
7BCAB0A8 | CC                       | int3                                    |
7BCAB0A9 | CC                       | int3                                    |
7BCAB0AA | CC                       | int3                                    |
7BCAB0AB | CC                       | int3                                    |
7BCAB0AC | CC                       | int3                                    |
7BCAB0AD | CC                       | int3                                    |
7BCAB0AE | CC                       | int3                                    |
7BCAB0AF | CC                       | int3                                    |
7BCAB0B0 | 68 98FA1F7C              | push server.7C1FFA98                    | 7C1FFA98:"VSERVERTOOLS002"
7BCAB0B5 | 68 00C4087C              | push server.7C08C400                    |
7BCAB0BA | B9 BC2A337C              | mov ecx,server.7C332ABC                 |
7BCAB0BF | E8 6CCA3F00              | call server.7C0A7B30                    |
7BCAB0C4 | C3                       | ret                                     |

From the assembly listing, we see that a member function at server.7C0A7B30 is being invoked on server.7C332AB0 and server.7C332ABC. This function takes in two arguments, one of which is the string name of the interface. The other parameter, after browsing in the debugger, is a static instance to something.

After seeing what the EXPOSE_SINGLE_INTERFACE_GLOBALVAR macro does in the code, it is more clear that this is the CServerTools singleton, which is being exposed as a global interface. Knowing this, we can easily obtain a pointer to this singleton at runtime: we simply take the address of this pseudo-function that is moving the pointer into EAX and invoke it directly. We can write the following generic code to achieve this, which we will continue to re-use for other functions:

template <typename T>
T GetFunctionPointer(const std::string moduleName, const DWORD_PTR offset) {

    auto moduleBaseAddress{ GetModuleHandleA(moduleName.c_str()) };
    if (moduleBaseAddress == nullptr) {
        std::cerr << "Could not get base address of " << moduleName
            << std::endl;
        std::abort();
    }
    return reinterpret_cast<T>(
        reinterpret_cast<DWORD_PTR>(moduleBaseAddress) + offset);
}

IServerTools* GetServerTools() {

    constexpr auto globalServerToolsOffset{ 0x3FC400 };
    static GetServerToolsFnc getServerToolsFnc{ GetFunctionPointer<GetServerToolsFnc>(
        "server.dll", globalServerToolsOffset) };

    return getServerToolsFnc();
}

Here, we take the base address that server.dll loaded at, add in the offset to get to where the CServerTools singleton can be accessed, and return it as a pointer to the caller. From there, we can call whatever functions we want in the interface and the game will act accordingly. The two functions of interest are going to be GetPlayerPosition and SnapPlayerToPosition.

Inside of GetPlayerPosition, the local player’s class is retrieved via a call to UTIL_GetLocalPlayer, and the EyePosition and EyeAngles functions are also called; inside of SnapPlayerToPosition, the player’s view angles are adjusted with a call to SnapEyeAngles. Together, this will give us everything we need to be able to retrieve entity positions and view angles, thus making it possible to make the appropriate calculations for the new vector and view angle that aligns to an enemies’ eyes.

Taking these one at a time, we can begin with GetPlayerPosition. Since we can obtain a pointer to IServerTools, and we have the interface definition, we can make an explicit call to GetPlayerPosition, and step through the call with a debugger. Doing so leads us to here:

7C08BEF0 | 55                       | push ebp                                |
7C08BEF1 | 8BEC                     | mov ebp,esp                             |
7C08BEF3 | 8B01                     | mov eax,dword ptr ds:[ecx]              |
7C08BEF5 | 83EC 0C                  | sub esp,C                               |
7C08BEF8 | 56                       | push esi                                |
7C08BEF9 | FF75 10                  | push dword ptr ss:[ebp+10]              |
7C08BEFC | FF50 04                  | call dword ptr ds:[eax+4]               |
7C08BEFF | 8BF0                     | mov esi,eax                             |
7C08BF01 | 85F6                     | test esi,esi                            |
7C08BF03 | 75 14                    | jne server.7C08BF19                     |
7C08BF05 | E8 E616E7FF              | call server.7BEFD5F0                    |
7C08BF0A | 8BF0                     | mov esi,eax                             |
7C08BF0C | 85F6                     | test esi,esi                            |
7C08BF0E | 75 09                    | jne server.7C08BF19                     |
7C08BF10 | 32C0                     | xor al,al                               |
7C08BF12 | 5E                       | pop esi                                 |
7C08BF13 | 8BE5                     | mov esp,ebp                             |
7C08BF15 | 5D                       | pop ebp                                 |
7C08BF16 | C2 0C00                  | ret C                                   |
7C08BF19 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08BF1B | 8D4D F4                  | lea ecx,dword ptr ss:[ebp-C]            |
7C08BF1E | 51                       | push ecx                                |
7C08BF1F | 8BCE                     | mov ecx,esi                             |
7C08BF21 | FF90 08020000            | call dword ptr ds:[eax+208]             |
7C08BF27 | 8B4D 08                  | mov ecx,dword ptr ss:[ebp+8]            |
7C08BF2A | D900                     | fld st(0),dword ptr ds:[eax]            |
7C08BF2C | D919                     | fstp dword ptr ds:[ecx],st(0)           |
7C08BF2E | D940 04                  | fld st(0),dword ptr ds:[eax+4]          |
7C08BF31 | D959 04                  | fstp dword ptr ds:[ecx+4],st(0)         |
7C08BF34 | D940 08                  | fld st(0),dword ptr ds:[eax+8]          |
7C08BF37 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08BF39 | D959 08                  | fstp dword ptr ds:[ecx+8],st(0)         |
7C08BF3C | 8BCE                     | mov ecx,esi                             |
7C08BF3E | FF90 0C020000            | call dword ptr ds:[eax+20C]             |
7C08BF44 | 8B4D 0C                  | mov ecx,dword ptr ss:[ebp+C]            |
7C08BF47 | 5E                       | pop esi                                 |
7C08BF48 | D900                     | fld st(0),dword ptr ds:[eax]            |
7C08BF4A | D919                     | fstp dword ptr ds:[ecx],st(0)           |
7C08BF4C | D940 04                  | fld st(0),dword ptr ds:[eax+4]          |
7C08BF4F | D959 04                  | fstp dword ptr ds:[ecx+4],st(0)         |
7C08BF52 | D940 08                  | fld st(0),dword ptr ds:[eax+8]          |
7C08BF55 | B0 01                    | mov al,1                                |
7C08BF57 | D959 08                  | fstp dword ptr ds:[ecx+8],st(0)         |
7C08BF5A | 8BE5                     | mov esp,ebp                             |
7C08BF5C | 5D                       | pop ebp                                 |
7C08BF5D | C2 0C00                  | ret C                                   |

There’s a bit to go through here, though it looks more straightforward as a control flow graph:

If we line up the disassembly side-by-side with the code, we can find what we want rather quickly. The code only calls the UTIL_GetLocalPlayer function if the passed in pClientEntity parameter is null. This logic is checked in the first function block in the graph. If there is a valid client entity, the code continues to retrieve the eye position and eye angles for it, otherwise it gets the local player’s entity. This call happens with the execution of the call server.7BEFD5F0 instruction at server.7C08BF05. Like before, we can create a function pointer to UTIL_GetLocalPlayer and invoke it directly.

CBasePlayer* GetLocalPlayer() {

    constexpr auto globalGetLocalPlayerOffset{ 0x26D5F0 };
    static GetLocalPlayerFnc getLocalPlayerFnc{ GetFunctionPointer<GetLocalPlayerFnc>(
        "server.dll", globalGetLocalPlayerOffset) };

    return getLocalPlayerFnc();
}

Next in the disassembly are calls to the EyePosition and EyeAngles functions. We are only interested in retrieving the eye positions, so only the first call is relevant. To get the address of the function we can step through the call until we end up calling the address that is in [EAX+0x208]. After executing that instruction, we will be at server.dll+0x119D00, and thus know where the function is located.

Vector GetEyePosition(CBaseEntity* entity) {
    
    constexpr auto globalGetEyePositionOffset{ 0x119D00 };
    static GetEyePositionFnc getEyePositionFnc{ GetFunctionPointer<GetEyePositionFnc>(
        "server.dll", globalGetEyePositionOffset) };

    return getEyePositionFnc(entity);
}

That is all that we need from GetPlayerPosition; we now have the ability to get the player’s local entity pointer, and have the ability to get the eye position of an entity. The last thing that we need is the ability to set the player’s eye angle. As mentioned before, we can do this by calling the SnapPlayerToPosition function and seeing where the SnapEyeAngles function is located at. The disassembly of SnapEyeAngles looks like this:

7C08C360 | 55                       | push ebp                                |
7C08C361 | 8BEC                     | mov ebp,esp                             |
7C08C363 | 8B01                     | mov eax,dword ptr ds:[ecx]              |
7C08C365 | 83EC 0C                  | sub esp,C                               |
7C08C368 | 56                       | push esi                                |
7C08C369 | FF75 10                  | push dword ptr ss:[ebp+10]              |
7C08C36C | FF50 04                  | call dword ptr ds:[eax+4]               |
7C08C36F | 8BF0                     | mov esi,eax                             |
7C08C371 | 85F6                     | test esi,esi                            |
7C08C373 | 75 14                    | jne server.7C08C389                     |
7C08C375 | E8 7612E7FF              | call server.7BEFD5F0                    |
7C08C37A | 8BF0                     | mov esi,eax                             |
7C08C37C | 85F6                     | test esi,esi                            |
7C08C37E | 75 09                    | jne server.7C08C389                     |
7C08C380 | 32C0                     | xor al,al                               |
7C08C382 | 5E                       | pop esi                                 |
7C08C383 | 8BE5                     | mov esp,ebp                             |
7C08C385 | 5D                       | pop ebp                                 |
7C08C386 | C2 0C00                  | ret C                                   |
7C08C389 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08C38B | 8BCE                     | mov ecx,esi                             |
7C08C38D | FF90 24020000            | call dword ptr ds:[eax+224]             |
7C08C393 | 8B4D 08                  | mov ecx,dword ptr ss:[ebp+8]            |
7C08C396 | F3:0F1001                | movss xmm0,dword ptr ds:[ecx]           |
7C08C39A | F3:0F5C00                | subss xmm0,dword ptr ds:[eax]           |
7C08C39E | F3:0F1145 F4             | movss dword ptr ss:[ebp-C],xmm0         |
7C08C3A3 | F3:0F1041 04             | movss xmm0,dword ptr ds:[ecx+4]         |
7C08C3A8 | F3:0F5C40 04             | subss xmm0,dword ptr ds:[eax+4]         |
7C08C3AD | F3:0F1145 F8             | movss dword ptr ss:[ebp-8],xmm0         |
7C08C3B2 | F3:0F1041 08             | movss xmm0,dword ptr ds:[ecx+8]         |
7C08C3B7 | 8BCE                     | mov ecx,esi                             |
7C08C3B9 | F3:0F5C40 08             | subss xmm0,dword ptr ds:[eax+8]         |
7C08C3BE | 8D45 F4                  | lea eax,dword ptr ss:[ebp-C]            |
7C08C3C1 | 50                       | push eax                                |
7C08C3C2 | F3:0F1145 FC             | movss dword ptr ss:[ebp-4],xmm0         |
7C08C3C7 | E8 14CFD0FF              | call server.7BD992E0                    |
7C08C3CC | FF75 0C                  | push dword ptr ss:[ebp+C]               |
7C08C3CF | 8BCE                     | mov ecx,esi                             |
7C08C3D1 | E8 4A0FE0FF              | call server.7BE8D320                    |
7C08C3D6 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08C3D8 | 8BCE                     | mov ecx,esi                             |
7C08C3DA | 6A FF                    | push FFFFFFFF                           |
7C08C3DC | 6A 00                    | push 0                                  |
7C08C3DE | FF90 88000000            | call dword ptr ds:[eax+88]              |
7C08C3E4 | B0 01                    | mov al,1                                |
7C08C3E6 | 5E                       | pop esi                                 |
7C08C3E7 | 8BE5                     | mov esp,ebp                             |
7C08C3E9 | 5D                       | pop ebp                                 |
7C08C3EA | C2 0C00                  | ret C                                   |

Following the same process as before, we find that the call server.7BE8D320 instruction is the call to SnapEyeAngles. We can define a function around it as follows:

void SnapEyeAngles(CBasePlayer* player, const QAngle& angles)
{
    constexpr auto globalSnapEyeAnglesOffset{ 0x1FD320 };
    static SnapEyeAnglesFnc snapEyeAnglesFnc{ GetFunctionPointer<SnapEyeAnglesFnc>(
        "server.dll", globalSnapEyeAnglesOffset) };

    return snapEyeAnglesFnc(player, angles);
}

At this point, we have everything needed to build the aimbot. The next post will put it all together and end with a working proof of concept.

Creating an Aimbot: Introduction (1/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:20 PM

This series of posts will go over what it takes to build an aimbot – a program that automatically aims at enemies – for an FPS game. The game that we will be building the aimbot for is Half-Life 2, running on the Source engine. The aimbot will run from within the game’s process and use reverse engineered internal game functions to perform its functionality, as opposed to something that runs externally and scans the screen.

The core of the series will be broken down into two parts: first we will take a look at the Source SDK and use it as a guide to reverse engineer the Half-Life 2 executable, then we will use the knowledge that was gained by reversing to build the bot. By the end of the series, we will have written an aimbot that snaps the view angle to the nearest enemy.

A link to each post is provided below:

December 1, 2021

Reverse Engineering REST APIs: Conclusion (12/12)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 9:16 AM

Table of Contents:

Reverse engineering the REST APIs that power the Age of Empires IV multiplayer lobby system is complete at this point.

Throughout the series, we took different approaches at achieving this goal. Initially, we started off using third-party tools that allowed us to set up a reverse proxy and route the game’s traffic through it. From this, we were able to see the decrypted request and response content. We were fortunate that the game did not use certificate pinning, otherwise this technique would have been rendered useless. The series then took a turn towards debugging the game and getting some basic information from the game’s strings dump. From this, it was found that the game used an OpenSSL feature that allowed the generated keys to be written out to a file specified by the SSLKEYLOGFILE environment variable. We were able to import the key information from this file and decrypt captured request and response data in Wireshark.

The series then got more technical as the game was reverse engineered. By attaching a debugger and setting breakpoints on the Winsock send and recv functions, we were able to walk the call stack backwards until we had a point in the code where we had access to the plaintext data. The functions responsible for encrypting and decrypting the data were reverse engineered and their prototypes were extracted. Using these prototypes, we were able to create and set our hook functions. From within these hook functions, we had direct access to the plaintext request and response data. In the example source code, we logged the data out to console, or a file, but we can do whatever we want with it.

Hopefully this series has been helpful in describing what goes in to reverse engineering a processes network communication. In the best case scenario, it can be a trivial task that can be accomplished with the use of third-party tools. In the more complex scenario, it is a very technical process that involves a deep dive into the code and a strong understanding of reverse engineering at the assembly level, dynamic analysis via a debugger, and an understanding of what to look for. I hope that readers of this series have learned something and walk away with a better understanding of what it takes to reverse engineer software.

« Newer PostsOlder Posts »

Powered by WordPress