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.dll
loaded into SteamService.exe
(or Steam.exe
if run as admin) prepares and runs anti-cheat modules. Client VAC infrastructure is built using C++
(indicated by many thiscall
convention functions present in disassembly) but this repo contains C
code 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 VacProcessMonitor data 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:
NtQuerySystemInformation
. Strings are xor-ed with ^
or >
or &
char.This module is loaded first and sometimes even before any VAC-secured game is launched.
At first module invokes GetVersion
function to retrieve major and build system version e.g 0x47BB0A00
- which means:
18363
)10
)The module calls GetNativeSystemInfo
function and reads fields from resultant SYSTEM_INFO
struct:
Then it calls NtQuerySystemInformation
API function with following SystemInformationClass
values (in order they appear in code):
SYSTEM_TIMEOFDAY_INFORMATION
struct, VAC uses two fields:
SYSTEM_CODEINTEGRITY_INFORMATION
, module saves CodeIntegrityOptions
fieldSYSTEM_DEVICE_INFORMATION
, module saves NumberOfDisks
fieldSYSTEM_KERNEL_DEBUGGER_INFORMATION
, VAC uses whole structSYSTEM_BOOT_ENVIRONMENT_INFORMATION
, VAC copies BootIdentifier
GUIDSYSTEM_RANGE_START_INFORMATION
which 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 LARGEADDRESSAWARE
option)For more information about SYSTEM_INFORMATION_CLASS
enum see Geoff Chappell's page.
Next, anti-cheat calls GetProcessImageFileNameA
function 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 GetWindowsDirectoryW
API.
Module reads NtDll.dll
file 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:
Next it enumerates volumes using FindFirstVolumeW
/ FindNextVolumeW
API. VAC queries volume information by calling GetVolumeInformationW
, GetDriveTypeW
and GetVolumePathNamesForVolumeNameW
functions 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 OpenProcess
API).
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 new
or 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 methods
of 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:%000008X
filemapping. The mapping has following layout:
struct VacProcessMonitorMapping {
DWORD magic; // when initialized - 0x30004
PVOID vacProcessMonitor;
}; // sizeof(VacProcessMonitorMapping) == 8
VacProcessMonitorMapping::vacProcessMonitor
is a pointer to the VacProcessMonitor
object (size of which is 292 bytes
).
VAC then reads the whole VacProcessMonitor
object (292 bytes) and its VMT (Virtual Method Table) containing pointers to 6
methods (24 bytes).
The base address of steamservice.dll
is also gathered.
These data are probably used on VAC servers to detect hooking VacProcessMonitor
. The procedure may be following:
if (method_ptr & 0xFFFF0000 != steamservice_base)
hook_detected();