Game Hacking #5: Hacking Walls and Particles

reverse-engineering c++ binary gamehacking

Hello fellow Wallhackers, NoSmokers and Copy-Pasters. Today, I’ll write about implementing several cool cheat features for your favorite game, CS:GO. There may be many articles like this, but this one is mine :)

First, of all I recommend checking out my previous posts covering several aspects and internals of CS:GO before reading this. I’ve got posts about Aimbots and NoFlash, as well as Direct3D hooks that will serve as a basis for this post.

Ok cool, so this is the plan for the internal cheat targeting the Windows version of the game:

Since I assume you’re not that patient, I’ll show you the results right here:

Setting Everything Up

How to develop and debug a cheat like this? Well, I didn’t know either so I’ve fiddled around and came up with a few handy things:

sv_cheats 1; bot_stop 1; mp_roundtime_defuse 9999; mp_restartgame 1

Otherwise the bots will play the game without you, kill you and will probably blow up dust2 and cause the memory layout to change while you’re staring at hex bytes.

There’s also a super secret debugging trick you can utilize. It’s called print debugging and you can spawn a console box after injecting to see the output:

#ifdef DEBUG
    AllocConsole();
    freopen_s(&console, "conout$", "w", stdout);
    [...]
    std::cout << "yolo" << std::endl;
#endif

Just don’t forget to close and free()the console when unloading the DLL. Real pros build DLLs in Debug mode and attach VisualStudio to csgo.exe though!

Interfacing

The cheat I’ve built is an internal one, which means that it actually runs as a thread inside of the game’s process. In contrast, external cheats don’t get injected into a game and act by reading and writing raw memory from the respective process.

Internal cheats have a bit of an advantage here: It’s easily possible to dynamically resolve and call engine functions. And because VALVe is very kind, all important engine DLLs export a function called CreateInterface() that allows to do just this:

// https://guidedhacking.com/threads/csgo-createinterface-tutorial-how-to-get-interfaces.14701/
typedef void* (__cdecl* tCreateInterface)(const char* name, int* returnCode);
void* Interface::GetInterface(const char* dllname, const char* interfacename)
{
    tCreateInterface CreateInterface = (tCreateInterface)GetProcAddress(GetModuleHandle(dllname), "CreateInterface");

    int returnCode = 0;
    void* result = CreateInterface(interfacename, &returnCode);
    if (!result) {
        throw std::runtime_error("[!] Could not create interface, very bad!");
    }
    return result;
}

Cool, what to do with that function? The Source engine SDK contains interface version information that can be passed as interfacename value to the function above, for example:

#define CLIENT_DLL_INTERFACE_VERSION "VClient017"

The resulting interface (or vTable or VMT or whatever you want to call it), can now directly be used to call engine functions of the IBaseClientDLL class, as defined in the SDK. An example of such a function is FrameStageNotify(), which will be important when implementing NoFlash and NoSmoke. However, the index of this function in the resulting vTable has to be reverse engineered first. You can also determine it with clever googling but that’s cheating and I hate all sorts cheating, especially in games. A quick note: today VClient017 is actually VClient018 so keep in mind that the SDK, which is from 2013(!) may not be up-to-date anymore.

Let’s say we know the index and we have a pointer to a vTable, as returned by GetInterface(). We can use this snippet the get the address of a function in the vTable at a specific index:

template<typename Function>Function GetVtableAtIndex(void* _VMT, int Index)
{
    void*** _TVMT = (void***)_VMT;
    void** VMT = *_TVMT;
    void* _Address = VMT[Index];
    return(Function)(_Address);
}

A triple pointer is used here because the pointer returned by GetInterface() is a pointer to a vTable, which in turn is an array of function pointers. Cool!

Wallhack

As soon as a player gets killed in CS:GO, the kill cam gets activated. A special thing about this view is that opponents will glow through walls. Ultimately, this is a built-in Wallhack feature and it just needs to be activated.

In the VALVe SDK you may notice a class called CGlowObjectManager, which holds references to GlowObjectDefinition_t objects. Here’s the struct from the SDK:

struct GlowObjectDefinition_t
{
    [...]

    EHANDLE m_hEntity;
    Vector m_vGlowColor;
    float m_flGlowAlpha;

    bool m_bRenderWhenOccluded;
    bool m_bRenderWhenUnoccluded;
    int m_nSplitScreenSlot;

    // Linked list of free slots
    int m_nNextFreeSlot;

    // Special values for GlowObjectDefinition_t::m_nNextFreeSlot
    static const int END_OF_FREE_LIST = -1;
    static const int ENTRY_IN_USE = -2;
};

As you can see, the first member holds a reference to the actual entity that is managed by a GlowObject. The most interesting member is m_bRenderWhenOcclude: It controls whether an entity’s glow will be rendered even though it is not visible, which is exactly what a Wallhack should do.

Therefore, the plan is to:

  1. Find an instance of CGlowObjectManager in memory.
  2. Iterate the linked list of GlowObjectDefinition_ts and check whether m_hEntity is an enemy.
  3. If so: Set the glow color (m_vGlowColor) and m_bRenderWhenOcclude accordingly.

I did not do that though. The reason for this is that I already had code ready to grab a list of all player entities, which I could filter for enemies. Each player entity holds a member value called glowIndex, which can be used to access the list of GlowObjectDefinition_ts in CGlowObjectManager by a numeric index. I’ve used this code snippet to access the linked list:

GlowObjectDefinition_t* GlowManager::GetGlowElement(int glowIndex) {
	DWORD dwGlowElem = base + (glowIndex * elementOffset) + skipBytes;
	return (GlowObjectDefinition_t*)dwGlowElem;
}

