作弊者对反作弊自我完整性检查特别感兴趣。如果可以规避它们,则可以有效地修补或“钩住”任何可能导致踢脚甚至禁止的反作弊代码。在EasyAntiCheat的情况下,他们使用内核模式驱动程序,其中包含一些有趣的检测例程。我们将研究它们的完整性检查如何工作以及如何规避它们,从而有效地使我们禁用反作弊。
首先要做的实际上是确定是否存在任何类型的完整性检查。最简单的方法是修补其中的任何字节,.text
然后看反作弊是否会在一段时间后决定踢您或禁止您。在修补随机函数后大约10至40秒,我被踢了,发现他们确实在内核模块中进行完整性检查。在使用EPT工具[1]的基于虚拟机管理程序的调试器的帮助下,我在由LoadImage通知例程调用的函数上设置了一个内存断点(请参阅PsSetLoadImageNotifyRoutine)。一段时间后,我可以找到他们正在访问内存的位置。
[1] EPT代表扩展页表。它是Intel的一项技术,用于MMU虚拟化支持。
在IDA Pro中检查了外部参照并设置了一些指令断点之后,我发现了从哪里调用完整性检查功能,其中之一在CreateProcess通知例程内部(请参阅PsSetCreateProcessNotifyRoutine)。此例程负责防作弊初始化的某些部分,例如创建将用于表示游戏过程的内部结构。如果EAC发现其内核模块已被篡改,则不会初始化。
完整性检查功能本身被混淆,主要包含垃圾指令,这使得对其进行分析非常烦人。这是混淆代码的示例:
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
在公共反汇编框架Capstone的帮助下,我编写了一个简单的工具,该工具可以从代码块中反汇编每个指令,并跟踪寄存器的修改。之后,它会根据寄存器使用情况找出哪些指令无效,然后将其删除。输出示例:
mov [rsp+arg_8], rbx
mov [rsp+arg_10], rbp
mov [rsp+arg_18], rsi
mov r9, cs:EasyAntiCheatBase
是时候扭转这个家伙了!
这是完整性检查功能的C ++代码:
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;
}
如您所见,EAC分配了一个池并制作了自己的副本(您可以自己检查),以用于其完整性检查。它将EAC.sys中的字节与其副本进行比较,然后查看两者是否匹配。如果修补了模块,它将返回false。
由于完整性检查功能被混淆,找到它会很烦人,因为它可能在发行版之间进行更改。希望简化旁路,我开始集思广益一些替代解决方案。
本.pdata
节包含功能表条目的数组,这些功能表条目对于异常处理而言是必需的。由于函数本身的语义不太可能更改,因此我们可以利用此信息!
为了使解决方案更整洁,我们需要打补丁EasyAntiCheat.sys
及其副本以禁用完整性检查。要查找包含副本的池,我们可以使用未记录的API ZwQuerySystemInformation并将SystemBigPoolInformation(0x42)用作第一个参数。调用成功后,它将返回SYSTEM_BIGPOOL_INFORMATION结构,其中包含SYSTEM_BIGPOOL_ENTRY结构的数组以及该数组中返回的元素数。该SYSTEM_BIGPOOL_ENTRY结构包含有关池本身的信息,例如池标记,基数和大小。使用此信息,我们可以找到由EAC分配的池并修改其内容,从而使我们能够不受阻碍地修补任何EAC代码而不会触发完整性违规。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。