“噩梦公式”二代 –2018年微软修复的首个Office 0day漏洞(CVE-2018-0802)分析

释放双眼,带上耳机,听听看~!
“噩梦公式”二代–2018年微软修复的首个Office0day漏洞(CVE-2018-0802)分析 简介2018年1月的微软安全补丁中修复了360核心安全高级威胁应对团队捕获的office0day漏洞(CVE-2018-0802),该漏洞几乎影响微软目前所支持的所有office版本

“噩梦公式”二代 –2018年微软修复的首个Office 0day漏洞CVE-2018-0802分析

 

简介

20181月的微软安全补丁中修复了360核心安全高级威胁应对团队捕获的office 0day漏洞(CVE-2018-0802),该漏洞几乎影响微软目前所支持的所有office版本。这是继360在全球范围内首家截获Office 0day漏洞(CVE-2017-11826)在野攻击以来,发现的第二起利用零日漏洞的在野高级威胁攻击。360核心安全团队一直与微软保持积极沟通,一起推进该0day漏洞的修复,让漏洞得到妥善解决后再披露漏洞信息。该漏洞的技术原理类似于潜伏了17年的“噩梦公式”漏洞(CVE-2017-11882),是黑客利用office内嵌的公式编辑器EQNEDT32.EXE再次发起的攻击,我们将其命名为“噩梦公式二代”CVE-2018-0802

攻击流程分析

我们捕获了多个“噩梦公式二代”的在野攻击,在野样本嵌入了利用Nday漏洞和0day漏洞的2个公式对象同时进行攻击,Nday漏洞可以攻击未打补丁的系统,0day漏洞则攻击全补丁系统,绕过了CVE-2017-11882补丁的ASLR(地址随机化)安全保护措施,攻击最终将在用户电脑中植入恶意的远程控制程序。

                                              图.jpg

图:“噩梦公式二代”在野样本攻击流程

 

漏洞细节分析

“噩梦公式二代”为CVE-2017-11882的补丁绕过漏洞,类型为栈溢出,根本原因为微软在“噩梦公式一代”的补丁中没有修复另一处拷贝字体FaceName时的栈溢出。本次漏洞在未打补丁的版本上只会造成crash,但在打补丁的版本上可以被完美利用。下面我们通过poc样本来分析CVE-2018-0802漏洞。

静态分析

CVE-2017-11882一样,本次漏洞的触发数据位于所提取OLE对象的“Equation Native”流内。图1中红线圈出部分为核心数据,共0x99=153字节。0x08代表font tag,紧随其后的00 01分别代表字体的typefacestyle,从33开始直到25 00的区域为Font名称,为栈溢出时拷贝的数据。这部分数据里面包含了shellcodebypass ASLR的技巧,进程命令行以及相关用于填充的数据,我们将在后面分析它们。

1.png

1

Equation Native 数据结构

据网上公开的资料,整个“Equation Native”的数据构成为:

Equation Native Stream Data = EQNOLEFILEHDR + MTEFData

其中MTEFData = MTEF header + MTEF Byte Stream

 

QNOLEFILEHDR的结构如图2所示:

2.png

2

 

MTEF header的结构如表1所示,关于这个结构,我们观察到的实际数据和格式规范存在差异,下表中以实际观察到的为主:

偏移量

说明

0

MTEF版本号

0x03

1

该数据的生成平台

0x00表示在Macintosh平台生成,0x01表示Windows平台生成

2

该数据的生成产品

0x00表示由MathType生成,0x01表示由公式编辑器生成  

3

产品主版本号

0x03

4

产品副版本号

0x0A

1

 

在攻击样本中,MTEF Byte Stream结构如表2所示:

初始SIZE记录

FONT记录

FONT内容

剩余数据

2

 

FONT记录及FONT内容结构如表3所示:

成员

说明

备注

tag

0x08

1字节

tface

typeface编号

1字节

style

字体样式

1字节

name

字体名称

NULL结尾的ASCII字符串

3

补丁绕过分析

CVE-2018-0802的漏洞触发点位于sub_21E39(IDA中将模块基址设为0),如图3所示,可以看出该函数的功能为根据公式中的字体数据来初始化一个LOGFONT结构体:

