前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >绕过EasyAntiCheat完整性检查

绕过EasyAntiCheat完整性检查

原创
作者头像
franket
发布2021-01-05 10:36:41
4.3K0
发布2021-01-05 10:36:41
举报
文章被收录于专栏:技术杂记

作弊者对反作弊自我完整性检查特别感兴趣。如果可以规避它们,则可以有效地修补或“钩住”任何可能导致踢脚甚至禁止的反作弊代码。在EasyAntiCheat的情况下,他们使用内核模式驱动程序,其中包含一些有趣的检测例程。我们将研究它们的完整性检查如何工作以及如何规避它们,从而有效地使我们禁用反作弊。

反转过程

首先要做的实际上是确定是否存在任何类型的完整性检查。最简单的方法是修补其中的任何字节,.text然后看反作弊是否会在一段时间后决定踢您或禁止您。在修补随机函数后大约10至40秒,我被踢了,发现他们确实在内核模块中进行完整性检查。在使用EPT工具[1]的基于虚拟机管理程序的调试器的帮助下,我在由LoadImage通知例程调用的函数上设置了一个内存断点(请参阅PsSetLoadImageNotifyRoutine)。一段时间后,我可以找到他们正在访问内存的位置。

[1] EPT代表扩展页表。它是Intel的一项技术,用于MMU虚拟化支持。

在IDA Pro中检查了外部参照并设置了一些指令断点之后,我发现了从哪里调用完整性检查功能,其中之一在CreateProcess通知例程内部(请参阅PsSetCreateProcessNotifyRoutine)。此例程负责防作弊初始化的某些部分,例如创建将用于表示游戏过程的内部结构。如果EAC发现其内核模块已被篡改,则不会初始化。

完整性检查功能本身被混淆,主要包含垃圾指令,这使得对其进行分析非常烦人。这是混淆代码的示例:

代码语言:javascript
复制
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的帮助下,我编写了一个简单的工具,该工具可以从代码块中反汇编每个指令,并跟踪寄存器的修改。之后,它会根据寄存器使用情况找出哪些指令无效,然后将其删除。输出示例:

代码语言:javascript
复制
mov     [rsp+arg_8], rbx
mov     [rsp+arg_10], rbp
mov     [rsp+arg_18], rsi
mov     r9, cs:EasyAntiCheatBase

是时候扭转这个家伙了!

完整性检查功能

这是完整性检查功能的C ++代码:

代码语言:javascript
复制
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 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 反转过程
  • 完整性检查功能
  • 解决方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档