前言
在调试漏洞战争上的CVE-2012-1876做了一些笔记,漏洞分析的话漏洞战争和vupen的文章已经写的很清楚了。
因为是第一次用windbg,所以我主要把自己的环境搭建(比如怎么导入符号文件)和调试日志(十分详细)记录了一下,希望对那些和我一样,刚开始学习漏洞分析与调试的人有所帮助。
参考资料
调试环境搭建
下载
windbg符号文件设置
在windbg的窗口里输入
1 | .sympath SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols |
重启后要重新输入。
poc调试
1 | <html> |
基于HPA的漏洞分析方法
- hpa:启动页堆,在堆块后增加专门用于检测溢出的栅栏页,若发生堆溢出触及栅栏页便会立刻触发异常。
在终端通过gflags启动hpa
启动ie浏览器后,用windbg attach进程
两个进程,一个是broker进程 一个是页面的内容进程,附加后面的那个就可以,就是内容进程。
检查一下hpa开了没。
1 | 0:000> .symfix |
然后需要开启子进程调试,这样才能断下来。
1 | .childdbg 1 |
然后g,启动调试器。
1 | 0:027> g |
看到debugger正在运行了。
然后把poc拖到浏览器里运行。
另外poc拖进去之后,会自动断下来。
再g一下,就变成下面这个样子。
1 | 1:021> g |
然后允许ActiveX控件运行。
1 | (4b8.c00): Access violation - code c0000005 (first chance) |
然后kb回溯一下栈
1 | 1:025> kb |
这里可能会出现没有符号的问题,解决方法如下:
在windbg的窗口里输入
1 | .sympath SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols |
然后可以看见
分析一下
首先导致崩溃的(分析内容在下述代码注释了)
1 | 1:025> uf mshtml!CTableColCalc::AdjustForCol |
这样就清楚了,我们要在上一个函数下断。
重启一下windbg,重新attach
1 | 0:021> .childdbg 1 |
这个时候把poc拖进去(注意到没有提示允许activeX运行)
1 | ************* Symbol Path validation summary ************** |
lm (List Loaded Modules)
lm命令显示指定的已加载模块。输出中包含模块状态和路径。- m Pattern
指定模块名必须匹配的模板。Pattern可以包含各种通配符和修饰符。关于语法的更多信息,查看字符串通配符语法。
- m Pattern
sx 命令显示当前进程的异常列表和所有非异常的事件列表,并且显示调试器遇到每个异常和事件时的行为。
- sxe Break
当发生该异常时,在任何错误处理器被激活之前目标立即中断到调试器中。这种处理类型称为第一次处理机会。
- sxe Break
ld (Load Symbols)
ld 命令加载指定模块的符号并刷新所有模块信息。
这样组合起来,就是ld制定mshtml加载,然后sxe强制在加载这个模块后断下。
现在我们就可以对这个函数下断了。
1 | 1:025> bp mshtml!CTableLayout::CalculateMinMax |
- bp, bu, bm (Set Breakpoint)
bp、bu和bm命令设置一个或多个软断点(software breakpoints)。可以组合位置、条件和选项来设置各种不同类型的软断点。 - bl (Breakpoint List)
bl 命令列出已存在的断点的信息。 - g 命令开始指定进程或线程的执行。这种执行将会在程序结束、遇到BreakAddress 或者其他造成调试器停止的事件发生时停止。
我在调试的时候辅助了一下IDA,其实是可以不用的。
直接静态分析找到CalculateMinMax
另外这里也需要导入符号。
单步继续跟随调试,按p就可以单步执行( 不进入函数那种),不过其实按回车也可以。
1 | eax=ffffffff ebx=0492aea8 ecx=00412802 edx=ffffffff esi=00000000 edi=0467e70c |
注意到mov ebx,dword ptr [ebp+8] ss:0023:0467e4b4=0492aea8,[ebp+8]是参数1(知道栈吧..)
1 | 1:025> dd poi(ebp+8) |
- dd 双字值(4字节)
默认的显示数量为32个DWORD(128字节)。 - poi()
指定地址处的指针大小的数据。指针大小或者是 32 位或者是 64 位。在内核调试模式,大小基于目标计算机上的处理器。在 Intel Itanium 计算机上用户模式调试下,大小或者是 32 位或者是 64 位,依赖于目标应用程序。所以,如果你想得到指针大小的数据最好使用 poi 运算符。
1 | 1:025> dd poi(ebp+8) |
- ln 命令显示给定地址处的或者最近的符号。
可见参数1引用的是CTableLayout对象,也就是<table>
标签中的对象。
1 | 1:025> p |
这里的ebx+54h指向的是table标签里的col元素的span值,在poc中只有一个span值1,所以这里赋值1.
讲道理,用windbg这样看汇编太难受了,接下来我们用IDA看吧
1 | .text:74D3018A |
跟进CImplAry::EnsureSizeWorker函数,发现该函数主要用于分配堆内存,分配的内存大小,分配的内存大小为spansum * 0x1C
,虽然此处spansum为1,但其分配的最小值为0x1C * 4=0x70
,分配的地址保存在CtableLayout+0x9C
1 | .text:74DF8F9C ; public: long __thiscall CDataAry<long>::EnsureSize(long) |
我们看下分配的缓冲区vulheap地址。
1 | 1:025> bp mshtml!CTableLayout::CalculateMinMax+0x168 |
分配的地址在ebx+0x9C
1 | 1:025> p |
此外,我们看一下用于比较的spansum和spancmp
1 | 1:025> dd ebx+54 |
从上面的代码段可知,这里分配了0x70大小的内存地址在CtableLayout+0x9C指向的地址。
总结:
- CtableLayout::CalculateMinMax的第一个参数为CtableLayout对象,即table标签在内存中的对象。
- CtableLayout+0x54:span属性值和spansum
- CtableLayout+0x9C: 保存vulheap,至少分配0x70字节的内存
- CtableLayout+0x94:用于和spansum比较的spancmp,当spancmp>>2小于spansum才分配漏洞堆块。
要注意的地方
再次g之后会出现允许activeX允许这个框,
然后发现
这我也不知道是中间再次在哪触发了这个函数,还是重新运行了poc,总之这个时候的spansum和spancmp都没变,分别为1和0.
我觉得可能是中间又在哪触发了吧,不像是重新运行了,我也不确定是为什么,没有完整的阅读这个模块。
总之再次g之后,就和泉哥书上一致了。spansum还是1,spancmp变成4.
当分配完内存后,执行poc中的over_trigger函数时,会再一次断在CTableLayout::CalculateMinMax函数中,跟进去看下spansum和spancmp的值。
1 | 1:025> bl |
把之前设置的多余断点删掉,注意bc后跟的是断点的标号。
1 | 1:025> g |
1 | 1:025> dd ebx+54 |
spansum为1,spancmp的值为4,(4>>2)为1==1,不发生跳转,不分配内存。
但是在over_trigger中,我们已经将span设置为1000了,这也是允许的最大值。
接着执行到mshtml!CTableLayout::CalculateMinMax+0x37e,我本来bp了一个断点在这,然后g一下,可是并没有断下来(这里没有断下来应该还是我断点下错了,没有进入那个断点的语句块),所以没办法,单步p呗,然后发现了新姿势,p 10能一次10下。
1 | 1:025> p |
在mshtml!CTableCol::GetAAspan下断点,让它第二次获取span值的时候断下来。
1 | 1:025> bp mshtml!CTableCol::GetAAspan |
gu是执行到当前函数结束返回。
此时span的值已经是0x3e8即最大值1000了。
继续分析后续代码。
1 | text:74EC5AB3 call ?GetPixelWidth@CWidthUnitValue@@QBEHPBVCDocInfo@@PAVCElement@@H@Z ; CWidthUnitValue::GetPixelWidth(CDocInfo const *,CElement *,int) |
复制的内容相当于width * 100
得到的数值,比如此处为0x41,则复制内容为0x41 * 1000=0x1004
。
在AdjustForCol中,会以1000 * 0x1c位计数循环向vulheap写入数据,最终造成heap溢出。
再g就崩溃了。
总结
- 当页面加载,CTableLayout::CalculateMinMax被首次调用,col的span属性被初始化为1,此时spansum=1,spancmp=0
- 由于(spancmp>>2)<spansum,即0<1,调用EnsureSizeWorker函数分配大小为
0x1c * spansum
的内存,但至少分配0x1C * 4=0x70
大小的内存块。 - 分配内存后,spancmp=spansum * 4 = 4,此时(spancmp>>2)==spansum,即4/4==1,因此不再分配内存
- 调用over_trigger,CTableLayout::MinMax第二次被调用,但spansum和spancmp未变,而span被更改为1000,在复制内容为
width * 100
的数据到分配缓冲区时,会以span为循环计数器写vulheap堆块,但是1000 * 0x1C > 0x70
,最终造成堆溢出。
经过调试,泉哥142页shr eax,2
理解错了,那个shr是右移的意思,而泉哥写的是左移运算符<<
实现漏洞利用
关于exp的编写请参考漏洞战争,这里只调试一些关键思路。
1 | <div id="test"></div> |
这部分主要是用来构造堆布局,构造结果如下。
然后从中间(200)开始释放EEEE…,腾出空间。
释放的位置就是为了在分配vulheap时能够占用到释放位置中的一个,当溢出时就可以占用到后面的字符串和CButtonLayout。
1 | ************* Symbol Path validation summary ************** |
先通过windbg attach ie,然后打开childdbg,因为刚开始IE还没有加载jsript.dll,所以可以先设置加载jscript.dll时断下(sxe),按g运行,拖入exp。
lmm确定载入后,再对JSCollectGarbage下断(bp),然后g运行。
1 | 1:023> bp jscript!JsCollectGarbage |
继续下断,找到vulheap分配的位置,具体分析参考漏洞战争。
1 | 1:023> bl |
1 | 1:023> .logopen |
打开log文件做记录,另外我在jscript!JsStrSubString下了额外的断点。
此外改动一下exp,加个alert。
1 | <script language='javascript'> |
断下
1 | ..... |
1 | 1:025> .logclose |
保存之后,最后一个vulheap就是我们要找的.
另外为了确定虚表偏移,直接动态找一下吧。
1 | 1:027> x mshtml!CButtonLayout::* |
奇怪的是,有两个虚表,这里我也不知道为什么……
1 | 1:027> lmm mshtml |
69ff3af8-69e80000=0x00173af8
这和泉哥书上说的中文版win7+ie8环境中的偏移也是一致的。
然后这我就很不解了……
此外看一下vulheap。
1 | 1:026> db 03f2ae30 l101c |
很简单的能观察到03f2ae30的AAAA字符串被大量覆盖,所以它就是vulheap。
为做对比,我多打印了很多,下面的未被覆盖的AAAA都是成片出现的。
不过对比漏洞战争书上,本来03f2b040地址处的fa被覆盖为48 00 01 00即0x00010048,这个覆盖看的出来(下图蓝色框线).
按照0x03f2ae30+0x100(EEEE…)+0x8(堆指针大小)+0x100(AAAA…)+0x8(堆指针大小)=03f2b040,也确实应该是这里,我应该没理解错。
但是很奇怪,我的fa也还在……(下图红色框线),这可能就是我之前弹窗打印出的虚表地址不正确的原因吧,感觉别人的文章里都不会这样……难以理解
得到虚表地址后,计算mshtml基地址,构造rop。
然后再次溢出,这次溢出直接像刚刚覆盖BBBB的大小一样,直接覆盖虚表指针,于是就可以劫持虚表指针到任意地址,如下。
1 | (6cc.7f8): Access violation - code c0000005 (first chance) |
总结
调试poc的时候还是比较顺利的,在调exp那里各种卡壳,唉。
主要还是学到了一些windbg的使用吧。
比如如果要下断点,其实可以在html里插入数学函数,比如用Math.cos,然后在jscript!Cos下断。
比如要查看jscript的导出表,可以在windbg里用x jscript!* 来查找,找虚表可以使用类似的方法(见上文)