3.png

3

 

我们来看一下微软对于LOGFONT结构体的说明(4)。可以看到这个结构体的最后一个成员为lfFaceName

4.png

4LOGFONT结构体

 

我们再看一下微软对lfFaceName成员的说明(5)。可以看到lfFaceName代表了字体的typeface名称,在所分析的版本上,它是一个以空结尾的char型字符串,最大长度为32,其中包含终止符NULL

5.png

5

 

问题很明显:图3红框内的代码在拷贝字体FaceName时并没有限制拷贝长度,而拷贝的源数据为用户提供的字体名称,目的地址为父函数传递进来的一个LOGFONT结构体地址。我们回溯到sub_21E39的父函数来看一下(6),可以看到这个地址位于父函数开辟的栈上,是父函数的一个局部变量。攻击者通过构造恶意数据,覆盖了父函数(sub_21774)的返回地址的后两个字节,然后将控制流导向了位于栈上的shellcode

6.png

6

 

分析过程中我们发现一处疑似递归的地方,图7sub_21774的反汇编代码,可以看到sub_21774先是调用了漏洞函数sub_21E39去初始化一个LOGFONT结构体,然后调用相关API,传入这个结构体,从系统获取到一个字体名称保存到Name。随后,它将获取到的Name和用户提供的lpLogFont作对比,如果不一致(并且sub_115A7函数需要返回False),会再根据a3指定的条件来继续调用或者不调用自身,而a3sub_21E39函数的第3个参数。

7.png

7

 

我们来看一下第3个参数的传参,否则可能存在多次递归,不能有效利用本次溢出。根据之前CVE-2017-11882的调试结果(8),我们可以看到,在解析用户提供的font数据时,调用sub_21774的函数为sub_214C6。我们回溯到sub_214C6看一下(9)sub_214C6调用sub_21774前给第三个参数传的值为1,所以图7中的if(a3)为真。我们再来看一下图7sub_21774递归调用自己时对第3个参数传的值为0,这意味着sub_21774不会再次调用自己,递归层级只会有1级。分析到这里,递归的疑惑解决了。

8.png

8CVE-2017-11882触发执行流

 

9.png

9

 

分析到这里还有一个问题,那就是在_strcmpi(lpLogfont, &Name)不成立的情况下(如果font数据为用户伪造,此处肯定不成立)sub_115A7会被调用,这意味着会走到CVE-2017-11882的溢出点。在未打11月补丁的版本上,如果要成功利用CVE-2017-11882CVE-2018-0802的点就不会发生溢出,因为前者需要的溢出长度比后者小很多,且拷贝最后有一个NULL符截断(我们知道溢出到CVE-2017-11882的可控eip只需要0x2C个字节,而通过下文(11)的分析我们可以知道溢出到CVE-2018-0802的可控eip需要0x94字节)。另一方面,在未打11月补丁的版本上,想要触发CVE-2018-0802,就必然会先触发CVE-2017-11882。总之,CVE-2018-080211补丁前的版本上无法利用。

可是,从图10可以看到,在11月的补丁中,微软在CVE-2017-11882溢出点的拷贝前,对拷贝长度进行了0x20的长度限制,并且拷贝完成后,在拷贝最后手动加了一个NULL,从而使CVE-2017-11882失效。这直接导致打补丁前无法被利用的CVE-2018-0802可以被利用了!现在,只要sub_115A7返回False,漏洞就可以得到完美利用,而实际调试发现,sub_115A7返回False

10.png

10

动态分析

溢出点的数据拷贝

有了上面的分析,动态分析就变得很简单了。既然本次溢出点会拷贝数据,我们来监控一下每次拷贝时的源字符串和相应的栈回溯,我们先进入OLE数据相关的Load函数(sub_6881),然后在拷贝数据前下断点并进行输出,结果如代码1所示:

进入Load函数后的字体拷贝过程

 

0:000> bp eqnedt32+6881 //   Load函数

0:000> g

Tue Dec 26 15:56:30.360 2017   (GMT+8): Breakpoint 0 hit

eax=01356881 ebx=00000006   ecx=00000000 edx=00000000 esi=0019f144 edi=0019ef40

