Source code of Valve Anti-Cheat obtained from disassembly of compiled modules
This repository contains parts of source code of Valve Anti-Cheat for Windows systems recreated from machine code.
Valve Anti-Cheat (VAC) is user-mode noninvasive anti-cheat system developed by Valve. It is delivered in form of modules (dlls) streamed from the remote server.
steamservice.dllloaded into
SteamService.exe(or
Steam.exeif run as admin) prepares and runs anti-cheat modules. Client VAC infrastructure is built using
C++(indicated by many
thiscallconvention functions present in disassembly) but this repo contains
Ccode for simplicity. Anti-cheat binaries are currently
32-bit.
| ID | Purpose | .text section raw size | Source folder |
| --- | --- | --- | --- |
| 1 | Collect information about system configuration.
This module is loaded first and sometimes even before any VAC-secured game is launched. | 0x5C00 | Modules/SystemInfo
| 2 | Enumerate running processes and handles.
This module is loaded shortly after game is launched but also repeatedly later. | 0x4A00 | Modules/ProcessHandleList
| 3 | Collect
VacProcessMonitordata from filemapping created by
steamservice.dll. It's the first module observed to use
virtual methods (polymorphism). | 0x6600 | Modules/ProcessMonitor
VAC uses several encryption / hashing methods: - MD5 - hashing data read from process memory - ICE - decryption of imported functions names and encryption of scan results - CRC32 - hashing table of WinAPI functions addresses - Xor - encryption of function names on stack, e.g
NtQuerySystemInformation. Strings are xor-ed with
^or
>or
&char.
This module is loaded first and sometimes even before any VAC-secured game is launched.
GetVersionfunction to retrieve major and build system version e.g
0x47BB0A00- which means: - 0x47BB - build version (decimal
18363) - 0x0A00 - major version (decimal
10)
The module calls
GetNativeSystemInfofunction and reads fields from resultant
SYSTEM_INFOstruct: - wProcessorArchitecture - dwProcessorType
NtQuerySystemInformationAPI function with following
SystemInformationClassvalues (in order they appear in code): - SystemTimeOfDayInformation - returns undocumented
SYSTEM_TIMEOFDAY_INFORMATIONstruct, VAC uses two fields: - LARGEINTEGER CurrentTime - LARGEINTEGER BootTime - SystemCodeIntegrityInformation - returns
SYSTEM_CODEINTEGRITY_INFORMATION, module saves
CodeIntegrityOptionsfield - SystemDeviceInformation - returns
SYSTEM_DEVICE_INFORMATION, module saves
NumberOfDisksfield - SystemKernelDebuggerInformation - returns
SYSTEM_KERNEL_DEBUGGER_INFORMATION, VAC uses whole struct - SystemBootEnvironmentInformation - returns
SYSTEM_BOOT_ENVIRONMENT_INFORMATION, VAC copies
BootIdentifierGUID - SystemRangeStartInformation - returns
SYSTEM_RANGE_START_INFORMATIONwhich is just
void*. Anti-cheat saves returned kernel space start address and sign bit of that address (to check if executable inside which VAC is running is linked with
LARGEADDRESSAWAREoption)
For more information about
SYSTEM_INFORMATION_CLASSenum see Geoff Chappell's page.
GetProcessImageFileNameAfunction to retrieve path of current executable and reads last 36 characters (e.g.
\Program Files (x86)\Steam\Steam.exe).
Later VAC retrieves system directory path (e.g
C:\WINDOWS\system32) using
GetSystemDirectoryW, converts it from wide-char to multibyte string, and stores it (max length of multibyte string - 200). Anti-cheat queries folder FileID (using
GetFileInformationByHandleEx) and volume serial number (
GetVolumeInformationByHandleW). Further it does the same with windows directory got from
GetWindowsDirectoryWAPI.
Module reads
NtDll.dllfile from system directory and does some processing on it (not reversed yet).
VAC saves handles (base addresses) of imported system dlls (max 16, this VAC module loads 12 dlls) and pointers to WINAPI functions (max 160, module uses 172 functions). This is done to detect import address table hooking on anti-cheat module, if function address is lower than corresponding module base, function has been hooked.
Anti-cheat gets self module base by performing bitwise and on return address (
_ReturnAddress() & 0xFFFF0000). Then it collects: - module base address - first four bytes at module base address (from DOS header) - DWORD at module base + 0x114 - DWORD at module base + 0x400 (start of .text section)
Next it enumerates volumes using
FindFirstVolumeW/
FindNextVolumeWAPI. VAC queries volume information by calling
GetVolumeInformationW,
GetDriveTypeWand
GetVolumePathNamesForVolumeNameWfunctions and fills following struct with collected data:
struct VolumeData { UINT volumeGuidHash; DWORD getVolumeInformationError; DWORD fileSystemFlags; DWORD volumeSerialNumber; UINT volumeNameHash; UINT fileSystemNameHash; WORD driveType; WORD volumePathNameLength; DWORD volumePathNameHash; }; // sizeof(VolumeData) == 32
VAC gathers data of max. 10 volumes.
If this module was streamed after VAC-secured game had started, it attemps to get handle to the game process (using
OpenProcessAPI).
Eventually, module encrypts data (2048 bytes), DWORD by DWORD XORing with key received from server (e.g 0x1D4855D3)
To be disclosed...
This module seems to be relatively
newor was disabled for a long time. First time I saw this module in
January 2020. It has an ability to perform many different types of scans (currently
3). Further scans depends on the results of previous ones.
Each scan type implements
four methodsof a base class.
Initially VAC server instructs client to perform
scan #1.
First scan function attemps to open
Steam_{E9FD3C51-9B58-4DA0-962C-734882B19273}_Pid:%000008Xfilemapping. The mapping has following layout:
struct VacProcessMonitorMapping { DWORD magic; // when initialized - 0x30004 PVOID vacProcessMonitor; }; // sizeof(VacProcessMonitorMapping) == 8
VacProcessMonitorMapping::vacProcessMonitoris a pointer to the
VacProcessMonitorobject (size of which is
292 bytes).
VAC then reads the whole
VacProcessMonitorobject (292 bytes) and its VMT (Virtual Method Table) containing pointers to
6methods (24 bytes). The base address of
steamservice.dllis also gathered.
These data are probably used on VAC servers to detect hooking
VacProcessMonitor. The procedure may be following:
cpp if (method_ptr & 0xFFFF0000 != steamservice_base) hook_detected();