首页
论坛
专栏
课程

[原创]ms14-017(cve-2014-1761)学习笔记

2015-1-29 18:34 7535

[原创]ms14-017(cve-2014-1761)学习笔记

2015-1-29 18:34
7535
一、        参考资料
1、网友“木无聊偶”写了一篇《CVE-2014-1761分析笔记》及两篇引用译文。地址:http://bbs.pediy.com/showthread.php?t=192351
2、《RTF文件格式规范V1.7_中文版.doc》。
3、微软编号ms14-017,CVE-2014-1761
二、        调试环境
操作系统:Xp sp3
调试器:Immunity Debugger 1.85
Office 2010安装三步骤:
首先,SW_DVD5_Office_Professional_Plus_2010w_SP1_W32_ChnSimp_CORE_MLF_X17-76734.iso
        其次,officesp2010-kb2687455-fullfile-x86-zh-cn    这个包658M
        最后,word2010-kb2863902-fullfile-x86-glb.exe     该升级包在微软的知识库链接地址:http://support.microsoft.com/kb/2863902
上述三步装完,office主要文件wwlib.dll版本号会是14.0.7113.5001。

在定位这个版本时颇费周折,参考资料中已说了是14.0.7113.5001,但试了好多office安装包都不对。只好在微软官网上搜索关键字“word 2010 安全更新 32位”,搜出来各个安装包,按编号的顺序全部下载回来,逐个测试,才找出14.0.7113.5001。

事后发现有个简单办法,在百度中直接搜索文件名和版本号,可以得出:

点击链接,就是知识库:http://support2.microsoft.com/kb/2863902,有详细的文件说明:

注:原poc在2010 sp1(wwlib.dll版本14.0.6024.1000)下能正常覆盖函数指针。只是几个核心地址有所不同,用7113版本的特征码来定位即可。例如下图中的0x316FC19C:

使用特征码定位如下:


三、        溢出原理:
这是一个由于数组溢出,覆盖了虚函数表,而执行shellcode的漏洞。在《0day2》书中198页,6.3节-攻击C++的虚函数。这一节讲解了虚函数攻击的原理和演示。而CVE-2014-1761的poc则是很好的实例。
其步骤主要如下:
1、        0x31D0C67F,申请数组:申请25个元素的数组,每个元素8字节,共200字节并初始化为0。
2、        0x31D131EF,故意溢出:向这个数组写入34个元素。导致数组后面紧跟的虚函数表被重写。Poc中一共有34(0x22)个lfolevel。
3、        0x 316FC19C,执行rop链:在虚函数(call [ecx+10])被调用时,会转而执行一个精心构造的rop链绕过DEP。在第二次执行这个call时触发rop。
溢出内存前后对比。可以看出,左图原来指向0x392C7A50等虚表,在右图中已经被替换。
      
注意:
上图中虚函数表和《Technical Analysis of CVE-2014-1761 RTF Vulnerability_翻译.pdf》文中重写的虚表不一样。该文章中的虚表是下图这个样子,和gfx相关,而我本地反复试验,虚表是指向mso的。



1、溢出的原理:
控制字listoverridecount的约定值是0,1或9。可以写成任意数值。只要是lfolevel控制字的数量超过了listoverridecount所声明的数量。就一定会溢出。
2、ROP的构造原理:
精心设计lfolevel控制字后的参数。可以摆放rop所需的信息。

但是,溢出并精确重写虚函数表,这值得试验一番。

四、        分配内存的过程:
经过反复地实验,这个poc有如下特征:
1、        先启动word,再打开poc,无法精确重写所需的虚函数表。导致rop无法执行。
2、        双击poc或winword.exe带参数poc.doc运行。每次启动内存结构没有变化。虚表被精确重写。
3、        如果poc是放在共享目录里,双击打开也无法精确重写所需的虚函数表。必须在本机的目录里。
换言之,需要在调试器中带参数执行winword.exe,才能精确重写虚函数表。

在0x31D0C67F处下断点,这里调用mso.#6306函数。

