RCE Endeavors 😅

August 1, 2022

Creating an ESP: World To Screen (2/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 8:41 PM

Table of Contents:

The first step for developing an ESP hack is being able to draw information on the screen at a specified position. While this seems like it should be easy at first, it is actually a rather complicated process. This is because objects exist in different vector spaces than the one that corresponds to your screen.

There is the local space, which puts the object’s center at the origin; world space, which is a common space that all objects live in; view space, which centers the camera at the origin and looks forward; clip space, which map objects to a fixed-size plane so that clipping can be done; and lastly, screen space, which is the window that is the two dimensional (x, y) space that corresponds to the window.

Going from one vector space to another involves multiplying the coordinates in one space by a transformation matrix to get the coordinates in the other space. For example, if you have coordinates in world space and you want to map them to view space, you would multiply those coordinates by a transformation matrix called a view matrix. Because matrix multiplication is associative, you can create a matrix that performs multiple transformations in one multiplication operation. This is where the world-to-screen matrix comes in: this matrix is a combination of a view matrix and a projection matrix whose purpose is to take three dimensional world space coordinates and map them to a two dimensional clip space. You can then transform the coordinates in the clip space to normalized device coordinates and adjust for the screen’s aspect ratio to get (x, y) coordinates in screen space.

Games will store some, or all, of these transformation matrices somewhere. Typically you will be able to find a world-to-screen matrix directly instead of having to find the view and projection matrices since a world-to-screen transformation happens so often. The process of reverse engineering a game to find these matrices is rather tedious: you will spend a lot of time panning your camera view around while scanning the process memory for values that you expect. For example, if you are looking directly down followed by directly up, you might expect the matrix to have values in [0, 1] or [-1, 1] within it. There are other ways such as trying to derive the matrix from your camera’s position and angles, and then scanning for that as well. All of these approaches are rather involved and feature a lot of trial and error; there can be an entire series of posts dedicated to reverse engineering view matrices in games.

This tedium will be removed in this series because the Source SDK is open source and provides an interface that allows users to get the world to screen matrix. As before, we will get a pointer to this interface and be able to access the WorldToScreenMatrix function. This is done by scanning for “VEngineClient014” in the referenced strings of the running game. After attaching a debugger and searching, we can find it pretty quickly

Looking at how it’s used, we can easily obtain the function pointer to the global interface

As in the previous series, we can construct the function to retrieve the interface as such

IVEngineClient* GetClientEngine() {

    constexpr auto globalGetClientEngineOffset{ 0xA3B30 };
    static GetClientEngineFnc getClientEngineFnc{ GetFunctionPointer<GetClientEngineFnc>(
        "engine.dll", globalGetClientEngineOffset) };

    return getClientEngineFnc();
}

Now the fun can begin. We have the ability to get the world-to-screen matrix, but we don’t know how to perform the actual transformation. There is an example of the world-to-screen matrix being used to perform a transformation in the ScreenTransform function. This function passes in the world-to-screen matrix to the FrustumTransform function, which is the function that performs the actual transformation. The FrustumTransform function performs the matrix multiplication to transform between the vector spaces, and in this case, it will transform the input point in world space to a (x, y) position in clip space. To go from clip space to screen space, we need to see how ScreenTransform is called. Fortunately, there is a helpful function called GetVectorInScreenSpace that shows the viewport transformation to screen space.

For our purposes, we can lift these from the Source SDK and incorporate them into the ESP hack with minor modifications. FrustumTransformation will stay more or less as it was

bool FrustomTransform(const VMatrix& worldToSurface, const Vector3& point, Vector2& screen) {

    screen.x = worldToSurface.m[0][0] * point.x + worldToSurface.m[0][1] * point.y + worldToSurface.m[0][2] * point.z + worldToSurface.m[0][3];
    screen.y = worldToSurface.m[1][0] * point.x + worldToSurface.m[1][1] * point.y + worldToSurface.m[1][2] * point.z + worldToSurface.m[1][3];
    auto w = worldToSurface.m[3][0] * point.x + worldToSurface.m[3][1] * point.y + worldToSurface.m[3][2] * point.z + worldToSurface.m[3][3];

    bool facing{};
    if (w < 0.001f)
    {
        facing = false;
        screen.x *= 100000;
        screen.y *= 100000;
    }
    else
    {
        facing = true;
        float invw = 1.0f / w;
        screen.x *= invw;
        screen.y *= invw;
    }

    return facing;
}

and we can modify GetVectorInScreenSpace slightly to only return true for positions that are visible in screen space. The function has been renamed to WorldToScreen for more clarity as well

bool WorldToScreen(const Vector3& position, Vector2& screenPosition) {

    auto worldToScreenMatrix{ GetClientEngine()->WorldToScreenMatrix() };

    auto facing{ FrustomTransform(worldToScreenMatrix, position, screenPosition) };

    int screenWidth{}, screenHeight{};
    GetClientEngine()->GetScreenSize(screenWidth, screenHeight);
    screenPosition.x = 0.5f * (1.0f + screenPosition.x) * screenWidth;
    screenPosition.y = 0.5f * (1.0f - screenPosition.y) * screenHeight;

    auto visible{ (screenPosition.x >= 0 && screenPosition.x <= screenWidth) &&
        screenPosition.y >= 0 && screenPosition.y <= screenHeight };
    if (!facing || !visible)
    {
        screenPosition.x = -640;
        screenPosition.y = -640;
        return false;
    }

    return true;
}

We can modify the aimbot code to print out the (x, y) screen coordinates of the closest enemy to the console to test this function out. After making the appropriate adjustments, we get the following results

which seem like legitimate numbers given the window size and resolution. The transformation appears to be successful! We are now able to translate an enemy’s world position to screen space. The first step in developing the ESP hack is done, now on to drawing.

Creating an ESP: Introduction (1/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 8:40 PM

Extra-Sensory Perception (ESP) hacks are a type of game hack that involve showing information to the player that they would not normally see. For example, these types of hacks might display an enemies’ position, their distance from the player, their health, what weapon they are using, and so on. They can also be more elaborate and change the enemies’ model to a more visible color, or draw a bounding box around them. This is all done with the purpose of providing the player with additional information that would not normally be visible to them. A few examples of ESP hacks are shown below which demonstrate this.

This next series of posts will go over how these hacks are created and provide a working proof of concept for an ESP targeting Half-Life 2. The series will start off by talking about a world-to-screen transformation: how models with a three dimensional position in the world get transformed to a two dimensional x and y coordinates system on your screen. Having established that, the series will then go over how to draw information on the game’s screen, and wrap up by showing an ESP proof of concept that draws some text over enemy models.

These posts will heavily leverage what was covered in the creating an aimbot series. It is recommended to read that first to get a better understanding of the Source SDK. Code from that series will also be heavily re-used for this series.

A link to each post is provided below:

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.

« Newer PostsOlder Posts »

Powered by WordPress