After trying and failing to hack this game on and off for almost 15 years (I started playing young), I finally had enough background in C, assembly, and operating systems to pull it off. I did some reading and then wrote an internal hack with some help from forum posts on unknowncheats.me.
This is what final result looks like:
It’s only a no-flashbang hack, but all of the dirty work is done. It wouldn’t be too hard to extend the hack into something like a wallhack or an aimbot.
Here’s how it works and how to extend it.
An internal hack is made of two parts:
- The injector
- The DLL file (.so on linux)
The injector is a program that allocates space in the target process (the game) and then calls the win32 api
CreateRemoteThread function to start execution of the DLL file. The DLL then has access to all the game’s memory and can do whatever you want it to do granted you know how to do it.
Luckily, Valve released the source code (called the sdk) to Half-Life which CS is a mod of. So we can take a look around there for hints.
In the source code there’s a function called
Initialize that takes in a pointer to
int CL_DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion )
Doing a quick search in the sdk comes up with:
// Pointers to the exported engine functions themselves
This is the game’s engine function table.
If we can find where the
Initialize function is in memory with a debugger, we can actually use this struct in our dll by reading the function’s parameters as they’re passed, and then creating our own
cl_enginefunc_t pointer and setting it to the address we find.
This is super important because it means we have a way to call these game engine functions–functions like
pfnSetScreenFade which I used to make the no flash hack–inside the dll which we inject.
Doing the debugging to actually find the engine function table is the real legwork. I use x64dbg instead of OllyDbg because it’s just a tiny bit more colorful and has a decompiler option to convert assembly instructions to C code. I also like the “Time wasted debugging” feature that counts how long you’ve spent debugging the program.
After reading some forum posts on unknowncheats.me, I found a guide that spells everything out.
So, what needs to be done, in order, is:
Use the references tab and search for Initialize in hw.dll
- The address of
GetProcAddressis moved into esi and then called
GetProcAddress takes in a module and a string which is the name of a function that you want it to return the address of. In this case, hw.dll wants the
Initialize function that’s exported from client.dll.
test eax, eax
If it can’t find it, it prints “could not link client.dll function Initialize”. If it found it, it moves the result from
eax (Remember: return values are put into eax) into a place in memory.
Awesome, that’s the address of
Initialize. Now we need to find where
Initialize is used so we can see its parameters being passed and therefore find the engine function table. Luckily, with its address doing this is easy.
Right clicking the address and hitting “search for references to selected address” gives a few results, but luckily the first one is the one we need. Double-clicking on it gives us this:
By default, arguments are passed via the stack in reverse order. The highlighted section shows the version (
7) first being pushed, then the address to the engine function table being pushed next (
THIS is the money right here. Address 0x4C56A98 is the relative address of the engine function table.
All that needs to be done is to find a way to reference this address in the dll we write. In other words, we have to find an absolute address.
An absolute address is an address that won’t change the next time we run the game. The above address will change with each run because it really takes the form of
When the game runs, the operating system first loads the executable and then loads each .dll file that the game requires. The spot in memory where they get put isn’t the same every time–it’s up to the OS. The catch is that all the code and data structures inside of each dll will stay the same distance away from the assigned base in memory. So the “offset” won’t change unless the game developers recompile the game with changes to the code.
The solution is to get the base address of hw.dll through x64dbg. Then we can calculate the offset.
We can find the base address of hw.dll by looking at the symbols tab in x64dbg:
The base of hw.dll is 0x04AF0000.
The offset to the game engine function table from hw.dll (the module where it lives) is:
0x4C56A98 (address we found) - 0x04AF0000 (base of hw.dll) = 0x00166A98
and voila! All we need to do then is use
GetModuleHandle to find the base of
hw.dll when the game runs and then add this number to it and then we’ll have a working pointer to the game engine function table.
The full code is on my Github here.
I used a non-steam version of CS 1.6. Shameful, I know. I just don’t want any hacking tools anywhere near my 15 year old steam account. I do NOT want to get vac banned (oh yeah, this hack is definitely detected).
You can download a copy here.
The steps are:
- Download MinGW, run the setup and get all of the basic stuff
- Download Git SCM for the command line
- Add your msys mingw folder to your path on the command line
- Clone the full code:
git clone https://github.com/nsarka/cs-internal-hack.git
- Run the game, then go into the injector folder and run the injector as administrator
If it worked, you should see “Hack Loaded” print into the console.
At this point it shouldn’t be too bad. You can take a look at the actual
cl_enginefunc_t struct to see what engine functions are available to you, and if that isn’t enough you can also use this same method to find some other function table structures. There is the export table, the studio and the interface. Read about how to find them here.
I can’t say that was all my original work–far from it. The people at unknowncheats.me have been doing this for years so I have to thank them for posting their findings. Without it this would’ve been a months long ordeal since reverse engineering is the most time consuming part.
It was pretty fun to do this type of research and even more fun to actually get things working. I probably crashed the game 20-30 times trying to get the offsets right.
If anybody is interested or needs help running this my email is email@example.com. I’ll reply within a day.