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

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

void  InitWardenInterfacePatch()
{
    
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函数去:

push FreeLibrary;  //  function to call
ret;  //  call FreeLibrary to unload myself 

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

void  __declspec(naked) InitWardenModPatch_ASM()
{
    __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指令调用前栈的布局如下:

|  hInstDLL                                      |  High address
|  
return  address of FreeLibrary  |
|  address of FreeLibrary             |  <-- ESP

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

你可能感兴趣的:(ant)