查看原文
其他

崩溃回溯分析

ioiojy 看雪学苑 2022-07-01

看雪论坛作者ID:ioiojy




崩溃函数的定位


我们这次课程研究的对象是上次dump出来的完整版的转储文件以及从XP系统中提取出来的mshtml.dll程序。对于这两个文件的分析,均可在真实系统中执行。

      
我们首先打开WinDbg,选择File菜单中的Open Crash Dump选项:


      
找到待分析的转储文件并打开:


       
打开转储文件以后,WinDbg会显示转储文件的概要信息、注释信息和异常信息。

可以发现,出问题的地方是mshtml.dll中地址为0x7e278c83位置的mov语句,不清楚为什么这里的ecx所指向的内容不是合法地址,所以我们研究的目标就是找出ecx这个值是在哪里被改变的。

可以在IDA中直接来到这个位置分析一下:


       
可以发现,该语句是CElement::GetDocPtr(sub_7E278C83)函数的第一条语句,而这个函数也非常简单,一共只有四行代码。

如果说想要弄清楚ecx值的由来,那么从当前函数所提供给我们的信息是无法得知的了,这个ecx的值只可能是调用当前函数的语句之前被改变的,所以我们可以使用IDA给我们的交叉引用功能来看一下:


       
不幸的是,在当前模块中,一共有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的值是哪来的。




崩溃回溯分析


由IDA给我们生成的图像可以明显看到,esi的来源有三个,分别是[eax+8]、[eax+4] 以及[eax],也就是说,这里同样会把eax寄存器中的值作为地址,并取该地址中的内容。而不论是哪种情况,eax的值都是由[ebp+var_8]所决定的,而由这个函数头部的信息可以得知,var_8也就是-8:



       
于是我们就知道了,整个流程中,对崩溃起了作用的就是[ebp-8]。它其实是一个dword即4字节大小的局部变量空间。

这个空间在被当前函数申请出来以后,只有在一个地方被怀疑修改了内容,从而影响了后续的使用,即位于0x7E44C475处的CALL语句:


       
这里首先将这个变量空间的内容作为唯一的参数入栈,之后便调用了sub_7E27E248函数,我们需要重点关注这唯一的参数,即arg_0的异常变动情况,并且还能发现,这个函数只有一个参数,而且是个struct EVENTPARAM * *类型的指针:


       
可以发现,这里主要出现了两个流程,即左边的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寄存器内容的变化情况:


       
可以看到,距离刚才我们分析的Call语句最近的两条影响ecx内容的是[ebp+var_C],直接对ecx执行了赋值的操作,而[ebp+var_C]的内容在最开始又是被ecx所决定的、在这里,ecx将自身的内容赋值给了函数的第三个局部变量,这个局部变量保存在[ebp-C]的位置。

在这里我们看不到ecx的来源,因此还要继续如同之前所讲的那样,依靠WinDbg的栈回溯和IDA的交叉引用功能进行确定,于是就可以知道ecx的值来源是位于0x7E44C60E位置的CEventObj::get_srcElement函数:


       
在这里,该函数的第一个参数被解引用后,赋值给了ecx寄存器,而这个第一个参数的内容可以在WinDbg中通过kb指令进行栈回溯获取到:


      
可以看到,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的流程:


       
在这里,程序会将ebp-8进行解引用的操作,赋值给eax,然后对eax也进行解引用的操作,并赋值给esi,也就相当于esi的值为[[ebp-8]]:


      
接下来继续执行,对esi进行解引用,也就是相当于把[[[ebp-8]]]的值赋给ebx,再赋值给ecx,由于[ecx],即[[[[ebp-8]]]]中的内容不是合法地址,于是出现了报错的情况。

也就是说,程序可以对ebp-8进行三次正常的解引用操作,而第四次则会报错。在WinDbg中观察,则如下图所示:


      
由此可见,崩溃是由于在进行了多次的解引用之后,出现了不可识别的指针所导致的。而源头就是CEventObj::get_srcElement函数。




小结


我们本次课程主要为大家介绍了溯源的一些基本方法,希望大家掌握,因为我们的分析都是一环扣一环的,只有掌握好了本节课的内容,才能更好地理解我们之后的课程知识。

*实验文件点击“阅读原文”下载。




崩溃回溯分析技巧你学会了吗?


想学习更多恶意程序的基本分析方法和高级对抗技术,报名姜老师的课程吧!


扫码立刻学习


在这里,有老师亲自为你答疑解惑,还能在群里和小伙伴互帮互助,共同进步~


点击图片,了解更多课程内容





- End -



推荐文章++++

* 对一篇反沙箱文章的分析学习小记

* 使用msfvenom生成恶意APP并对该APK进行拆包分析

* 内核APC调用过程学习笔记

* 探究 Process Explorer 进程树选项灰色问题

* Linux Kernel Pwn 学习笔记(栈溢出)




公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com


求分享

求点赞

求在看


点击“阅读原文”一起充电吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存