一次美丽的误会引发对函数调用保护的思考 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
anhkgg
V2EX    程序员

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

  •  
  •   anhkgg
    anhkgg 2019-09-28 10:08:52 +08:00 2909 次点击
    这是一个创建于 2214 天前的主题,其中的信息可能已经有所发展或是发生改变。

    很久没碰 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的模块中找一个不用(零值)的内存,用来保护临时指令,不细讲了,大家自行探索吧。

    (完)

    8 条回复    2019-09-29 10:03:03 +08:00
    zhensjoke
        1
    zhensjoke  
       2019-09-28 10:24:57 +08:00
    我就服会汇编的人。
    Chaos11
        2
    Chaos11  
       2019-09-28 10:40:52 +08:00
    翻了下 po 主 blog,有点东西
    ech0x
        3
    ech0x  
       2019-09-28 12:23:51 +08:00 via iPhone
    这个有点意思,但是这样做没有法律风险吗?
    May725
        4
    May725  
       2019-09-28 12:36:07 +08:00 via iPhone
    硬核 hack
    meeken
        5
    meeken  
       2019-09-28 12:40:59 +08:00 via iPhone
    这边都是 po 主的帖子,太硬核了爱了爱了
    janxin
        6
    janxin  
       2019-09-28 22:29:21 +08:00
    新版本的微信都加 vmp 了?
    Chenamy2017
        7
    Chenamy2017  
       2019-09-29 09:20:37 +08:00
    Orz
    azcvcza
        8
    azcvcza  
       2019-09-29 10:03:03 +08:00
    好 HACK
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2942 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 13:35 PVG 21:35 LAX 06:35 JFK 09:35
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86