Information

November 4, 2023 ยท View on GitHub

Cheaters are particularly interested in anti-cheat self-integrity checks. If they can be circumvented, they can be effectively patched or "hooked" on any anti-cheat code that could lead to kicking or even banning. In the case of EasyAntiCheat, they use kernel-mode drivers that contain some interesting detection routines. We will look at how their integrity checks work and how to circumvent them, effectively allowing us to disable anti-cheat.

Reverse the process

About 10 to 40 seconds after patching the random function, I got kicked and found out that they do integrity check in the kernel module. With the help of a hypervisor-based debugger, I set a memory breakpoint on a function called by the LoadImage notification routine. After a while, I can find out where they are accessing the memory.

After checking the xrefs in IDA Pro and setting some instruction breakpoints, I found out where to call the integrity check features, one of which is inside the CreateProcess notification routine. This routine is responsible for certain parts of cheat-proof initialization, such as creating an internal structure that will be used to represent the gameplay. If EAC finds that its kernel module has been tampered with, it will not initialize.

The integrity check feature itself is obfuscated and mostly contains garbage instructions, which makes it very annoying to analyze it. Here's an example of obfuscated code:

mov [rsp+arg_8], rbx
ror r9w, 2
lea r9, ds:588F66C5h[rdx*4]
sar r9d, cl
bts r9, 1Fh
mov [rsp+arg_10], rbp
lea r9, ds:0FFFFFFFFC17008A9h[rsi*2]
sbb r9d, 2003FCE1h
shrd r9w, cx, cl
shl r9w, cl
mov [rsp+arg_18], rsi
cmc
mov r9, cs:EasyAntiCheatBase

With the help of Capstone, a public disassembly framework, I wrote a simple tool that can disassemble each instruction from a block of code and track changes to the registers. After that, it finds out which instructions are invalid based on register usage and then removes them. Example output:

mov [rsp+arg_8], rbx
mov [rsp+arg_10], rbp
mov [rsp+arg_18], rsi
mov r9, cs:EasyAntiCheatBase

Integrity check function

Here's the C++ code for the integrity check feature:

bool check_driver_integrity()
{
if ( !peac_base || !eac_size || !peac_driver_copy_base || !peac_copy_nt_headers )
return false;

bool not_modified = true;

const auto num_sections = peac_copy_nt_headers->FileHeader.NumberOfSections;
const auto* psection_headers = IMAGE_FIRST_SECTION( peac_copy_nt_headers );

// Loop through all sections from EasyAntiCheat.sys
for ( WORD i = 0; i < num_sections; ++i )
{
const auto characteristics = psection_headers[ i ].Characteristics;

// Ignore paged sections
if ( psection_headers[ i ].SizeOfRawData != 0 && READABLE_NOT_PAGED_SECTION( characteristics ) )
{
// Skip .rdata and writable sections
if ( !WRITABLE_SECTION( characteristics ) && ( *reinterpret_cast< ULONG* >( psection_headers[ i ].Name ) != 'adr.' ) )
{
auto psection = reinterpret_cast< const void* >( peac_base + psection_headers[ i ].VirtualAddress );
auto psection_copy = reinterpret_cast< const void* >( peac_driver_copy_base + psection_headers[ i ].VirtualAddress );

const auto virtual_size = psection_headers[ i ].VirtualSize & 0xFFFFFFF0;

// Compare the original section with its copy
if ( memcmp( psection, psection_copy, virtual_size ) != 0 )
{
// Uh oh
not_modified = false;
break;
}
}
}
}

return not_modified;
}

As you can see, the EAC allocates a pool and makes its own copy (which you can check yourself) for its integrity check. It compares the bytes in the EAC .sys to its copy and sees if the two match. If the module is patched, it will return false.

Resolution

Since the integrity check feature is obfuscated, it will be annoying to find it as it may change between releases. Hoping to simplify the bypass, I started brainstorming some alternative solutions.

This section contains an array of feature table entries that are required for exception handling. Since the semantics of the function itself are unlikely to change, we can take advantage of this information!

To make the solution tidier, we need to patch and its copy to disable integrity checking. To find a pool that contains replicas, we can use the undocumented API ZwQuerySystemInformation and use SystemBigPoolInformation(0x42) as the first parameter. When the call is successful, it will return a SYSTEM_BIGPOOL_INFORMATION structure that contains an array of SYSTEM_BIGPOOL_ENTRY structures and the number of elements returned in that array. The SYSTEM_BIGPOOL_ENTRY structure contains information about the pool itself, such as pool tags, cardinality, and size. Using this information, we can find the pool allocated by the EAC and modify its contents, allowing us to patch any EAC code without hindrance without triggering an integrity breach.

Notes

Original Post: https://www.unknowncheats.me/forum/anti-cheat-bypass/609412-bypass-easyanticheat-integrity-checks.html