这个函数的返回值(EAX)就是分配的200个字节内存。也就是25个元素的8字节数组。
反复调试可以发现,每次分配的EAX中地址虽然不同,但其内容是相同的。
注意:上图中内存表里,69CB8C0所指向的392C7A50就是虚表。换个角度,用ds命令来看这个地方。

为什么每次地址不同,而内容相同呢?那就来跟踪分析数组空间分配函数mso.#6306。

上图中调用mso8163函数,一共传四个参数。8是数组元素大小,19是元素数量。

0x 3900563E,mso8163如下:

上一段代码中有一个常量:3A031EB4,再往下还有五级子函数调用。





通过跟踪分析,可以得到整个分配过程的算法如下,从常量开始:
1、[[3A031EB4]:157350 + C + C]:963130 + 2C = [96315C]:9A0000
2、9A0000+C4 = [9A00C4]:961080  注:31h * 4 = C4;    C8-1/ 4 = C7 / 4 = 31h (即:199 / 4);
3、961080 + 64h = 9610E4
4、[[9610E4] + 34h(D*4)]
有了这个算法,在分配数组0x31D0C67F中断时,可以手工在内存中找出即将分配的内存空间。也即是:尚未分配数组空间时,已经可以通过手工计算,精确地知道哪里的虚函数将被替换。


看看函数执行完成后EAX是不是这个值:

五、        控制字listoverridecount的参数为什么是25
按分配数组内存的算法中第2步,手工计算一下:
9A0000+C4 = [9A00C4]:961080  注:31h * 4 = C4;    C8-1/ 4 = C7 / 4 = 31h (即:199 / 4);
这个C4是由C8-1,也就是200-1后除以4再乘以4得到的。

如果listoverridecount是24,将变成:
24*8=192-1=191/4=47*4=188=BCh
9A0000+BC=[9A00BC]。

再看图上第4步内存9A00BC所指向的地址是960F80,很显然,虽然溢出成功。但将无法精确重写所需要的虚表。注意,手写算法是第2步,图上是第4 步。

如果listoverridecount是26,将变成:
26*8=208-1=207/4=51*4=204=CCh
9A0000+CC=[9A00CC]。

现在仔细看图上第4步,9A0000+C4 和 9A0000+CC 指向了相同的内存空间:961080。
换言之,25和26都将覆盖所需虚表。

同理,listoverridecount的参数若是其它值,这一步也会发生变化。无法精确重写所需要的虚表。但却有可能会重写其它虚表。

综上,poc中的内容有如下特征:
1、listoverridecount的参数值将影响重写虚表的内存空间。
2、lfolevel的参数值将影响填充的内容。

六、        简单看看微软如何解析rtf格式
先翻翻看rtf格式中文说明,中文看起来的确快多了,看雪有人说过,中文比英文更适合一目十行。

下断点,如下图。字符串处理并解析,当解析到listoverridecount时,对应着30F,栈中另一个参数19h就是参数值25。


七、        Win7 x64 office 2013 x64下的表现
Wwlib.dll版本号:15.0.4420.1017
微软的官方文档上说这个漏洞会影响到2013 x64版本。刚好机器上安装有。那就windbg一番吧。看看漏洞在x64上会是个什么样子。
1、        先把wwlib.dll文件拷到临时目录里。用ida pro x64版本反汇编。
2、        如何定位到那几个关键的地址呢?特别是需要定位出覆盖函数指针的地址。x64和x86指令不同,这不能用特征码了。那就使用ida的Search Text功能。
直接搜索:case 83。会搜出来好几个。再人工识别一下,就可以找到该地址了。
下图是x86版本

下图是x64版本


用windbg带参数加载winword.exe。
用lm命令得到wwlib的加载地址,这和2010 x86版本不同,2010x86是固定地址。
000007fe`d65e0000 000007fe`d8031000   wwlib      (deferred)
用ba e1 000007fe`d65e0000+39983B 这是不断解析lfolevel并填充数组导致溢出的地方。
中断后可知目标数组的地址,
用dps 94624c0 l 50 命令,看一下这里的内存结构。
1、数组元素所占空间和32位不同,32位一个元素是8位。x64是16位。
2、数组后方紧接着一些函数指针。如下图:

执行后对比:

八、        精简原Poc文件:
回到32位版本。
1、        删除listoverridecount25之前的所有leveltext内容。但必须以{\rt做为文件头。否则word将识别不了格式而直接以txt模式打开。
2、        删除objclass MSComctlLib.ImageComboCtl.2部分,会导致MSCOMCTL.OCX不加载。溢出虽然成功,虚表也成功被替换。但无法rop了。因此,要使用MSCOMCTL.OCX来做rop。则必须有这一部分内容,让word在解析这一段内容时,加载MSCOMCTL.OCX。如下图所示,从114行开始,即是MSCOMCTL相关部分。MSCOMCTL没有启用ASLR,是一个较好的rop选择。
a)        objclass MSComctlLib.ImageComboCtl.2},这是很明显的ActiveX对象。
b)        {\*\objdata 01050000020000001c000000  这后面都是objdata,对象数据。

3、        删除poc后半部分的乱码。在poc的后面,有相当多的乱码。经过测试,可以删除。不影响溢出和rop。但有可能会影响后续的shellcode和正常打开word功能。如下图所示,从199行的}控制字结束,后面有大量的乱码。

4、        为了进一步实验其溢出的关键部分,把5个带属性的lfolevel控制字的属性全部删掉。溢出正常;目标虚表覆盖正常;rop失效。
5、        综合上述四步,最小的能精确覆盖mso的392C7A50虚表的poc,如下图所示。图中第9行,即是把属性全部删掉后的5个lfolevel。如果要让rop成功。需要在下面这个最简poc里,摆放lfolevel的属性值。换言之,所删掉的各个部分,对目标虚表的构造没有影响。通过对poc的逐步简化并测试,可以厘清各部分对溢出的影响。

九、        尝试其它参数能不能利用
通过上述的分析过程,使用25这个参数,恰好覆盖函数指针。转向rop。现在来尝试一下其它参数值能否利用。
首先用百度关键字“listoverridecount9”,找一份带有listoverridecount9的正常rtf文档。如下图所示,可见listoverridecount是可以反复定义的。

1、        在上述最小poc文件的基础上,来改造一下。
2、        借用一下原poc文件中带shellcode的那一段lfolevel,其中的shellcode可以删掉。这段话中有rop去的目标地址。测试的时候可以辅助观察,只要向275A48E8跳转。就是覆盖了某函数指针。
3、        用这一句更换最小poc文件中的lfolevel。每个listoverride中包括100+的lfolevel。
4、        把listoverride的关键字内容重复24次,第一是因为根据前面的分析,26次跳走的目标地址会和25次相同。第二是我测试了50个listoverride,第25个(=26个)都会优先触发。既然优先触发,那就无法判断其它的覆盖是否影响函数指针了。这里就用24次吧。构造好的测试文件内容太长,截个示范:

5、        继续用immdbg带参数运行winword。砰的一声。出现了一个对话框:

6、        这个对话框是提示EIP=275A48E8。测试的目的达到了。除了25参数,其它参数值如19,也覆盖了某函数指针并能转移。由于这是一个简化版本的poc,没有加载MSCOMCTL.ocx。所以地址不可到达,就会弹出上图这个Error提示框。下图在immdbg里看看EIP。但是19的执行时机要比25晚一些,这就增加了复杂性。
7、        综上,25是一个经过精心选择的参数。



题外: 上图里immdbg的广告挺好。我们坐在某个角落,沉浸在二进制空间里,但不断变化的广告提示我们,我们仍然与真实世界相连,并不孤独。

[公告]LV6级以上的看雪会员可以免费获得《2019安全开发者峰会》门票一张!!

上传的附件:
最新回复 (2)
zhenwo 1 2015-1-29 20:41
2
0
贵在实践呀
fatecaster 1 2015-1-30 10:01
3
0
感谢分享,很多时候都是环境搭不起来无法入手分析,lz讲的很仔细。
游客
登录 | 注册 方可回帖
返回