Hey everyone. In this article, we’ll break down some of the protection mechanisms used in the EasyAntiCheat_EOS driver (Rust-based EAC was used, build date around March 10th of this year).
Based on reverse engineering results, we’ll cover:
- Telemetry collection
- System driver integrity checks
- Report encryption
Telemetry collection
EAC collects information about every running process and sends it to the server. It uses LoadImageNotify as a callback for this.
The anti-cheat collects process telemetry to:
- Identify potentially dangerous processes (known cheats, injectors, etc.)
- Build behavior patterns across different users
- If the game crashes — help determine which process might have caused it
Pseudocode:
void collect_process_telemetry( PEPROCESS process, bool use_extended_info ) {
const auto packet = allocate_pool( 0x330 );
if ( memory::is_valid( packet ) ) {
// Get process information
const auto is_wow64 = PsGetProcessWow64Process( process ) != 0;
const auto pid = PsGetProcessId( process );
const auto process_info = copy_process_info( 0, 0, 0, 0x801, is_wow64, 0, pid, 0 );
// If extended info flag is set, copy the process name into the data buffer
if ( memory::is_valid( process_info ) ) {
if ( use_extended_info ) {
char buffer[ 16 ]{ };
// PsGetProcessImageFileName
if ( get_process_name( process, buffer ) ) {
auto dest = process_info + 0x20;
for ( int idx{ }; idx < 15 && buffer[ idx ]; ++idx )
dest[ idx ] = ( wchar_t )buffer[ idx ];
dest[ 15 ] = 0;
}
}
}
// Fill packet header
*( uint64_t* )packet = process_info; // Pointer to data buffer
*( DWORD* )( packet + 0x28 ) = 1; // Status
*( DWORD* )( packet + 0x30 ) = 0x7429AC76; // Packet type
*( DWORD* )( packet + 0x40 ) = 0; // Flags (?)
*( uint64_t* )( packet + 0x38 ) = 0; // Additional information
// Send packet to server
send_telemetry_packet( packet, 0 );
ex_free_pool_with_tag( process_info );
}
}
- Copy process information ( parse PE headers into required format, copy process name if extended info flag is set )
- Allocate pool, fill with data
- Send to server
System driver integrity checks
EAC iterates through every loaded driver ( retrieved via ZwQuerySystemInformation with SystemModuleInformation flag ), obtains their DriverObject, and checks each MajorFunction, verifying the dispatch address. The anti-cheat does this to detect hooks in system drivers. Hooks are commonly used for spoofers or usermode communication.
bool check_driver_dispatch( PDRIVER_OBJECT driver_object, uint32_t index, void* buffer, uint32_t* status_code ) {
if ( status_code )
*status_code = 0;
if ( !driver_object ) {
if ( status_code )
*status_code = 2; // driver_object not found
return false;
}
const auto major_function = driver_object->MajorFunction[ index ];
if ( !major_function ) {
if ( status_code )
*status_code = 4; // No implementation for this index
return false;
}
const auto base_address = driver_object->DriverStart;
const auto size = driver_object->DriverSize;
if ( !base_address
|| !size ) {
if ( status_code )
*status_code = 5; // Failed to determine driver bounds
return false;
}
bool found{ };
if ( major_function >= base_address && major_function <= ( base_address + size ) ) {
if ( status_code )
*status_code = 7; // Check passed, function not hooked
found = true;
if ( buffer ) {
*( uint32_t* )buffer = major_function - base_address; // Save function RVA
*( uint32_t* )( ( uintptr_t )buffer + 4 ) = size; // Save driver size
memcpy( ( char* )buffer + 8, get_module_name( base_address ), 255 ); // Copy module name
}
} else
if ( status_code )
*status_code = 6; // Function points outside module => DETECTED
return found;
}
- Check if function resides within the module
- Copy driver information
- Send report if status_code != 7 or !found ( report sending is in the function that calls the dispatch check )
Report encryption
EAC generates an encrypted key using values from KUSER_SHARED_DATA. The anti-cheat does this to hide the real information sent to the server, preventing simple emulation.
void create_encrypted_key( ) {
const auto buffer = reinterpret_cast< uint8_t* >( allocate_pool( 0x1000 ) );
if ( !buffer )
return;
// 0x2C4 -> SystemExpirationDate
// 0x14 -> SystemTime
// 0x260 -> BootId
const auto v16 = *( ULONG* ) 0xFFFFF780000002C4 ^ *( ULONG* )0xFFFFF78000000014;
const auto v17 = *( ULONG* )0xFFFFF78000000260 ^ *( ULONG* )0xFFFFF78000000008;
const auto v18 = ( 1533831705 * ( buffer >> 2 ) ) ^ *( LONG* )0xFFFFF78000000018;
const auto v19 = *( LONG* )0xFFFFF7800000000C ^ ( 1533831705 * ( ( unsigned __int64 )&buffer >> 2 ) );
buffer[ 4 ] = v18 ^ 0x97B9BC33;
*buffer = -96604097;
buffer[ 1 ] = -345804981;
buffer[ 5 ] = v17 ^ 0xE220A00D;
buffer[ 2 ] = 144253742;
buffer[ 7 ] = 897887310;
buffer[ 3 ] = v16 ^ 0xA868AC37;
buffer[ 8 ] = -123907290;
buffer[ 6 ] = v19 ^ 0x79BB2376;
/*
v15 = buffer;
v20 = v19 ^ v16 ^ v18 ^ v17 ^ 0x4C53A039;
*/
}
- Generates an encrypted key for further encryption of the report buffer.
Conclusion
With this article, you can learn useful information about the inner workings of EasyAntiCheat. I hope this article was helpful!