Anti-Warden技术之外挂的自我卸载

外挂Anti-warden的方法之一是给warden mod做数字签名,在检测到不安全或者未知的warden mod时,停止工作。对于以注入到游戏进程的方式运行的外挂来说,应该在warden开始执行检测之前自动卸载。这一篇介绍外挂自动卸载技术,也就是说如何在检测到不安全的.mod时把自己卸载掉。可能有人会想,这有什么难的,不就是调用FreeLibrary(假设加载用LoadLibrary)吗。调用FreeLibrary是对的,不过需要一点儿小技巧。如下面代码所示,检测、卸载流程大致如下:

void InitWardenInterfacePatch()
{
if ( ! CheckWardenMod())
{
// unsafewarden.mod!
FreeLibrary(g_hInstDLL);
}
}

InitWardenInterfacePatch的旁路点(detour patch)安装在.mod解压加载完成后、执行检测代码之前,如果检查失败则调用FreeLibrary卸载自身。代码逻辑看上去很合理,但仔细想想,你会发现有点儿问题,就是在FreeLibrary之后,DLL的代码和数据所占用的内存已经被释放了,可是FreeLibrary调用的返回地址是在DLL中,显然这会产生内存访问违例异常致使游戏崩溃!如何解决这个问题呢?一种想法可能是(用VirtualAlloc)动态分配一块内存,把调用FreeLibray的代码拷贝到这块内存里执行,但这又会导致内存泄漏。另外一种想法是在栈里预留一块空间,把这块代码拷贝到预留的栈空间,然后用jmp指令跳到栈中运行。你可以看到,这个问题是有那么点儿麻烦的。

这里介绍另外一种简洁、优雅的做法。有汇编基础的朋友都知道,在x86上调用一个函数用指令call,返回函数则用ret。你可能想不到的是,其实用ret也能调用函数。ret指令的动作,是从栈指针esp指向的内存地址取出一个32位数做为返回地址,然后跳到该地址处继续执行。如果我们把FreeLibrary入栈再执行ret指令,就可以跳到FreeLibrary函数去:

pushFreeLibrary; // functiontocall
ret; // callFreeLibrarytounloadmyself

比较起来,这种方法和用call指令调用函数的不同之处在于,call指令在跳转到目标函数执行之前还有一个动作,就是把call指令的下一条指令地址入栈。在这个例子中就是把FreeLibrary调用的下一条地址-这是我们不希望的。用ret指令可以省去这一副作用。用ret指令调用函数的关键是构造栈上的数据,把合适的参数传递给目标函数,并让目标函数返回时返回到合适的地址继续执行:

void __declspec(naked)InitWardenModPatch_ASM()
{
__asm{
callCheckWardenMod;
testeax,eax;
jzunloadmyself;
ret;
unloadmyself:
popeax;
// returnaddressofFreeLibrary
pushhInstDLL; // parameterofFreeLibrary
pusheax; // returnaddressofFreeLibrary
pushFreeLibrary; // functiontocall
ret; // callFreeLibrarytounloadmyself
}
}

上述代码中,ret指令调用前栈的布局如下:

|hInstDLL | High address
|
return addressofFreeLibrary |
|addressofFreeLibrary | <-- ESP

这只是一个简单的示例,在实际的应用中,栈上数据如何构造还应该结合具体的需要(跟detour patch有关)进行调整。最早提出利用ret调用函数的可能是Gary Nebbett(《Windows NT Native API Reference》的作者),用于程序的自我删除

你可能感兴趣的:(游戏,windows,工作)