作者:@银雁冰
预估稿费: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接口的各个函数指针。
图1
通过工具我们可以看到EQNEDT32.EXE实现了如下接口(图2):
图2
我们可以在EQNEDT32.EXE文件里面看到对这些接口的比较和使用,如图3所示:
图3
我们重点关注IPersistStorage接口,任何ole对象必须实现该接口,图4为微软对该接口的说明:
图4
通过简单的逆向工程,我们可以看到IPersistStorage接口的各个方法指针在 sub_40440A中被初始化,如图5所示:
图5
图5中红框圈出的IPersistStorage::Load方法的主要用途是用来读入ole数据,在EQNEDT32.EXE中实现该方法后,即可被调用以读入MathType对应的ole数据,我们来看一下这个Load函数内部是怎么实现的,我们可以看到该函数的核心逻辑是打开并读入一个叫做“Equation Native”的流的数据(图6-1),在此基础上进一步读入MathType数据(图6-2),图6-3为动态调试记录:
图6-1
图6-2
图6-3
我们来看一下这个“Equation Native”流来自哪里(图7),通过分析ole文件,我们可以看到该流的数据由用户所提供,正常情况下,流里面的数据代表一个MathType的公式,而恶意攻击者构造的数据可以如图7所示:
图7
该漏洞的直接触发原因为:在读入公式的Font Name数据时,在将Name拷贝到一个函数内局部变量的时候没有对Name的长度做校验,从而造成栈缓冲区溢出,如图8所示。从图9可以看出,函数给SrcStr变量分配的大小是0x24个字节,超过该大小就会造成溢出,从而覆盖不远处的eip,达到劫持程序执行流的目的,从StrStr开始算起,eip的位置为+0x2c,即44,再往前覆盖就是调用参数。
图8
图9
整个漏洞执行过程的步骤如图10所示
图10
图11
图12
图13
从零开始构造POC
上面已经把这个漏洞的原理和触发流程讲清楚了,下面我们尝试构造一个poc。由于该漏洞涉及到MathType公式数据在OLE中的结构,所以我们需要熟悉其结构分布。根据网上公开的数据结构,整个“Equation Native”的数据构成为:
Equation Native Stream Data = EQNOLEFILEHDR + MTEFData,其中
MTEFData = MTEF header + MTEF Byte Stream
下面一个一个来看。
EQNOLEFILEHDR的结构如下(图14-1):
图14-1
MTEF header的结构如下(图14-2),实际发现通过office 2007插入的公式其product subversion字段恒定为0x0A,这与下图有所出入:
图14-2
MTEF Byte Stream的结构如下(图14-3),可以看到它由一个SIZE record及后续的一些record构成,各种record的类别如图14-4所示,其中对于本次漏洞相关的Font record的说明如图14-5所示。
图14-3
图14-4
图14-5
我们来对照首次被公开的CVE-2017-11882 poc的数据,看一下上述结构的具体对应情况(图15):
图15
我们再来看一下漏洞发现者自己给出的poc里面上述结构的对应情况(图16):
图16
最后我们来看一下一个插入的普通公式的上述结构对应情况(图17),可以看到此时数据中并没有font record:
图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为动态调试时进行的验证。
图18-1
图18-2
随后将tag传入sub_43A720函数,并进一步传入其子函数sub_43A87A进行判断,如图19所示:
图19
在sub_43B418函数中,首先读取font record中代表typeface和style的两个字节,如图20所示:
图20
随后的sub_4164FA开始逐个读入字节读入font name的数据,直到遇到一个NULL,如图20所示。读入font name后,再调用sub_4214A6函数进行一些处理(图21),由于前面读入的font name数据过长,从而导致在sub_4214A6函数内部再进入几层调用后导致栈溢出,整个流程我已经在图10中进行概述,这里不再往下展开
图21
从正常插入的ole开始构造poc。为了方便构造poc,我先用word插入了一个正常的Equation.3公式,提取出的Equation Native数据如图17所示,现在开始我要在此基础上构造一个弹出计算器的poc。
首先,直到SIZE record(0x0A)截止的数据都保持不变,将后面的数据替换为font tag(0x08)及其对应的数据(可以看到我将typeface和style这两个字节设为了0),如图22所示,我用从0x01开始的有规律的数据进行填充以方便后面我在栈溢出后对数据偏移进行定位。
图-22
用修改过的ole替换正常的ole,(对一个docx文件来说,就是用压缩软件替换wordembeddingsoleObject1.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所示
图23
图23-2
图23-3
搞清楚问题的原因后,我们先确保这里溢出时不覆盖返回地址和第一个参数,我们需要将9B改成00,让数据在拷贝时自然截断,如图24所示,继续进行尝试:
图24
继续尝试,发现此时又在如图25所示的位置发生访问违例:
图25
排查发现这是因为调用sub_44C430时父函数(sub_41160F)的第一参数作为sub_44C430函数的第二参数传入导致的(如图26-1,图26-2,图26-3所示),传入前父函数的第一参数已被覆盖,所以sub_44C430在取数据时发生访问违例。
图26-1
图26-2
图26-3
再次搞清楚原因之后,我们再对poc的字节码做些修改,我们并不希望sub_41160F的第一个参数被破坏。重新执行样本,在sub_41160F的溢出发生后断下,如图27-1所示,可以看到此时第一参数被覆写为0x3a393837,我们再次修改poc数据,将37改成00,让拷贝在此处截止,修改完后的数据如图27-2所示。
图27-1
图27-2
再次替换ole,这次终于执行到了我们所期待的地址处,且返回地址已经被我们提供的数据所覆盖,如图28所示:
图28
现在我们将原先33343536处的数据改成120C4300这个地址(该地址处调用了WinExec),如图29所示:
图-29
再次替换ole,发现执行到所期待的ret栈上的数据已经完全为我们所控制,如图30所示:
图30
最终,我们只需要将01020304…处的数据替换为任意我们想执行的命令的字符串对应的十六进制即可(可以计算得出cmd的最大长度为44个字节),比如“calc.exe”,如图31所示:
图31
最后一次替换ole,411874处的ret语句执行前栈上数据如图32所示,单步执行,最终弹出计算器(图33),并且word进程没有crash:
图32
图33
对该漏洞利用方式的思考
关于自动触发,由于调试时我保存成docx,所以每次都是单击公式触发的。若要自动触发,我们可以将其保存成rtf格式,并且在在ole对象头部增加红色的关键字“{objectobjembobjupdate…”。还可以在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个字节,可能为了保险起见吧。
图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