由Hook引起的XP Explorer崩溃

有用户反应XP系统上一拖动桌面上的图标即会导致桌面重启,验证后,证实和360安全的升级有关,360安全9.0及后续版本和我们的软件有冲突。

360安全本身太激进,作为一款安全及防毒软件来说,激进并不是什么好事,这也是我不喜欢用360安全软件的原因之一。但是在事情未查出原因之前,不能随便怪罪360,毕竟在二者不共存时,均不会有桌面崩溃的事情发生。

先通过排除法,发现冲突发生在Hook上,而且是在特定的响应拖拽事件的API的Hook上:

    ole32.dll: DoDragDrop

问题定位之后照理应该很直接了,但分析新的DoDragDrop的实现及相关代码,并没有发现明显问题。

我们的代码在一个DLL文件中,并根据需要注入被Hook的进程,此案中便是Explorer.exe。调试起来稍有些周折,但也不算太麻烦。对此类的应用层调试问题,用VS (Visual Studio)远程调试最方便。远程attach到目标XP系统上的explorer.exe进程,然后触发崩溃事件,VS直接响应,显示问题出在一条很正常的压栈(push)操作上,再查看堆栈指针,果然已经耗尽,esp所指内存为非法地址(空页)。

初步揣测可能是堆栈不够所致,explorer.exe默认的size of stack reserve是0x400000,即4M字节。为了验证是不是堆栈的问题,直接将修改explorer.exe的PE文件头,将reserved stack size改成了64M(0x4000000)。

再测试时,explorer.exe在拖动桌面图标时还是会崩溃,只是会等上一会,不像之前一拖动便立即崩溃,看来问题并不是堆叠不够所致。

通过VS中断,发现Call stack中全是iNetSafe.dll (360安全的一个模块),由此断定出现了嵌套调用,死循环。

后面继续跟踪代码的调用过程发现了这样一个有趣的事,先将相关代码列出来:

ole32.dll地址空间 【774E0000 - 7761E000】:
_DoDragDrop@16:
775D0DC0 E9 0B DA 13 EE       jmp         6570E7D0
775D0DC5 83 EC 4C                sub         esp,4Ch
。。。。。。

iNetSafe.dll的地址空间 【65700000 - 65737000】:
6570E7D0 E9 AB 3A 1E 9B       jmp         CDragDropObj::NewDoDrapDrop (8F2280h) 
6570E7D5 57                           push        edi 
。。。。。。
6570E7F5 8B 4C 24 18          mov         ecx,dword ptr [esp+18h] 
6570E7F9 8B 54 24 14          mov         edx,dword ptr [esp+14h] 
6570E7FD 51                         push        ecx 
6570E7FE 8B 4C 24 14          mov         ecx,dword ptr [esp+14h] 
6570E802 52                   push        edx 
6570E803 51                   push        ecx 
6570E804 56                   push        esi 
6570E805 FF D0                call        eax                     [eax: 6FFE00A0]
6570E807 5F                   pop         edi 
6570E808 5E                   pop         esi 
6570E809 C2 10 00             ret         10h 
6570E80C 8B C7                mov         eax,edi 
6570E80E 5F                   pop         edi 
6570E80F 5E                   pop         esi 
6570E810 C2 10 00             ret         10h

360 iNetSafe.dll 所维护的Hook地址信息:
6FFE00A0 E9 DB 21 91 90       jmp         CDragDropObj::NewDoDrapDrop (8F2280h) 
6FFE00A5 E9 1B 0D 5F 07       jmp         _DoDragDrop@16+5 (775D0DC5h) 

MyHook32.dll地址空间 【008F0000 - 00C5F000】:
CDragDropObj::NewDoDrapDrop:
008F2280 55                   push        ebp 
008F2281 8B EC                mov         ebp,esp 
008F2283 8B 0D 38 70 C1 00    mov         ecx,dword ptr […::m_OldDoDragDrop 0C17038h] 
                                                                  0C17038h:0EC9002C  ECX = 0EC9002C 
008F2289 33 C0                xor         eax,eax 
008F228B 85 C9                test        ecx,ecx 
008F228D 74 03                je          CDragDropObj::NewDoDrapDrop+12h (8F2292h) 
008F228F 5D                   pop         ebp 
008F2290 FF E1                jmp         ecx                              [ecx = 0EC9002C]
008F2292 5D                   pop         ebp 
008F2293 C2 10 00             ret         10h 

MyHook32.dll: 原地址及代码
0EC9002C 56                         push        esi 
0EC9002D 8B 74 24 08          mov         esi,dword ptr [esp+8] 
0EC90031 E9 9F E7 A7 56      jmp         6570E7D5 

流程分析:

当Explorer调用_DoDragDrop()函数时,将跳转到iNetSafe的DoDragDrop实现,地址为6570E7D0。iNetSafe的DoDragDrop函数又被MyHook32 Hook了,所以还要继续跳至地址008F2280。MyHook32执行完还会跳转至被Hook前的_DoDragDrop函数,地址存放于0EC90032处,跳转地址为:6570E7D5,即iNetSafe的DoDragDrop函数入口加上5个字节的偏移。同理,iNetSafe的DoDragDrop函数也要调用它所Hook的原DoDragDrop地址,此地址的跳转指令存放于6FFE00A0处。但奇怪的是,位置6FFE00A0所存放的地址竟指向MyHoo32的
NewDoDrapDrop(即8F2280),所以任何对_DoDragDrop的调用,将会陷入一个嵌套的死循环里再也出不来了,直到堆栈被耗尽。

从上面代码上来看,iNetSafe在前,MyHook32在后,所以iNetSafe于6FFE00A0中所保存的原_DoDragDrop地址不应该是MyHook32的,而应该是ole32的_DoDragDrop才是。

但根据Explorer.exe进程的加载模块表,MyHook32.dll的加载order为40,而iNetSafe则为103,明显是MyHook32.dll先被加载,所以Hook的顺序明显违背上面的分析。

至此,方想到问题可能出在MyHook32的Anti-UnHook检查机制上。MyHook32在一个线程里面会实时检测被Hook的函数入口代码,如果不是MyHook32自己的,则会再次尝试Hook,因为360的Hook,便导致了MyHook32的再Hook,所以结果便导致了嵌套Hook的乌龙。

问题已经明确,并不是360的问题,而是出在我们自己的程序上,实在不应该先入为主的怪罪360,尽管其中还是有不少感情因素。

调试终于结束,剩下的的事情便是思考出一个万全的Anti-UnHook机制了。

下图为Explorer.exe所加载的模块(截屏自VS2010):

HookModules-01HookModules-02HookModules-03

注:有几个DLL的图标上均有红叹号,表示是此DLL被加载到的虚拟地址空间并不是此DLL所指定的地址空间。如MyHook32.dll(此版本为DEBUG版本,故体积及占用的地址范围也比较大)其默认的虚拟地址为10000000 - 1036EFFF,而实际上却被加载至008F0000 - 00C5F000。对于DLL文件的加载及从Vista之后所支持的ASLR(Address Space Layout Randomization),以后有机会再单独介绍。