我们这次课程研究的对象是上次dump出来的完整版的转储文件以及从XP系统中提取出来的mshtml.dll程序。对于这两个文件的分析,均可在真实系统中执行。
不幸的是,在当前模块中,一共有696个地方调用了这个函数,这就给我们的分析带来了困扰,于是我们只能回到WinDbg,通过栈空间的特性来定位了。
我们知道,大家所使用的计算机系统都是基于栈架构的,栈是进行函数调用的基础。栈中记录了软件运行的丰富信息,观察和分析栈的内容是转储文件分析的一项重要手段。函数调用时会通过CALL指令将返回地址记录在栈上,而通过从栈顶向下遍历每个栈帧来追溯函数的调用过程,就被称为栈回溯。在WinDbg中,我们可以通过k命令进行栈的回溯:这里所展示的是在当前崩溃位置执行栈回溯的结果。其中的每一行所表示的是当前线程用户态栈上面的一个栈帧,其中的第一行所表示的是我们当前出现崩溃的函数。每个函数下面的一行是调用这个函数的上一级函数,也被称为父函数。因此函数的整体调用顺序是由下向上的。最下面一行是栈中的第一个栈帧,对应的是当前线程的启动函数GlobalWndProc,它位于mshtml这个模块。倒数第二行所执行的是位于mshtml中的CServerEnumCacheAry::`scalar deleting destructor'函数,同理,之后还调用了jscript模块中的NameTbl::Invoke函数。
如果我们从横向分析,其中的第一列是栈帧的基地址,因为x86架构中通常使用EBP寄存器来记录栈帧的基地址,所以这一行的列名称叫做ChildEBP,表示子栈帧(子函数)的基址寄存器(EBP)的值。第二列的名称是RetAddr,即“返回地址”,这个地址是当前函数的父函数中的指令地址,也就是调用当前函数的那个Call指令的下一条指令的地址。
可以发现,当前崩溃函数是CElement::GetDocPtr,而这个函数在执行完毕以后,会去执行0x7e44c4c8地址的内容,也就是回到其父函数的位置。那么我们就可以在IDA中来到这个地址看一下:我们通过之前的分析知道,崩溃是由于ecx寄存器的值出现问题而导致的。那么在这个call语句的上方可以看到,ecx的值是由ebx的值赋予的,而ebx的值又是由esi决定的,这里将esi作为地址,取该地址中的内容赋给ebx,所以我们下面还需要弄清楚这个esi的值是哪来的。
可以发现,这里主要出现了两个流程,即左边的loc_7E27E265以及右边的loc_7E341EF4。进一步分析发现,右边的流程并不会对参数arg_0进行异常的改变,因为这个流程会利用and操作将[ebp+arg_0]的内容清零,然后再赋以0x80020003这个值。但是左边的流程就不一样了,因为出现了一个不可控的因素,即eax的内容,它可能会对[ebp+arg_0]的内容产生异常的影响。而这个eax的值是由[ecx+18h]所决定的,于是还要对ecx寄存器中的内容溯源。
或者我们也可以使用F5功能,让代码更加直观一些:可以看到,当前函数是CEventObj类型,程序使用this指针获取当前类型偏移为6的内容,通过类型转换可以发现,这个偏移的内容属于EVENTPARAM **结构。
返回到上一级函数,我们看一下在当前Call语句上方的ecx寄存器内容的变化情况:可以看到,get_srcElement函数的第一个参数的内容是0x01d3eec0,而骇客就可以通过控制这个参数的内容进行攻击。
这里我们再总结一下整个执行流程。程序在一开始会调用CEventObj::get_srcElement函数,将第一个参数解引用赋值给ecx之后,就来到了CEventObj::GenericGetElement函数,之后程序在0x7E44C475位置调用了CEventObj::GetParam函数,在这里会将[ecx+18h]的内容赋给[[ebp+arg_0]],而这里面的arg_0其实就是ebp+var_8,即ebp-8。我们刚才分析过了,程序只可能执行左边的流程,于是接下来程序对eax自身进行异或运算,也就是将eax寄存器清零,这样在当前函数的返回的时候,test语句会检测eax的内容,发现是零,跳转到0x7E44C485的流程,该流程最后会再一次对刚才的test结果进行验证,于是会跳转到0x7E44C4B2的流程:由此可见,崩溃是由于在进行了多次的解引用之后,出现了不可识别的指针所导致的。而源头就是CEventObj::get_srcElement函数。