外挂Anti-warden的方法之一是给warden mod做数字签名,在检测到不安全或者未知的warden mod时,停止工作。对于以注入到游戏进程的方式运行的外挂来说,应该在warden开始执行检测之前自动卸载。这一篇介绍外挂自动卸载技术,也就是说如 何在检测到不安全的.mod时把自己卸载掉。可能有人会想,这有什么难的,不就是调用FreeLibrary(假设加载用LoadLibrary)吗。调 用FreeLibrary是对的,不过需要一点儿小技巧。如下面代码所示,检测、卸载流程大致如下:
{
if (!CheckWardenMod())
{
// unsafe warden .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函数去:
![](http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif)
![](http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif)
比较起来,这种方法和用call指令调用函数的不同之处在于,call指令在跳转到目标函数执行之前还有一个动作,就是把call指令的下一条指令 地址入栈。在这个例子中就是把FreeLibrary调用的下一条地址-这是我们不希望的。用ret指令可以省去这一副作用。用ret指令调用函数的关键 是构造栈上的数据,把合适的参数传递给目标函数,并让目标函数返回时返回到合适的地址继续执行:
{
__asm {
call CheckWardenMod;
test eax, eax;
jz unloadmyself;
ret;
unloadmyself:
pop eax; // return address of FreeLibrary
push hInstDLL; // parameter of FreeLibrary
push eax; // return address of FreeLibrary
push FreeLibrary; // function to call
ret; // call FreeLibrary to unload myself
}
}
上述代码中,ret指令调用前栈的布局如下:
| return address of FreeLibrary |
| address of FreeLibrary | <-- ESP
这只是一个简单的示例,在实际的应用中,栈上数据如何构造还应该结合具体的需要(跟detour patch有关)进行调整。最早提出利用ret调用函数的可能是Gary Nebbett(《Windows NT Native API Reference》的作者),用于程序的自我删除。