这事缘起于dota,前几天和同学在某vx平台dota,被一开挂黑店虐得不行。一气之下也找来个war3的挂,想报仇雪恨。结果游戏刚启动就被平台踢出来了,看来偷懒是不行的了,网上google了一番,该平台的反外挂主要基于黑名单和运行时内存检测。
黑名单就不说了,运行时内存检测是比较致命的,因为开图必然要改程序流程,改流程免不了要打内存补丁,打了补丁又不想被发现也是有办法的,比如shadowwalk,这是刚学到的新名词,详见http://bbs.pediy.com/showthread.php?t=58896
该方法简述就是模块重载,让读取和执行分离开来,不过要做的工作实在太多,而且不稳定,总之我真心认为硬碰硬不是好办法,也不是长久之计。其实简单的改改流程不被发现可以用异常处理来做,很安全稳定。
先复习一下异常处理流程,在没有调试器的情况下,用户层异常的分发由KiUserExceptionDispatcher完成,其中再调用RtlDispatchException分发异常,RtlDispatchException中首先RtlCallVectoredExceptionHandlers执行veh,如果此异常被处理则不再继续分发,未处理则展开seh链依次RtlExecuteHandlerForException执行seh,同样的,如果被处理则不再分发,否则继续,如果都没有处理则进程崩溃。这么看来只要抢在游戏本身seh之前捕获并处理异常游戏肯定是不会发现的。
简单说下怎么做,AddVectoredExceptionHandler给进程装上一个向量异常处理handler,然后在将drx寄存器设置为要打补丁的位置,也就是在补丁位置下一个硬件执行断点,执行到此处时抛出单步异常,异常处理捕获异常,在handler里模拟补丁功能。异常处理函数被调用时会传进来线程context指针,所以可以干的事情很多很多,这里可以发挥一下想象力。
就拿war3来举例吧,游戏就不用调了,网上找个开图工具,开图进游戏,扫扫进程钩子可以发现游戏被patch了两个地方
0x6f361f7c 0x01 - 0x00 0x6f3a20dd 0x75 - 0xeb
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);
SetHwBreakpoint();
}
case DLL_THREAD_ATTACH:
{
SetHwBreakpoint();
}
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void SetHwBreakpoint()
{
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(GetCurrentThread(), &ctx);
ctx.Dr0 = 0x6f3a20dd;
ctx.Dr1 = 0x6f361f7b;
ctx.Dr7 = 0x405;
SetThreadContext(GetCurrentThread(), &ctx);
}
DWORD NTAPI ExceptionHandler(EXCEPTION_POINTERS * ExceptionInfo)
{
if ((DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress == 0x6f3a20dd)
{
//直接改eip模拟jmp
ExceptionInfo->ContextRecord->Eip += 0x34;
return EXCEPTION_CONTINUE_EXECUTION;
}
else if ((DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress == 0x6f361f7b)
{
//直接设eax为零模拟mov eax,0
ExceptionInfo->ContextRecord->Eax = 0;
ExceptionInfo->ContextRecord->Eip += 5;
return EXCEPTION_CONTINUE_EXECUTION;
}
else
{
//在异常handler里重设drx防止断点被意外清除
ExceptionInfo->ContextRecord->Dr0 = 0x6f3a20dd;
ExceptionInfo->ContextRecord->Dr1 = 0x6f361f7b;
ExceptionInfo->ContextRecord->Dr7 = 0x405;
return EXCEPTION_CONTINUE_SEARCH;
}
}
让游戏启动时候加载这个dll就生效了,这样不费一兵一卒,不动一草一木,便完成了需要打补丁才能完成的工作,对于进程本身而言只是多了一个dll和drx寄存器被使用,别的什么都没动。唯一的缺陷是drx寄存器只有4个,也就是用这方法理论上只能最多支持4个补丁位置,其实还有其他方法,搞清楚了程序流程断点可以来回设置,还是很强大的。
上述代码只是演示性质的,所以地址硬编码了,而且仅适用于1.24e,别的版本没试,本来也不是真心想作弊,就是试试新方法而已,有兴趣的自己去搞吧
要注意的就是异常handler里不能有任何可能产生异常风险的代码,否则死循环了
附件是一个改了导入表的storm.dll和异常处理dll,将这两个放游戏目录下就可以玩了