最近遇到一个Windows Office Communicator 2007 崩溃的问题,有些意思,写下来跟大家分享。
【现象】
我们公司内部使用office communicator来做内部人员的IM工具,使用的是一个定制版本(plugin), 可以跟公司内部的组织架构做整合。我使用的OS是Windows 7 32bit,一开始使用并无问题,在某次windows update之后,发现没法添加好友,在其他的同事的windows 7机器也出现这个问题,windows XP 上并无这个问题。由于我并没有源码,只能在汇编这一级别进行调试。
大概的过程是这样的,在communicator上点击按钮后,会打开一个IE窗口,这个页面会使用一个公司的ActiveX控件,崩溃会发生在这个控件中,无法使用该功能。
【调试】
在没有调试器的情况下,windows只会给你一个出错提示,没有任何有用信息。这次我使用的Windbg,使用"Windbg -I"设置成默认的事后调试器。
当崩溃发生的时候,自动打开windbg:
从上面的出错信息,可以得到以下几点信息:
1.崩溃进程是iexplorer.exe(使用lmf命令)
2.崩溃的模块是AddContact.dll(这个正是定制的DLL)
3.崩溃的指令是mov ecx,dword ptr [eax],这条指令相当于ecx = *eax;但是由于eax =0 ,导致了一个空指针访问,从而崩溃了。
让我们看看为什么eax会等于0,反汇编看看更多
可以看出eax指向了ebp+8的地址,我们知道vc的函数调用堆栈是:(可以参考我以前的一篇文章:
vc6函数调用浅析)
ebp+8实际上函数的第一个参数。
接下来我们使用kb命令来看看crash stack:(要先设置好symbol的path,我没有AddContact.dll的pdb), 发现第一个参数确实为0
我们必须要看上一帧的调用情况,从上图可以看出返回地址是0b163df8(AddContact的基址是0x0b160000,偏移量是3df8)。
我标记了关键的四条指令:
mov esi,dword ptr [ebp+8]
mov eax,dword ptr [esi+0B4h]
push eax
call AddContact!DllUnregisterServer+0x640a (0b16755f)
可以看出esi指向的第一个参数,eax指向的是参数的B4偏移处,而这个eax正是作为第一个参数(记住函数调用是从右向左入栈)
从前面的kb命令可以看出第二帧的第一个参数是0080cd88,使用db命令来查看内存,
奇怪的是这个值并不为0!【尝试解决问题1】
我的目标是让程序不再崩溃,所以我能不能把崩溃的指令去掉来解决问题呢?使用OllyDBG来改改看吧。我们把函数调用的部分都是NOP来填充。然后右键使用[copy to executable]来另存为一个新的DLL
确实,使用这个patch之后,确实不会再crash,能正常显示页面,但是仍然无法添加好友。
所以简单的屏蔽崩溃指令,并不能解决问题,
我们必须寻求深层次的崩溃原因。
【调试2】
从上面的调试结果看,最大的疑点是为什么崩溃帧的第一个参数变成0,这也是crash的最根本原因。因为刚才我们使用的都是事后调试,无法跟踪得到更详细的信息。
这里遇到一个比较棘手的问题,因为新打开的IE窗口是一个新建的进程,我们根本来不及attach到该进程,它已经崩溃了,我们如何调试呢?有一个办法,是attach到该进程的父进程,然后使用" .childdbg 1"来调试子进程。
使用process explorer可以看出,该浏览器的父进程为DCOM的service进程,我们attach到这个进程
使用childdbg 1命令:
果然当新窗口打开的时候,windbg捕捉到。
下一步我们要去设置断点,我想在AddContact 偏移处3de0的设置断点,但是由于iexplorer进程刚刚被启动,AddContact模块设置还没有被load,不知道基址,无法设置断点。(由于安全的原因,windows 7的module每次被load到一个随机地址).
我先使用windbg设置一个Module Load的命令,当load AddContact停下来,这样我就可以设置断点。
在windbg中设置Event Filters:
果然停下来了:
可以看到AddContact.dll 的基址为0x0b9e0000,设置断点 "bp 0b9e3de0",然后输入g执行到断点。然后输入t单步调试
发现确实eax不为0,所以传入的参数并不为0,但是后来被改变了。
如何跟踪栈上面的数据变化?我们使用ba命令来监视,这样一旦有人改变,就能停下来让我们知道:
继续执行,发现在执行COM QueryInterface 的时候改变了那个参数的值。
看看汇编代码,可以发现正是这句话让第一个参数变为0,而返回值80004002,这是一个COM 的错误值,找不到interface:
看看堆栈信息,可以发现要查找的COM interface为{e1af1028-b884-44cb-a535-1c3c11a3d1db}
通过google,我们发现这个interfaces是windows communcaitor的
IMessengerGroup所以根本原因是找不到这个interface,为了验证,我在注册表的HKCR\Interface下面查找此键,果然没有发现,在正常的机器上却能找到。我怀疑是windows update的时候这个interface被移除了,所以我们只要重新注册这个interface就可以了。
【总结】
1. 利用.childdbg命令调试子进程
2. Event Filters可以有效的帮助调试DLL
3. ba命令是神器,帮助你监控数据
4. 调试要胆大心细,不放过任何细节,真相就在下一秒,坚持....