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》的作者),用于程序的自我删除

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用gdb调试时,可以使用commands命令来设置断点,并指定在每次到达该断点时要执行的一组命令。该命令的使用方法如下: 1. 首先,使用gdb启动程序,例如:gdb ./gdbdebug 2. 接下来,使用break命令设置一个断点,例如:b abc.cpp:10 3. 然后,使用commands命令将以下命令添加到指定的断点上: commands breakpoint-number 命令1 命令2 ... end 这里的breakpoint-number是断点号,表示将以下命令添加到指定的断点上。可以添加任何有效的GDB命令,每行一个命令,以end结束。 例如,假设我们有以下示例代码: ```cpp using namespace std; void func1() // 行号为10 { cout << "before" <<endl; } int main() { func1(); return 0; } ``` 我们可以使用以下命令来设置断点并指定commands: ```bash gdb a.out b abc.cpp:10 commands 1 silent prints "after" return c end run ``` 这样,在运行程序时,当程序执行到这个断点时,GDB会执行commands中指定的命令列表,然后继续执行程序。在这个例子中,commands命令会将输出修改为"after",然后继续执行程序,最终输出结果为"after"。 参考资料: 1. [GDB]断点(breakpoint)命令列表:commands、silent 2. 书籍《软件调试的艺术.pdf》2.11节 3. gdb commands命令用法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [GDG调试技巧之命令列表(commands)](https://blog.csdn.net/warden007/article/details/82888035)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [GDB Commands用法](https://blog.csdn.net/qq_33726635/article/details/117199722)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值