EasyAntiCheat UserMode AntiCheat Exception Handler
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;
}
我差点都以为你不管这个blog了 谁知道三天前你竟然更新了
只是没以前那么多时间去更新啦~~