eip=01356881 esp=0019ef40   ebp=0019ef58 iopl=0         nv up ei pl   nz na po nc

cs=001b  ss=0023    ds=0023  es=0023  fs=003b    gs=0000             efl=00000202

EqnEdt32!AboutMathType+0x5881:

01356881 55              push    ebp

0:000> bp eqnedt32+21e5b   “.printf   \”——————————————————————————-\\n\”;   db esi lecx; k; g”

0:000> g

——————————————————————————-

 

0019ed10  33 c0 50 8d 44 24 52 50-eb 1d 63 61 6c 63 2e 65  3.P.D$RP..calc.e

0019ed20  78 65 20 20 20 20 20 20-20 20 20 20 20 20 20 20  xe             

0019ed30  20 20 20 20 20 20 26 90-90 90 8b 44 24 2c 66 2d        &….D$,f-

0019ed40  51 a8 ff e0 a5 23 79 ec-b1 2e 2a e2 74 e3 de 4f  Q….#y…*.t..O

0019ed50  31 76 4e e9 44 2d 1d ca-eb 87 21 39 a1 22 2e 3a  1vN.D-….!9.”.:

0019ed60  27 40 fb 5f db 43 a0 10-92 54 6d cd 8c 18 f4 90  ‘@._.C…Tm…..

