4. Game Hacking - Valve Anti-Cheat (VAC)
Intro
In 2002 Valve created an Anti-Cheat solution called “Valve Anti-Cheat” aka VAC. The first game they implemented VAC into was Counter-Strike. When VAC was introduced it only operated in User Mode (Still does) meaning it runs entirely in user space 1 and has no kernel component.
Below is a list of games that use VAC.. 2
Call of Duty: Modern Warfare 2
Call of Duty: Modern Warfare 3
Counter-Strike (video game)
Counter-Strike: Condition Zero
Counter-Strike: Source
Counter-Strike 2
Day of Defeat
Day of Defeat: Source
Deathmatch Classic
Half-Life 2: Deathmatch
Half-Life Deathmatch: Source
Ricochet
Team Fortress
Team Fortress Classic
A longer list can be found here 3.
VAC-cident?
So.. if you don’t know VAC has been around for quite a while, at the time of writing it’ll be 23 years. Over the time they’ve made some mistakes but who doesn’t? (Taken from wikipedia) 4 5
- In July 2010, [snip] Approximately 12,000 owners of Call of Duty: Modern Warfare 2 were
banned when Steam updated a DLL file on disk after it had been loaded into memory by the
game, causing a false positive detection. These bans were revoked and those affected received
a free copy of Left 4 Dead 2 or an extra copy to send as a gift.
- In October 2023, certain users of AMD graphics cards were banned from Counter-Strike 2
after AMD added support for their "Anti-Lag+" feature via a driver update, which the game
flagged as a cheat due to it detouring certain DLL functions. AMD subsequently withdrew the
driver update and Valve pledged to unban any affected users.
This post isn’t created to bash Valve they clean up after their mistakes and listen to their community, gotta love devs when they do that. I also commend them because getting VAC banned isn’t such a slap on the wrist. Getting VAC banned has some stipulations such as:
- Having the VAC ban show on your Steam profile
- Being banned from all “GoldSrc” games
- Being banned from all “Source engine” games (The Counter-Strike serise)
- Not being able to refund the game you’re VAC banned on
Knowing what’ll happen if you get VAC banned is important to know because regardless if you’re cheating or not false bans are no good. People in the community took it upon themselves to reverse engineer the Anti-Cheat and understand what it does (some did it just to cheat).
VAC what?
The previous section brings us to a term you may have heard before, the infamous “VAC Bypass”. Searching online for information about bypassing VAC brings many blogs and repos that all seem to do/talk about something similar and that’s “Dumping the VAC modules”. Let me explain, VAC is NOT just one executable on the system it streams it’s Anti-Cheat modules (DLLs) from a server, once a module is recieved by some routine in steamservice.dll inside steamservice.exe (or steam.exe if ran as admin) it will be loaded using one of the two methods below. 6 7 8 9 10 11 12 13
- Reflectively load the DLL into memory
- Use the WinAPI function LoadLibrary
By default the Anti-Cheat modules are reflectively loaded into memory. The goal is to force LoadLibrary into being used, then someone can hook that function and dump the modules (DLLs) to disk, allowing someone to analyse the dumped DLLs and understand what they’re doing to detect cheating.
Dumping VAC Modules in the big ‘25
To kick start the journey on dumping the VAC modules load steamservice.dll into Binary Ninja. Once the binary is fully analyzed go to “Triage Summary”
It is VERY important to take note that this is a 32-bit process, so all pointers will be 32-bits in size you’ll see why this is important later.
Next we’ll search for calls to LoadLibrary* I’ll save the reader some time and tell you that we should be looking for calls to LoadLibraryW it will be called in a very important function that we can use to back track.
Following the reference takes us to an interesting function sub_10086f80
Judging by the return value HMODULE and the calls to LoadLibrary* it’s safe to say this function’s job is to load some kind of module and return a handle to it. Following references to where this function is called leads us to another interesting function sub_10086c40.
The beginning of the sub_10086c40 function didn’t look too important (at the time) but we should remember that this function also returns a handle to a module. I looked at the references and it shows that this function is called once in the function sub_10059040.
We can see sub_10086c40 being called if we trace back the first argument passed to that function, we’ll see that it was used by another function sub_100859d0. If that function call is successful execution carries on, so it’s safe to say it’s important. Let’s take a look at this function.
This function makes two WinAPI calls
GetTempPathW : Retrieves the path of the directory designated for temporary files.
GetTempFileNameW : Creates a name for a temporary file. If a unique file name is generated, an empty file is created and the handle to it is released; otherwise, only a file name is generated.
The combination of these calls tells us that we need to be looking for any .TMP
files being accessed, the names are usually in this format <uuuu>.TMP
.
Now there’s a path to a DLL floating in memory how does it get used? Look no more.
100591f7 HMODULE eax_13 = sub_10086c40(edi_1, 0)
100591ff *(esi + 4) = eax_13
100591ff
10059204 if (eax_13 != 0)
10059215 int32_t eax_14 = sub_10086c20(eax_13, "_runfunc@20")
1005921d *(esi + 0xc) = eax_14
The path edi_1
is used by sub_10086c40, this function is used to get a handle to a module eax_13
then it’s passing that handle to sub_10086c20.
sub_10086c20 takes two arguments we know the first is a handle to a module the second is from what we can see here a string _runfunc@20
, the return value
int32_t
looks a little weird but this is a 32-bit process remember ;) so this could be a pointer to something dont ya think? Here’s the function prototype
int32_t sub_10086c20(HMODULE arg1, PSTR arg2)
Place your bets on it being a GetProcAddress wrapper.. Drum roll please… It is…
So with this bit of information we know steamservice.dll recieves the VAC modules, it’s using a function sub_10086c40 which calls
sub_10086f80 to load the Anti-Cheat module and return a handle, then that handle is passed to sub_10086c20 to get the address
of a function named _runfunc@20
. By default as said earlier the modules are reflectively loaded so this isn’t the regular control flow of
steamservice.dll, this can be confirmed if you scroll up a bit in sub_10059040 you’ll see a flag being checked.
steamservice.dll will most likely take this path unless we can do something about it
Let’s look at it in assembly
Take a look at je 0x10059127
( 0x74 0x47
)
0x74
is the jump if equal instruction and 0x47
is how many bytes forward to jump (71) in hex
What we wan’t to do is change the first instruction at steamservice.dll + 0x590DE (0x100590de)
- to
jne 0x10059127
(0x75 0x47
)
we’re changing the first byte of this instruction to be 0x75
which is jump if NOT
zero/equal. (Inverting)
Now that we have a potential way of dumping the VAC modules let’s test it! first we start steam and launch x32dbg as admin we should remember the offset to our instructions steamservice.dll + 0x590DE.
Once x32dbg is loaded attach to steamservice.dll
Press CTRL+G
and enter steamservice.dll + 0x590DE
Now we’re where we need to patch
Right click on that instruction and click “Assemble” then enter jnz 0x10059127
and hit ok
It should be changed
The next step is to open Procmon play a game that uses VAC (I chose CSGO) and wait for
steamservice.exe to access some .TMP
files.
Here are the Procmon filters
While loading the game we see our first TMP file C:\Windows\Temp\D54A.tmp
Let’s join a public match and see if there are others
And some more..
We can also look at these files in the temp directory..
I copied all of these files to a new directory and loaded D54A.tmp
into PE-bear
We see something familiar _runfunc@20
this is the function that was found using sub_10086c20.
To be continued
In the next post we will be doing analysis on these Anti-Cheat Modules to get a better understanding of what’s going on. I hope you enjoyed this post and most importantly learned a thing or two. Stay tuned!
References