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:
- Create a Wallhack based on the
GlowManager
technique. - Add hooks to the particle and material system, so smoke and flash effects can be removed.
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:
- Since CS:GO is running in an x86 process, the injected DLL has to be an x86 one too.
- You can’t just go and use a DLL and a random loader with CS:GO, since it won’t allow arbitrary DLLs to be loaded into the process. Therefore, it is recommended to launch the game with the
--insecure
flag to disable VAC when developing and debugging. If the DLL is ready, it can then be injected using the GuidedHacking Injector or any other injector that uses manual mapping without having to disable VAC. Bonus: If the DLL is stealthy enough, it won’t get flagged <: I’ve used my simple DLL loader during development. - Grab the latest offsets from hazedumper. Or, even better, build a pattern scanning class to resolve the required offsets after injecting. I told you to read my previous posts, right?.
- Bind and issue the following CS:GO in-game command to make your life easier:
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:
- Find an instance of
CGlowObjectManager
in memory. - Iterate the linked list of
GlowObjectDefinition_t
s and check whetherm_hEntity
is an enemy. - If so: Set the glow color (
m_vGlowColor
) andm_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_t
s 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:
- Get an interface of to
VClient018
. - Get the address of the function
HudProcessInput
, which is at vTable index10
. - Read the value in memory at offset
0x5
and grab a pointer toClientMode
. - 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 37
of 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
:
FindMaterial()
to find a material based on a string-based identifier.SetMaterialVarFlag()
to add the flagMATERIAL_VAR_NO_DRAW
to a material.
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: