当前位置:安全客 >> 知识详情

【漏洞分析】CVE-2017-11882漏洞分析、利用及动态检测

2017-11-24 18:44:37 阅读:49701次 收藏 来源: 安全客 作者:银雁冰

http://p6.qhimg.com/t01574b7ebbcfa43e38.png 

作者@银雁冰

预估稿费:1200RMB

(本篇文章享受双倍稿费 活动链接请点击此处

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门


【漏洞分析】Microsoft Office内存损坏漏洞(CVE–2017–11882)分析


前言


CVE-2017-11882是微软本月公布的一个远程执行漏洞,通杀目前市面上的所有office版本及Windows操作系统(包括刚刚停止支持的Office 2007)。该漏洞的成因是EQNEDT32.EXE进程在读入包含MathType的ole数据时,在拷贝公式字体名称时没有对名称长度进行校验,从而造成栈缓冲区溢出,是一个非常经典的栈溢出漏洞。上次出现这么典型的office栈溢出漏洞是著名的CVE-2012-0158。本文将深入分析该漏洞背后的机制,并在此基础上讲一下poc的构造方法,利用思路及动态检测方式。

 

漏洞原理分析


调试环境:  windows7_sp1_x86 + office 2007 x86 + windbg 6.11 x86

EQNEDT32.EXE version:  2000.11.9.0

该漏洞和经典的CVE-2012-0158一样,位于实现OLE接口的IPersistStorage::Load函数中。sub_40415B为ole的初始化过程,如下图1所示,它调用了sub_40440A函数,sub_40440A的主要作用是在初始化EQNEDT32.EXE实现的COM接口的各个函数指针。

http://p0.qhimg.com/t0107c4c1102c3f7cc4.png

图1 

通过工具我们可以看到EQNEDT32.EXE实现了如下接口(图2):

http://p6.qhimg.com/t016bee7432345ab9c6.png

图2 

我们可以在EQNEDT32.EXE文件里面看到对这些接口的比较和使用,如图3所示:

http://p3.qhimg.com/t0147fbab22e0f50baa.png

图3 

我们重点关注IPersistStorage接口,任何ole对象必须实现该接口,图4为微软对该接口的说明:

http://p5.qhimg.com/t01ea2b18944c196a32.png

图4 

通过简单的逆向工程,我们可以看到IPersistStorage接口的各个方法指针在 sub_40440A中被初始化,如图5所示:

http://p4.qhimg.com/t0175910fa8dde5db83.png

图5 

图5中红框圈出的IPersistStorage::Load方法的主要用途是用来读入ole数据,在EQNEDT32.EXE中实现该方法后,即可被调用以读入MathType对应的ole数据,我们来看一下这个Load函数内部是怎么实现的,我们可以看到该函数的核心逻辑是打开并读入一个叫做“Equation Native”的流的数据(图6-1),在此基础上进一步读入MathType数据(图6-2),图6-3为动态调试记录:

http://p9.qhimg.com/t0148f4ea46d0e0e2b5.png

图6-1

http://p8.qhimg.com/t011f7b1907a0dfe562.png 

图6-2

http://p3.qhimg.com/t019eca0768cba6df2a.png

 图6-3 

我们来看一下这个“Equation Native”流来自哪里(图7),通过分析ole文件,我们可以看到该流的数据由用户所提供,正常情况下,流里面的数据代表一个MathType的公式,而恶意攻击者构造的数据可以如图7所示:

http://p5.qhimg.com/t018c904b4cbef3564a.png

图7 

该漏洞的直接触发原因为:在读入公式的Font Name数据时,在将Name拷贝到一个函数内局部变量的时候没有对Name的长度做校验,从而造成栈缓冲区溢出,如图8所示。从图9可以看出,函数给SrcStr变量分配的大小是0x24个字节,超过该大小就会造成溢出,从而覆盖不远处的eip,达到劫持程序执行流的目的,从StrStr开始算起,eip的位置为+0x2c,即44,再往前覆盖就是调用参数。

http://p9.qhimg.com/t0114fd5bc4057fcb53.png

图8

http://p3.qhimg.com/t014951282b95f71e22.png

 图9

整个漏洞执行过程的步骤如图10所示

http://p4.qhimg.com/t01c9e087d50b7c4858.png

图10

http://p3.qhimg.com/t018ea180c05d9f327e.png 

图11

http://p3.qhimg.com/t01db158c5f85950406.png 

图12

http://p7.qhimg.com/t017e2861a6db6f9958.png 

图13

 

从零开始构造POC


上面已经把这个漏洞的原理和触发流程讲清楚了,下面我们尝试构造一个poc。由于该漏洞涉及到MathType公式数据在OLE中的结构,所以我们需要熟悉其结构分布。根据网上公开的数据结构,整个“Equation Native”的数据构成为:

Equation Native Stream Data = EQNOLEFILEHDR + MTEFData,其中

MTEFData = MTEF header + MTEF Byte Stream

下面一个一个来看。

EQNOLEFILEHDR的结构如下(图14-1):

http://p2.qhimg.com/t01f2c28cca756cf81b.png

图14-1

MTEF header的结构如下(图14-2),实际发现通过office 2007插入的公式其product subversion字段恒定为0x0A,这与下图有所出入:

http://p9.qhimg.com/t01c22cb6fae5d3c269.png

图14-2 

MTEF Byte Stream的结构如下(图14-3),可以看到它由一个SIZE record及后续的一些record构成,各种record的类别如图14-4所示,其中对于本次漏洞相关的Font record的说明如图14-5所示。

http://p7.qhimg.com/t0166794fa94c554a63.png

图14-3

http://p9.qhimg.com/t01c507a6e68ca5aedd.png 

图14-4

http://p1.qhimg.com/t01c9ac94f7c88bf8ce.png 

图14-5

我们来对照首次被公开的CVE-2017-11882 poc的数据,看一下上述结构的具体对应情况(图15):

http://p8.qhimg.com/t01f6317d24d2856f0f.png

图15

我们再来看一下漏洞发现者自己给出的poc里面上述结构的对应情况(图16):

http://p3.qhimg.com/t01b6b9fb3feb424c9a.png

图16

最后我们来看一下一个插入的普通公式的上述结构对应情况(图17),可以看到此时数据中并没有font record:

http://p0.qhimg.com/t01609a30feb3d342fc.png


图17 

通过上面的观察我们已经发现,所有插入的Equation Native数据在截止到SIZE record的数据排布都是一致的,不同之处在于恶意的Equation Native在SIZE record后放了一个Font record,其数据构成为:

Font record = tag(固定为8,占一个字节) +  typeface(占一个字节) + style(占一个字节) + font_name(以0x00结尾的字符串)

观察发现typeface和style这两个字节比较随意(两个poc里面这两个字节并不相同),实际构造时,我把两个字节改成其他的一些值(例如全为0)并不影响漏洞的触发。

 

对Equation Native数据的具体解析


Eqnedt32在如下位置读入font tag(图18-1),图18-2为动态调试时进行的验证。

http://p1.qhimg.com/t016bdfa879b86af3e9.png

图18-1

http://p9.qhimg.com/t01876b40341842c2a0.png 

图18-2

随后将tag传入sub_43A720函数,并进一步传入其子函数sub_43A87A进行判断,如图19所示:

http://p7.qhimg.com/t0137816a8a96974c30.png


图19

在sub_43B418函数中,首先读取font record中代表typeface和style的两个字节,如图20所示:

http://p1.qhimg.com/t01e70588e402f328ea.png

图20 

随后的sub_4164FA开始逐个读入字节读入font name的数据,直到遇到一个NULL,如图20所示。读入font name后,再调用sub_4214A6函数进行一些处理(图21),由于前面读入的font name数据过长,从而导致在sub_4214A6函数内部再进入几层调用后导致栈溢出,整个流程我已经在图10中进行概述,这里不再往下展开

http://p3.qhimg.com/t01247c22f74c02d2a4.png

http://p1.qhimg.com/t0198530fb645cad1b5.png


图21

从正常插入的ole开始构造poc。为了方便构造poc,我先用word插入了一个正常的Equation.3公式,提取出的Equation Native数据如图17所示,现在开始我要在此基础上构造一个弹出计算器的poc。

首先,直到SIZE record(0x0A)截止的数据都保持不变,将后面的数据替换为font tag(0x08)及其对应的数据(可以看到我将typeface和style这两个字节设为了0),如图22所示,我用从0x01开始的有规律的数据进行填充以方便后面我在栈溢出后对数据偏移进行定位。

http://p5.qhimg.com/t0146150f27d778e5ff.png

图-22 

用修改过的ole替换正常的ole,(对一个docx文件来说,就是用压缩软件替换word\embeddings\oleObject1.bin这个文件,然后重新保存成docx即可,rtf的替换方式稍微有些不同,不过也比较简单),打开后单击界面上的公式即可启动eqnedt32.exe进程。

为了调试,我们需要挂上windbg进行调试。可能有人会问如何用windbg挂上eqnedt32.exe,方法很简单,手动增加一个注册表项,或者用gflag工具设置一下eqnedt32.exe启动时附加windbg即可,具体过程请参考文末给出的链接。

Windbg挂上进程后让其运行,发现执行流在如下位置(图23-1)发生访问违例,可以看到违例数据为0xa2a1a09f(这并不是我们所期待看到的访问违例点),这即为我在图10里面提到的备注1和备注2。问题数据位于图23-2中的蓝色位置。访问违例发生的原因是4217c3处调用的sub_421E39函数内发生栈溢出(覆盖的是父函数的栈,这个溢出点在11月的补丁中并没有修复),导致覆盖了父函数的第一个参数lpLogfont,具体如图23-3所示

http://p8.qhimg.com/t014dffdd0d9ebe58ac.png

图23

http://p8.qhimg.com/t0172a6989298f8038f.png 

图23-2

http://p2.qhimg.com/t01708393edd03a6ca5.png

图23-3 

搞清楚问题的原因后,我们先确保这里溢出时不覆盖返回地址和第一个参数,我们需要将9B改成00,让数据在拷贝时自然截断,如图24所示,继续进行尝试:

http://p9.qhimg.com/t011ceb75069cc7b2dd.png

图24

继续尝试,发现此时又在如图25所示的位置发生访问违例:

http://p9.qhimg.com/t01259d1a19a16d73f3.png

图25

排查发现这是因为调用sub_44C430时父函数(sub_41160F)的第一参数作为sub_44C430函数的第二参数传入导致的(如图26-1,图26-2,图26-3所示),传入前父函数的第一参数已被覆盖,所以sub_44C430在取数据时发生访问违例。

http://p7.qhimg.com/t018dde83759bc936ab.png

图26-1

http://p0.qhimg.com/t01c92540dd21947040.png 

图26-2

http://p9.qhimg.com/t013f48286762727306.png

 图26-3 

再次搞清楚原因之后,我们再对poc的字节码做些修改,我们并不希望sub_41160F的第一个参数被破坏。重新执行样本,在sub_41160F的溢出发生后断下,如图27-1所示,可以看到此时第一参数被覆写为0x3a393837,我们再次修改poc数据,将37改成00,让拷贝在此处截止,修改完后的数据如图27-2所示。

http://p6.qhimg.com/t01c3c5ad9ef70c9ffa.png

图27-1

http://p6.qhimg.com/t0171c32659b6c7c618.png

 图27-2 

再次替换ole,这次终于执行到了我们所期待的地址处,且返回地址已经被我们提供的数据所覆盖,如图28所示:

http://p5.qhimg.com/t016c05ac6cf4a14fa3.png

图28

现在我们将原先33343536处的数据改成120C4300这个地址(该地址处调用了WinExec),如图29所示:

http://p4.qhimg.com/t01ce1305d890462b82.png

图-29 

再次替换ole,发现执行到所期待的ret栈上的数据已经完全为我们所控制,如图30所示:

http://p3.qhimg.com/t01e39a0b7a1a2fb667.png

图30

最终,我们只需要将01020304…处的数据替换为任意我们想执行的命令的字符串对应的十六进制即可(可以计算得出cmd的最大长度为44个字节),比如“calc.exe”,如图31所示:

http://p5.qhimg.com/t0156272419b5872e82.png

图31

最后一次替换ole,411874处的ret语句执行前栈上数据如图32所示,单步执行,最终弹出计算器(图33),并且word进程没有crash:

http://p6.qhimg.com/t01747428c7915d0287.png

图32 

http://p8.qhimg.com/t01d5cc426cd471682c.png

图33

 

对该漏洞利用方式的思考


关于自动触发,由于调试时我保存成docx,所以每次都是单击公式触发的。若要自动触发,我们可以将其保存成rtf格式,并且在在ole对象头部增加红色的关键字“{\object\objemb\objupdate…”。还可以在pptx文档内插入公式数据对该漏洞进行利用,自动触发方式可以借鉴CVE-2014-4114/CVE-2014-6352的触发方式,用自动播放动画的特性去调用DVerb进行触发,我还没尝试过,不过这是一个很好的思路。

       由上面的分析可以推算,WinExec的命令行的最大长度为0x24+0x4(第一个局部变量)+0x4(ebp)=0x2c,所以受到了很多限制。这里提一种有意思的方法,由于ole在实现IPersistStorage::Load方法时一直存在一个缺陷,导致在某些特定条件下下可以将一个ole对象里面存储的文件保存到temp目录(并且文件名可指定),这个机制缺陷在几年前已经被HaifeiLi所吐槽,但微软一直没有修复该问题,感兴趣的同学可以阅读参考链接中他在BlackHar2015上的演讲。

这样一来,我们可以在一个rtf文件中插入两个(或多个)ole,第一个用来将文件写入到temp文件夹,第二个ole利用CVE-2017-11882去执行temp文件夹下面的文件。这样的使用方法和CVE-2014-4114/CVE-2014-6352的方式有异曲同工之妙。目前野外已经出现这类样本,这里也请大家暂时参照网上的方法禁用公式编辑器3.0这一组件,以做好防范工作。

 

漏洞动态检测及防御


该漏洞的动态防御特别简单,因为是栈缓冲区拷贝时溢出,所以校验待拷贝长度是否超过缓冲区的长度即可,如图34所示,在红框处执行完毕后检查ecx的值,如果大于StrStr缓冲区的长度(0x24),即视为触发漏洞。微软在补丁里面把溢出校验长度设置为0x20,比实际少4个字节,可能为了保险起见吧。

http://p0.qhimg.com/t01e0307ce9ac33f18b.png

图34

 

参考链接


《Attacking Interoperability》 https://www.blackhat.com/docs/us-15/materials/us-15-Li-Attacking-Interoperability-An-OLE-Edition.pdf

《IPersistStorage interface》https://msdn.microsoft.com/en-us/library/windows/desktop/ms679731(v=vs.85).aspx

《MathType MTEF v.3(Equation Editor 3.x)》 http://web.archive.org/web/20010304041035/http://mathtype.com:80/support/tech/MTEF3.htm

《How MTEF is Stored in Files and Objects》http://web.archive.org/web/20010304111449/http://mathtype.com:80/support/tech/MTEF_storage.htm

《Skeleton in the closet. MS Office vulnerability you didn’t know about》https://embedi.com/blog/skeleton-closet-ms-office-vulnerability-you-didnt-know-about

《Did Microsoft Just Manually Patch Their Equation Editor Executable? Why Yes, Yes They Did. (CVE-2017-11882)》 https://0patch.blogspot.jp/2017/11/did-microsoft-just-manually-patch-their.html

《How to: Launch the Debugger Automatically》https://msdn.microsoft.com/en-us/library/a329t4ed(v=vs.100).aspx

《CVE-2017-11882》https://github.com/embedi/CVE-2017-11882


本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4753.html

参与讨论,请先 | 注册 | 匿名评论
发布
用户评论
银雁冰 2017-11-28 10:58:15
回复 |  点赞

多看几遍参考链接1和微软的相关文档吧,回答自己如下问题:1.为什么要实现IPersistStorage接口? 2. 为什么要实现Load函数? 3.Load函数的逻辑对应到ole文件格式上是如何对应的? 4.IDA的交叉会用吗? 5.为什么说这个漏洞和0158很像,哪里像,原理真的一模一样吗? 6.原作者的《Skeleton in the closet. MS Office vulnerability you didn’t know about》这篇文章你认真看了吗?里面有哪些信息特别有价值,而哪些东西又写错了? 7.Corelan的《Exploit writing tutorial part 1 : Stack Based Overflows》动手实践过一遍吗? 如果这些你都认真思考过,我不认为一个小小的Load函数的定位会让你绞尽脑汁。定位Load函数用IDA就行了,当看到它在解析“Equation Native”流时,就已经100%锁定了。

男科圣手 2017-11-27 21:44:36
回复 |  点赞

这是Load函数调用时粘回溯: EQNEDT32!AboutMathType+0x5881 RPCRT4!Invoke+0x2a RPCRT4!NdrStubCall2+0x2d6 ole32!CStdStubBuffer_Invoke+0xb6 ole32!SyncStubInvoke+0x3c (FPO: [Non-Fpo]) ole32!StubInvoke+0xb9 (FPO: [Non-Fpo]) ole32!CCtxComChnl::ContextInvoke+0xfa (FPO: [Non-Fpo]) ole32!MTAInvoke+0x1a (FPO: [Non-Fpo]) ole32!STAInvoke+0x46 (FPO: [Non-Fpo]) ole32!AppInvoke+0xab (FPO: [Non-Fpo]) ole32!ComInvokeWithLockAndIPID+0x372 (FPO: [Non-Fpo]) ole32!ComInvoke+0xc5 (FPO: [Non-Fpo]) 怎么判断这是IPersistStorage:Load呢。请赐教!感觉参考链接1和您的文章都是说很简单的逆向工程都可以判断,绞尽脑汁搞不懂。。。求教

男科圣手 2017-11-27 21:44:36
回复 |  点赞

这是Load函数调用时粘回溯: EQNEDT32!AboutMathType+0x5881 RPCRT4!Invoke+0x2a RPCRT4!NdrStubCall2+0x2d6 ole32!CStdStubBuffer_Invoke+0xb6 ole32!SyncStubInvoke+0x3c (FPO: [Non-Fpo]) ole32!StubInvoke+0xb9 (FPO: [Non-Fpo]) ole32!CCtxComChnl::ContextInvoke+0xfa (FPO: [Non-Fpo]) ole32!MTAInvoke+0x1a (FPO: [Non-Fpo]) ole32!STAInvoke+0x46 (FPO: [Non-Fpo]) ole32!AppInvoke+0xab (FPO: [Non-Fpo]) ole32!ComInvokeWithLockAndIPID+0x372 (FPO: [Non-Fpo]) ole32!ComInvoke+0xc5 (FPO: [Non-Fpo]) 怎么判断这是IPersistStorage:Load呢。请赐教!感觉参考链接1和您的文章都是说很简单的逆向工程都可以判断,绞尽脑汁搞不懂。。。求教

银雁冰 2017-11-27 10:34:46
回复 |  点赞

问题1: 因为微软没有给符号,实际调试时是没有函数名称的,Load函数需要通过栈回溯或是在IDA里面追溯调用关系先定位到,然后通过动态调试进行验证。图5中10个函数里面,Load函数和QueryInterface函数我在动态调试中到达过,是确定的,其余8个的顺序可能并不完全对应,我暂时只是参照参考链接1的第37页,若需要确保结果不出错,你需要对每个函数进行下断点进行动态验证。我建议你在调试这个漏洞前先把参考链接1好好看几遍,并且调试一下0158。问题2: 我觉得这并不是一个问题,可能你打开文档时eqnedt32.exe就没启动。如果担心权限问题,你可以把UAC关闭试一下。

2017-11-26 20:28:16
回复 |  点赞

1,第五个图的interface的各个函数是调试时获取的吗,插件只是暴力搜索guid和iid吧? 2,windbg在eqnedt32.exe启动时加载似乎有问题,按照您给的链接。感觉像是权限的问题,因为注册表设置后,直接双击eqnedt32.exe调试器windbg会蹦出来,但是打开rtf文档就不会。不知道具体您怎么设置的

2017-11-26 13:31:29
回复 |  点赞

楼主请教下:第三个图中,IDA里面guid和iid的标记是自动导入的吗?还是手动注释的?还有第五个图中如果interface的标记是怎么导入的

2017-11-26 20:28:16
回复 |  点赞

1,第五个图的interface的各个函数是调试时获取的吗,插件只是暴力搜索guid和iid吧? 2,windbg在eqnedt32.exe启动时加载似乎有问题,按照您给的链接。感觉像是权限的问题,因为注册表设置后,直接双击eqnedt32.exe调试器windbg会蹦出来,但是打开rtf文档就不会。不知道具体您怎么设置的

2017-11-26 13:31:29
回复 |  点赞

楼主请教下:第三个图中,IDA里面guid和iid的标记是自动导入的吗?还是手动注释的?还有第五个图中如果interface的标记是怎么导入的

2017-11-26 16:21:14
回复 |  点赞

感谢您的耐心解答

银雁冰 2017-11-26 15:54:21
回复 |  点赞

问题1: https://github.com/nihilus/GUID-Finder 问题2: 参考链接1

2017-11-26 13:31:29
回复 |  点赞

楼主请教下:第三个图中,IDA里面guid和iid的标记是自动导入的吗?还是手动注释的?还有第五个图中如果interface的标记是怎么导入的

银雁冰 2017-11-26 15:54:21
回复 |  点赞

问题1: https://github.com/nihilus/GUID-Finder 问题2: 参考链接1

2017-11-26 13:31:29
回复 |  点赞

楼主请教下:第三个图中,IDA里面guid和iid的标记是自动导入的吗?还是手动注释的?还有第五个图中如果interface的标记是怎么导入的

2017-11-26 13:31:29
回复 |  点赞

楼主请教下:第三个图中,IDA里面guid和iid的标记是自动导入的吗?还是手动注释的?还有第五个图中如果interface的标记是怎么导入的

银雁冰 2017-11-25 11:57:25
回复 |  点赞

OleViewDotNet

土司观光团 2017-11-24 23:51:53
回复 |  点赞

图二用到的工具是什么呀?

土司观光团 2017-11-24 23:51:53
回复 |  点赞

图二用到的工具是什么呀?

SMercenary 2017-11-24 19:12:41
回复 |  点赞

查看更多