The value base is the base address of client.dll, which holds a reference to the CGlowObjectManager object. It seems at some point VALVe decided to troll cheaters and added 4 bytes of trash (?) at the start of each GlowObjectDefinition_t, which will be skipped by adding skipBytes (0x04). Here’s the memory layout of that:

As can be seen above, there’s a 4-byte value of 0xFFFFFFFE before the member value m_hEntity, which is important to skip when overwriting any memory.

And just for reference, here’s a memory view of the entity list an entity’s team number (0x03):

But when should be memory be overwritten? You could do it in a loop, which executes every 5ms or so but that would result in bad performance and flickering glow. The correct way to do it is to hook the function that’s responsible for adding effects before the rendering of a frame is finished. That function is called DoPostScreenSpaceEffects(), which is a member function of ClientMode.

In order to hook that function, its address has to be known. Remember the part about creating interfaces? This is how it works:

  1. Get an interface of to VClient018.
  2. Get the address of the function HudProcessInput, which is at vTable index 10.
  3. Read the value in memory at offset 0x5 and grab a pointer to ClientMode.
  4. Use vTable index 44 to get the function pointer of the desired function.

Why is there a pointer to ClientMode at this specific index, you may ask. Here’s the code of the HudProcessInput function:

void CHLClient::HudProcessInput( bool bActive ) {
	g_pClientMode->ProcessInput( bActive );
}

And this is the disassembly:

And now it’s just a matter of applying a trampoline hook, as described in a previous post:

unsigned char prologuePostScreenEffect[6];
void* objClientMode = nullptr;
tPostScreenEffect oPostScreenEffects = nullptr;

void InitWallhack() {
    MemoryManager ml;
    void* objClient = (void*)Interface::GetInterface("client.dll", "VClient018");

    // pointer to HudProcessInput method
    // https://www.unknowncheats.me/wiki/Finding_a_pointer_to_ClientMode
    DWORD pHudProcessInput = GetVtableAtIndex<DWORD>(objClient, 10);
    // this points to a pointer to ClientMode
    DWORD ppClientMode = ml.ReadMem<DWORD>((DWORD)pHudProcessInput + 0x5);
    objClientMode = ml.ReadMem<void*>(ppClientMode);
    oPostScreenEffects = (tPostScreenEffect)Haken::hookFn(GetVtableAtIndex<char*>(objClientMode, 44), (char*)hPostScreenEffects, 6, prologuePostScreenEffect, "PostScreenEffect");
}

// aka DoPostScreenSpaceEffects
void __stdcall hPostScreenEffects(void* edx, void* setup) {
    // wallhax code
    EntityList::AddGlowToMultiple();
    // call original
    // we could have also use __thiscall but yolo
    oPostScreenEffects(objClientMode, edx, setup);
}

NoFlash and NoSmoke

In CS:GO, the effects for flash and smoke grenades are called materials. I’ve implemented NoSmoke and NoFlash by applying a special flag to the respective materials in order to hide them. As described previously, the best place to apply this is a function that gets called as soon as a frame is about to be rendered. For materials, the best option is to use FrameStageNotify(), which resides in client.dll. As before, the interfacing technique can be applied to obtain a pointer to this function at vTable index 37of the interface VClient018.

Once FrameStageNotify() is hooked, the respective materials have to be searched, so that additional flags can be added. Two functions are required for this, which can both be found in materialsystem.dll with interface VMaterialSystem080:

The FindMaterial() function can be identified quite easily with a disassembler because of debugging statements left in the code:

Or dynamically with ReClass after finding the vTable pointer and checking the entry at index 84:

This gives us the following code to apply both NoFlash and NoSmoke:

std::vector<const char*> materials = {
        "particle/vistasmokev1/vistasmokev1_fire",
        "particle/vistasmokev1/vistasmokev1_smokegrenade",
        "particle/vistasmokev1/vistasmokev1_emods",
        "particle/vistasmokev1/vistasmokev1_emods_impactdust",

        "effects\\flashbang",
        "effects\\flashbang_white"
};

unsigned char prologueFrameStateNotify[17];
void* objClient = nullptr;
tFrameStageNotify oFrameStageNotify = nullptr;
tFindMaterial oFindMaterial = nullptr;
void* objMaterialSystem;

void InitMaterialHax() {
    objClient = (void*)Interface::GetInterface("client.dll", "VClient018");
    objMaterialSystem = (void*)Interface::GetInterface("materialsystem.dll", "VMaterialSystem080");

    oFindMaterial = GetVtableAtIndex<tFindMaterial>(objMaterialSystem, 84);
    oFrameStageNotify = (tFrameStageNotify)Haken::hookFn(GetVtableAtIndex<char*>(objClient, 37), (char*)hFrameStageNotify, 17, prologueFrameStateNotify, "FrameStageNotify");
}

void hFrameStageNotify(ClientFrameStage_t curStage) {
    if (curStage == ClientFrameStage_t::FRAME_NET_UPDATE_POSTDATAUPDATE_END) {

        for (auto it = materials.begin(); it != materials.end(); it++) {
            auto dasMaterial = oFindMaterial(objMaterialSystem, *it, "Other textures", true, NULL);
            if (!dasMaterial) { continue; }
            GetVtableAtIndex<tSetMaterialVarFlag>(dasMaterial, 29)(dasMaterial, MaterialVarFlags_t::MATERIAL_VAR_NO_DRAW, true);
        }
        oFrameStageNotify(curStage);
    }
    else {
        oFrameStageNotify(curStage);
    }
}

That's it. Don't forget to unhook all functions before unloading a DLL or the game will crash <:

References


And now a word from my fans:

BinaryGolf 2023: Building A GameBoy-Bash Polyglot

binary ctf

ShhPlunk: Muting the Splunk Forwarder

reverse-engineering c++ linux

Game Hacking #4: Cheating in Unity Games

frida gamehacking binary