Counter-Strike 2 Release
Last month in September, Valve officially released Counter-Strike 2, which took over from Counter-Strike: Global Offensive on Steam.
Although I haven’t had the chance to play the game yet, I’ve heard that it hasn’t changed much from the beta version I tried in March. My involvement in other research projects has kept me from diving deeper into the game, despite my initial strong interest.
CS2 Code Integrity & VAC
A few weeks, articles started appearing saying AMD’s Anti-Lag+ technology is getting Counter-Strike 2 players banned. This got me interested, so I started looking into the game again.
I made a post about VAC and CSGO back in 2017, focusing on Untrusted Bans. Valve began signing game libraries and checking the game’s integrity at a file system level; since then, many more checks have been added to the game, including checks on code integrity including core game VMT pointers. You can see this openly by viewing the CUserMessage_Inventory_Response/CUserMessage_DllStatus protobuff.
If you open CS2.exe inside IDA and xref the string
system.signatures you will see what I’m talking about.
Taking a quick look what else that is interesting to note is a file log that gets written to
C:\Program Files (x86)\Steam\userdata\(Steamid)\730\local\cfg\trustedlaunch.cfg
If you open this you will see it’s not actually a configuration file but more of a log:
- The first element represents a state,
3being a normal state,
2being set when you launch the game with the
- The second element again
3seems to be a duplicate representing the same state. (Not sure why?)
- The third element in the list represents the active process id of the game cs2.exe.
- The fourth and fifth element represent the DateTime at which the log was written.
To explain this a bit further if we take the first entry in my sample above:
0x1da0559represents the HighPart
0xf47511d2represents the LowPart
This combined represents a FILETIME object in Windows.
This can be achieved like so in C:
uint64_t combined = ((uint64_t)HighPart << 32) | LowPart;
Which results in :
October 23, 2023, XX:XX:XX.
I won’t detail every aspect of the process here—perhaps that’s material for another post. Essentially, files on your PC, including system files, game files, and third-party files like the Discord Overlay, are all checked before the game can run successfully and allow you to join official Valve servers. Nothing amazing here, you can literally open the executable and have a look yourself as it’s not protected or packed with anything.
A little deeper
A few hooks are placed on some notable library functions:
- ntdll.dll -> NtOpenFile
- kernelbase.dll -> LoadLibraryExW
- user32.dll -> PeekMessageW
The game registers a Vectored Exception Handler, where it handles the following:
- Privileged Instruction Exception (0xC0000096)
- Access Violation (0xC0000005)
Some interesting functions get loaded from
- Plat_GetModuleProcAddress (Encapsulates GetProcAddress)
- Plat_LoadModule (Calls LoadLibraryExW internally)
- Plat_FindModuleByName (Calls GetModuleHandleW internally)
- Plat_ReportDllLoadDenial (Stored somewhere, Plat_GetLastDllLoadDenial called later to retrieve it)
This hook does the following in this order:
ObjectAttributes->ObjectName->Bufferif it’s invalid calls original
Check incoming Access Rights through the
DesiredAccessparameter. It looks for:
If it can not find these it will call the original
It will call a sub function which does the actual file verification which will present the following errors:
- File verification failure.
- File verification failure. Could not calculate signature.
- File verification failure. Unknown file not found in manifest.
- File verification failure. File signature does not match manifest.
- File verification failure. File in wrong directory.
- File verification failure. Unknown foreign dll.
- File verification failure. Unknown Unsigned File.
If the verification fails it will call a bunch of unknown methods and end with a call to
Finally it will return the status code
This hook handles checking what libraries attempt to be loaded by the process.
GetCurrentThreadIdand stores that in a data structure (I’m calling TrustStore).
- Does some checks, looks at whether the incoming Library File Name and certain global variables are not null. If there are issues it will
SetLastErrorto 8 (
ERROR_NOT_ENOUGH_MEMORY) and return.
- Finds existing data that matches the library being loaded and returns the previously allocated memory.
This hook just calls the original function; Nothing seems to happen here.
VACnet & VAC Live
This topic is somewhat of a black box. From what I’ve observed, it seems that Valve has enhanced their server-side detection mechanisms, particularly concerning the input data sent from players connected to official Valve matchmaking servers. However, controversy has recently arisen regarding this subject. New articles are emerging, stating that Counter-Strike 2 is banning players for moving their mouse too quickly.
I also came across a post on Valve’s
csgo-osx-linux repository, which claims that tampering with the game’s
m_yaw cvar could trigger a VAC ban. Here is another thread talking about it on Reddit. In typical Valve fashion, they seem to have quietly patched the issue without making any public statements about it.
I plan to continue sharing any intriguing findings here. One task on my to-do list is to revisit VAC and assess whether there have been any notable updates.