一次美丽的误会引发对函数调用保护的思考

2019-09-28 10:08:52 +08:00
 anhkgg

很久没碰 wx 了,最近想写个东西,就重新拿了起来,最新版本 2.6.8.65 (此时已经 2.6.8.68 )。

找到以前分析过的发送文本消息接口,发现函数大变样,很明显的 vm 痕迹。

.vmp0:1131CE33 000                 push    2493AC03h
.vmp0:1131CE38 004                 call    sub_1134AEB3
.vmp0:1131CE3D 000                 mov     cx, [ebp+0]
.vmp0:1131CE42 000                 test    bp, 373Dh
.vmp0:1131CE47 000                 shl     ah, cl
.vmp0:1131CE49 000                 mov     dx, [ebp+2]
.vmp0:1131CE4E 000                 cmovnb  eax, edi
.vmp0:1131CE51 000                 lea     ebp, [ebp-2]
...
.vmp0:1131CE9C                     bswap   eax
.vmp0:1131CE9E                     inc     eax

当时也没在意,仔细看接口参数并没有变化,就直接拿来用了。

结果发现接口不能用了,并没有成功发送文本信息。

擦,难道 vm 里面藏了什么玄机,做了防止函数调用的保护??

...

正整备大干一场的时候,重新测试给别人发送消息是 ok 的。

这是一次美丽的误会,测试时是给自己的微信发送消息,结果证明该接口是不能给自己发的,所以没成功。

...

然后就继续说说先前自以为的 wx 在函数中可能做的防止调用的保护吧。

按照自己思考的防止别人调用函数的思路,其实就是检查调用源,那么肯定是从调用栈入手:

  1. 在函数内部回溯调用堆栈,检查返回地址
  2. 返回地址为微信模块则正常调用,否则拒绝执行
  3. 可能检查一层( wechatwin.dll ),或者多层
  4. 可能检测返回地址在模块范围,或者是准确的返回地址
  5. vm 相关逻辑,增加分析难度

大概实现代码就是:

void TestAntiCall(DWORD a1)
{
//vmstart
    DWORD retAddr = *((DWORD*)((char*)&a1 - 4));//
    if(retAddr > wxModuleBase && retAddr < wxModuleEnd) {
      //do things
    } else {
       //anti
      //do nothing
    }
//vmend
}

所以能够想到的对抗方式就是在调用 TestAntiCall 的时候,修改调用栈返回地址,让 TestAntiCall 误以为确实是正常调用。

这里分析只考虑检查一层返回地址。

比如如下正常调用代码,00003 就是返回地址,在合法模块内,即可正常调用。

//正常调用代码
void Right_TestAntiCall()
{
00001 push a1
00002 call TestAntiCall
00003 add esp, 4
}

而我的调用 TestAntiCall 函数(在我的模块内)如下,add esp, 4;为 TestAntiCall 拿到的返回地址,这个地址肯定在我的模块内,调用失败。

pfnTestAntiCall = 原始 TestAntiCall 地址;
pfnTestAntiCall_RetAddr = 000003;//调用 TestAntiCall 返回地址
//这个会失败
void MyTestAntiCall(DWORD a1)
{
 __asm {
    push a1;
    call pfnTestAntiCall;
    add esp, 4; //返回地址
  }
}

然后尝试欺骗TestAntiCall,我们修改一下调用栈的返回地址(本来应该是 MyRetAddr )。

通过push+jmp来替换通常的call,这样返回地址由我们自己压入,这里压入正常调用的返回地址g_SendTextMsgRetAddr

//这个会成功
void MyTestAntiCall(DWORD a1)
{
    __asm {
        push a1;
        push g_SendTextMsgRetAddr;//压入原始 retaddr
        jmp pfnWxSendTextMsg; //调用函数,这样函数内部检测就是正常的
        add esp, 4; //MyRetAddr
    }
}

当然,就这么简单的调用,肯定会出问题的,因为jmp pfnWxSendTextMsg之后,就会返回到Right_TestAntiCall00003,如此显然导致栈破坏,会出现崩溃。

所以为了让程序正常执行,还需要多两个处理步骤。

  1. Right_TestAntiCall的 00003 处修改指令为 jmp MyRetAddr。让执行流返回到 MyTestAntiCall1
  2. 恢复 00003 处原始指令。
//1. `Right_TestAntiCall`的 00003 处修改指令为 jmp MyRetAddr。让执行流返回到 MyTestAntiCall1
void fakeAntiTestCall(DWORD retaddr1, DWORD retaddr2, char OrigCode[5])
{
    DWORD MyRetAddr = retaddr1 - 24;
    DWORD ShellCode[5] = { 0xe9, 0x00, 0x00, 0x00, 0x00 };
    *((DWORD*)(&ShellCode[1])) = MyRetAddr;
    memcpy(OrigCode, (char*)retaddr2, 5);
    Patch((PVOID)retaddr2, 5, ShellCode);
}

