Time:2020.12.9
game:apex_legends

//在上一个EAC更新后 他们开始使用 VEH+PAGE_GUARD+SINGLE_STEP 来检查游戏中 是否存在未知的作弊内容
**目前仅一种检测类型:
修改Direct Virtual Function Table为 PAGE_READONLY | PAGE_GUARD 页面大小为0x1000 使其调用Dx虚表函数时进入它的例程 然后 使用TF重新修改页面权限 为下次拿到执行流 做准备.
这是一种不同于BattlEye的另一种利用VEH例程来检测作弊的方式.
**
//理论上 他们可以使用这种方法 监控任意的虚表.
//此例程 经过大量Vmp 还未逆向完成.

**//大致执行是
//VirtualProtect(xxxx)
//VEH XxOO [STATUS_GUARD_PAGE_VIOLATION] Set Tf
//VEH XxOO [STATUS_SINGLE_STEP] Clear Tf

**
搜集在哪些地方调用了该虚表
搜集是否在安全的虚表调用范围内.
搜集当前调用再哪些线程中.
检查虚表是否是真实的 防止 替换虚表的挂钩.
**
**

//伪代码类似
VOID Start_DirectVirtualHook_AndCall_Detection(){

        PVOID k = AddVectoredExceptionHandler(0x0,          VmProtect_EasyAntiCheat_Exception_Handler);
    VirtualProtect(Dx_VirtualTable);

}


//由于原始的函数经过VMP 大致手动分析下例程
LONG CALLBACK VmProtect_EasyAntiCheat_Exception_Handler(PEXCEPTION_POINTERS exception)
{
     #define TRAPFLAGS 0x100

    typedef struct _eAc_Safe_List {
        struct _eAc_Safe_List*  next;//下一个 0x0
        struct _eAc_Safe_List* prev;//上一个 0x8
        struct _eAc_Safe_List* unk;//此处也是指向一个和该结构一样的内存的地址//0x10
        uintptr_t Valid;//标志位0x18 0x1意味着 还有下面还有有效的 0x101意味着 这已经是链表的结尾了
        PVOID pSafeVirtual_Table;//此处就是保存的安全内存地址//0x20

    }eAc_Safe_List,*peAc_Safe_List;
    //首先修改页面权限 拿到执行流
    if (exception->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {

        //此处一旦读取后会触发 该异常 然后PAGE_GUARD会自行抹去  所以 不需要 再次调用 NtProtectVirtualMemory
        //获取发生异常的目标内存地址 必然目标地址是Dx虚表范围内的.
        PVOID Target_Exception = (PVOID)exception->ExceptionRecord->ExceptionInformation[1];

        //对齐一波
        PVOID Dx_Api_VirtualTable = (PVOID)((uintptr_t)Target_Exception & 0xFFFFFFFFFFFFF000);

        //然后EAC使用了一个链表用以简单查询该表是否位于它安全区间的任何一个.

        peAc_Safe_List pEasyAntiCheat_ProtectDx_VirtualTable = 0x0;
        //mov     rax, cs:qword_E27C8
        // add     rax, 10h
        //
        pEasyAntiCheat_ProtectDx_VirtualTable = (peAc_Safe_List)((PCHAR)pEasyAntiCheat_ProtectDx_VirtualTable + 0x10);

        for (peAc_Safe_List i = pEasyAntiCheat_ProtectDx_VirtualTable;i->Valid != 0x101; i = i->next)
        {

            if (i->pSafeVirtual_Table== Dx_Api_VirtualTable)
            {

                //.........未完待续

            }


        }
        RtlEnterCriticalSection();//
        PVOID _Exception_addr = (PVOID)exception->ContextRecord->Rip;
        PVOID align_Exception_addr = (PVOID)(exception->ContextRecord->Rip & 0xFFFFFFFFFFFFF000);

        //查询方式 和上面的很类似 都是一张表.
        if (IsInSafeExecAddressRange(pCheckTable,out, align_Exception_addr))
        {
            //....
        }
        RtlLeaveCriticalSection();

        RtlEnterCriticalSection();//
        //
        if (unknowThreadID(pCheckTable, out, VmpGetCurrentThreadId()))
        {
            //....
        }

        RtlLeaveCriticalSection();






        //.....


        exception->ContextRecord->ContextFlags |= TRAPFLAGS;//Set Tf
        //.......未完待续


        return EXCEPTION_CONTINUE_EXECUTION;
    }
    else if (exception->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {

        //主要的检查貌似是在STATUS_GUARD_PAGE_VIOLATION 中 这里只是负责 恢复页面权限.
        //.....
        //....
        //.....
        PVOID Dx_Api_VirtualTable = 0X0;//指向Dx 虚表
        DWORD Size = 0x1000;
        DWORD old;
        NtProtectVirtualMemory(&Dx_Api_VirtualTable, &Size, PAGE_READONLY | PAGE_GUARD, &old);//用于再次拿到执行流.

        exception->ExceptionRecord->ExceptionCode = 0x406D1388;

        exception->ContextRecord->ContextFlags &=~ TRAPFLAGS;//Clear Tf
        // 处理该异常 
        return EXCEPTION_CONTINUE_EXECUTION;
    }



    return EXCEPTION_CONTINUE_SEARCH;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据