0019ed70  8b ce 5f ca fe ee 52 71-7d 93 ba 59 2d ef 60 98  .._…Rq}..Y-.`.

0019ed80  9e f5 cd 3f 74 47 4a 6a-e3 59 7e 66 52 7c c9 30  …?tGJj.Y~fR|.0

0019ed90  c3 9d 91 e8 98 c2 4d a5-47 65 31 1f e6 e7 de 53  ……M.Ge1….S

0019eda0  c7 8a 2b d3 25 00                                ..+.%.

ChildEBP RetAddr 

WARNING: Stack unwind   information not available. Following frames may be wrong.

0019ebc8 013717c8   EqnEdt32!FMDFontListEnum+0xbc7

0019ecc0 013714e2   EqnEdt32!FMDFontListEnum+0x534

0019ecec 0138b463   EqnEdt32!FMDFontListEnum+0x24e

0019ee14 0138a8a0 EqnEdt32!MFEnumFunc+0xcc66

0019ee2c 0138a72f   EqnEdt32!MFEnumFunc+0xc0a3

0019ee44 013875da   EqnEdt32!MFEnumFunc+0xbf32

0019eea8 0137f926   EqnEdt32!MFEnumFunc+0x8ddd

0019eed8 01356a98   EqnEdt32!MFEnumFunc+0x1129

0019ef3c 755a04e8   EqnEdt32!AboutMathType+0x5a98

0019ef58 75605311   RPCRT4!Invoke+0x2a

0019f360 75ddd7e6   RPCRT4!NdrStubCall2+0x2d6

0019f3a8 75ddd876   ole32!CStdStubBuffer_Invoke+0xb6 [d:\w7rtm\com\rpc\ndrole\stub.cxx @ 1590]

0019f3f0 75ddddd0   ole32!SyncStubInvoke+0x3c [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @   1187]

0019f43c 75cf8a43   ole32!StubInvoke+0xb9 [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1396]

0019f518 75cf8938   ole32!CCtxComChnl::ContextInvoke+0xfa   [d:\w7rtm\com\ole32\com\dcomrem\ctxchnl.cxx @ 1262]

0019f534 75cf950a   ole32!MTAInvoke+0x1a [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 2105]

0019f560 75dddccd   ole32!STAInvoke+0x46 [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 1924]

0019f594 75dddb41   ole32!AppInvoke+0xab [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1086]

0019f674 75dde1fd ole32!ComInvokeWithLockAndIPID+0x372  

[d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx   @ 1724]

0019f69c 75cf9367   ole32!ComInvoke+0xc5 [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1469]

——————————————————————————-

0019ec58  cb ce cc e5 00                                   …..

ChildEBP RetAddr 

WARNING: Stack unwind   information not available. Following frames may be wrong.

0019eacc 013717c8   EqnEdt32!FMDFontListEnum+0xbc7

0019ebc4 01371980   EqnEdt32!FMDFontListEnum+0x534

0019ecec 0138b463   EqnEdt32!FMDFontListEnum+0x6ec

0019eda8 7545a24c   EqnEdt32!MFEnumFunc+0xcc66

0019ee08 0136775e   kernel32!GlobalUnlock+0xba

0019ee14 0138a8a0   EqnEdt32!EqnFrameWinProc+0x8c7e

0019ee2c 0138a72f   EqnEdt32!MFEnumFunc+0xc0a3

0019ee44 013875da   EqnEdt32!MFEnumFunc+0xbf32

0019eea8 0137f926   EqnEdt32!MFEnumFunc+0x8ddd

0019eed8 01356a98   EqnEdt32!MFEnumFunc+0x1129

0019ef3c 755a04e8   EqnEdt32!AboutMathType+0x5a98

0019ef58 75605311   RPCRT4!Invoke+0x2a

0019f360 75ddd7e6   RPCRT4!NdrStubCall2+0x2d6

0019f3a8 75ddd876   ole32!CStdStubBuffer_Invoke+0xb6

[d:\w7rtm\com\rpc\ndrole\stub.cxx   @ 1590]

0019f3f0 75ddddd0   ole32!SyncStubInvoke+0x3c [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @   1187]

0019f43c 75cf8a43   ole32!StubInvoke+0xb9

[d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx   @ 1396]

0019f518 75cf8938   ole32!CCtxComChnl::ContextInvoke+0xfa

 

[d:\w7rtm\com\ole32\com\dcomrem\ctxchnl.cxx   @ 1262]

0019f534 75cf950a   ole32!MTAInvoke+0x1a [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 2105]

0019f560 75dddccd ole32!STAInvoke+0x46   [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 1924]

0019f594 75dddb41   ole32!AppInvoke+0xab [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 1086]

Tue Dec 26 15:56:36.506 2017   (GMT+8): ModLoad: 74f90000 74fdc000     C:\Windows\system32\apphelp.dll

Tue Dec 26 15:56:36.584 2017   (GMT+8): (304.784): Access violation – code c0000005 (first chance)

First chance exceptions are   reported before any exception handling.

This exception may be expected   and handled.

eax=00000021 ebx=0019ece8   ecx=0019ec24 edx=771470f4 esi=00000001 edi=00190001

eip=01380c46 esp=0019ecd8   ebp=d32b8ac7 iopl=0         nv up ei pl   nz na po nc

cs=001b  ss=0023    ds=0023  es=0023  fs=003b    gs=0000             efl=00010202

EqnEdt32!MFEnumFunc+0x2449:

01380c46 c9              leave

 

从日志中可以看到存在两次拷贝,通过栈回溯我们可以知道这两次拷贝正是静态分析中对sub_21174的两次调用。第一次是sub_214c6sub_21174的调用,第二次是sub_21174对自身的调用。可以看到第一次拷贝时明显发生了栈溢出。这里稍微提一下,cb ce cc e5代表的是宋体。

我们来详细计算一下需要溢出多少长度才能控制父函数(sub_21174)的返回地址(这个问题的结论在“补丁绕过分析”一节已被提及),由图11可知,从lfFaceName(-0x90)溢出到ret_addr(+0x4),一共需要0x94字节,超出0x94部分的字节会逐个从低地址开始覆盖返回地址。

11.png

11

 

我们对照poc里面的数据来看一下,如图12所示,蓝色部分为溢出的前0x94字节,25 00 为溢出的最后两个字节,00为终止符,拷贝时遇到00就停止。按照小端地址布局,poc运行时,EIP只会被覆盖低位的2个字节。为什么这样做?答案是为了绕过ASLR

12.png

12

Bypass ASLR

我们来看一下为什么区区两个字节就可以绕过 ASLR

给TA买糖
共{{data.count}}人
人已赞赏
HackerNews

安全客2017季刊—第4期,这次让我们谈谈尺度问题

2018-1-9 11:04:50

HackerNews

2017全球僵尸网络DDOS攻击威胁态势报告

2018-1-10 11:24:08

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索