//2. 恢复 00003 处原始指令。
void fakeAntiTestCall1(DWORD retaddr2, char OrigCode[5])
{
    Patch((PVOID)retaddr2, 5, OrigCode);
}

//这个会成功
void MyTestAntiCall(DWORD a1)
{
    DWORD MyRetAddr = 0;
    char OrigCode[5] = { 0 };
    __asm {
        jmp RET1;
    INIT:
        pop eax;//retAddr
        mov MyRetAddr, eax;
        lea eax, OrigCode;
        push eax;
        push g_SendTextMsgRetAddr;
        push MyRetAddr;
        call fakeAntiTestCall; //在原始 g_SendTextMsgRetAddr 处跳入 MyTestAntiCall1 的 MyRetAddr
        push a1;
        push g_SendTextMsgRetAddr;//压入原始 retaddr
        jmp pfnWxSendTextMsg; //调用函数,这样函数内部检测就是正常的
        add esp, 4; //MyRetAddr
        lea eax, OrigCode;
        push eax;
        push g_SendTextMsgRetAddr;
        call fakeAntiTestCall1;//恢复 g_SendTextMsgRetAddr 数据
        ret;
    RET1:
        call INIT;
        nop;
    }
}

为了拿到 MyRetAddr 的地址,通过 call+pop 的方法完成,如下:

__asm {
    jmp RET1:
    WORK:
        pop eax; //eax = retaddr
        mov retaddr, eax;
        //do thing
        add esp, 4;//MyRetAddr
    RET1:
        call WORK;//push retaddr; jmp WORK;
        nop;//retaddr
}

上面拿到 retaddr 和 MyRetAddr 明显不是同一个,所以在fakeAntiTestCall中减去一个偏移 24 拿到MyRetAddr

偏移值通过下面的字节码可以计算出来10024E1E - 10024E06 = 24。

.text:10024DDF EB 37                             jmp     short RET1
.text:10024DE1                   INIT:   
.text:10024DE1 58                                pop     eax
.text:10024DE2 89 45 F4                          mov     MyRetAddr, eax
.text:10024DE5 8D 45 F8                          lea     eax, OrigCode
.text:10024DE8 50                                push    eax
.text:10024DE9 FF 35 00 D0 25 10                 push    pfnTestAntiCall_RetAddr
.text:10024DEF FF 75 F4                          push    MyRetAddr
.text:10024DF2 E8 C9 00 00 00                    call    fakeAntiTestCall; 
.text:10024DF7 FF 75 E0                          push    a1
.text:10024DFA FF 35 00 D0 25 10                 push    pfnTestAntiCall_RetAddr
.text:10024E00 FF 25 D4 A4 28 10                 jmp     pfnTestAntiCall; 
.text:10024E06 83 C4 04                          add     esp, 4
.text:10024E09 8D 45 F8                          lea     eax, OrigCode
.text:10024E0C 50                                push    eax
.text:10024E0D FF 35 00 D0 25 10                 push    MyRetAddr
.text:10024E13 E8 88 00 00 00                    call    fakeAntiTestCall1; 
.text:10024E14 C3                                ret;
.text:10024E19
.text:10024E19                   RET1:    
.text:10024E19 E8 C4 FF FF FF                    call    INIT
.text:10024E1E 90                                nop

如此可以正常完成一次调用,但是还有问题,因为会反复修改Right_TestAntiCall的指令,可能在多线程中执行时出现问题。

所以更好的方法时在Right_TestAntiCall的模块中找一个不用(零值)的内存,用来保护临时指令,不细讲了,大家自行探索吧。

(完)

2493 次点击
所在节点    程序员
8 条回复
zhensjoke
2019-09-28 10:24:57 +08:00
我就服会汇编的人。
Chaos11
2019-09-28 10:40:52 +08:00
翻了下 po 主 blog,有点东西
ech0x
2019-09-28 12:23:51 +08:00
这个有点意思欸,但是这样做没有法律风险吗?
May725
2019-09-28 12:36:07 +08:00
硬核 hack👍
meeken
2019-09-28 12:40:59 +08:00
这边都是 po 主的帖子,太硬核了爱了爱了
janxin
2019-09-28 22:29:21 +08:00
新版本的微信都加 vmp 了?
Chenamy2017
2019-09-29 09:20:37 +08:00
Orz
azcvcza
2019-09-29 10:03:03 +08:00
好 HACK

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/605022

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX