首页
社区
课程
招聘
[翻译]CVE-2018-0797—RTF样式表释放后重用漏洞的根源分析
2018-4-16 17:28 3656

[翻译]CVE-2018-0797—RTF样式表释放后重用漏洞的根源分析

2018-4-16 17:28
3656

CVE-2018-0797—RTF样式表释放后重用漏洞的根源分析


        过去几个月,微软安全响应中心(MSRC)发布了多个Windows更新来修复FortiGuard实验室发现的多个UAF漏洞。正如我们先前的博客所述,我们将对其中一个被MSRC归类为critical级别的UAF漏洞写一篇技术分析。这个漏洞是CVE-2018-0797。在这篇博客中,我们将分享一些我们定位这个漏洞的根源所用到的方法,同时也将分析一下微软对这个UAF的修复方式。

        请注意,接下来的分析基于运行在32位Windows7操作系统上的Microsoft Word 2010。

站在巨人的肩膀上——使用增强版AlleyCat,Lighthouse和BinDiff进行差异化分析

        漏洞研究人员都知道,安全审计十分复杂,尤其对于代码量很大的软件。更具挑战性的是微软office并不提供调试符号,这些符号可帮助识别函数名,它们的参数或者任何局部变量。所以,大部分时候,了解微软office的补丁比起通常情况需要更多逆向工程方面的努力。

幸运的是,在分析一个庞大的二进制文件时,我们可以使用一些方便的工具来帮助减少所遇到的一些挑战。闲话少说,让我们开始分析。请注意下面的差异化分析基于wwlib.dll 14.0.7191.5000 和 14.0.7192.5000。

        首先,我们来看一下BinDiff对打过补丁和未打补丁的wwlib.dll的比较截图:

图 1: wwlib.dll 14.0.7191.5000 和14.0.7192.5000的BinDiff结果

        从BinDiff的结果可以看到,这个补丁里有很多更新。事实上,差异比较结果的可信度是较低的,这意味着在这个分析中,BinDiff可能在一些结果上产生错误的告警。换句话说,我们不能仅依赖BinDiff去寻找与我们的UAF漏洞相关的补丁函数。所以下面是我们需要解决的挑战:

  • 把函数范围缩小到我们感兴趣的那些
  • 找到并分析引导我们走向漏洞函数的代码路径

        感谢Embedi的博客,我们学到了一个非常棒的叫做AlleyCat的IDA Pro脚本,脚本由devttys0开发,它可以让IDA Pro自动找到两个或多个函数间的路径。毫无疑问,这可以帮助我们解决第二个挑战,但是它仍有自身的局限,也就是第一个挑战。一图胜千言:

图 2: 漏洞函数调用图

        图2所展示的调用图是借助AlleyCat使用下面步骤产生的。

  • 首先,我们在漏洞版本的Microsoft Word上运行我们的RTF POC文件来触发漏洞函数,该函数在图2被红框高亮
  • 选中wwlib_cve_2018_0197漏洞函数,执行AlleyCat View -> Graphs -> Find paths to the current function from … -> Pick FMain -> OK

        显然,生成的调用图十分复杂,因为默认情况下AlleyCat包含了从FMain入口开始,与wwlib_cve_2018_0197直接或者间接相关的所有函数。我们的最终目标是找到只与我们的POC相关的函数。好消息是这可以通过控制代码覆盖轻松做到。基本上,代码覆盖可以通过使用DynamoRio的DrCov插件得到。我们所需要做的就是在DynamoRio的工具包里使用合适的命令行工具,比如drrun.exe,来一起运行有漏洞的Microsoft Word和我们的POC文件。随后drrun.exe就会生成一个代码覆盖文件。然后,我们可以用Lighthouse插件的解析器代码来解析DrCov插件产生的覆盖信息,使用这个信息作为过滤器,然后把它应用到我们之前得到的第一个调用图。最后,我们得到以下精简后的调用图:

图 3: 精简的漏洞函数调用图

        如图3所示,我们已经减少了要研究的函数个数。我们现在甚至可以用BinDiff来找出哪(些)个更新函数修复了这个漏洞。这个精简的调用图的另一个好处是,它让我们可以把注意力放在只与我们的POC相关的函数上。例如,通过一些回溯后,我们可以在函数sub_31B22D39 和sub_31B25BD5中快速识别RTF解析程序,这节省了我们很多分析时间。最重要的是,Lighthouse提供的函数覆盖图的功能也会让我们的静态分析更容易。

        (补充说明一下,在用Lighthouse调用DorCov解析器代码的时候,我们发现了一个无关紧要的bug,它会在某些情况下导致覆盖输出不一致。我们随后修复了这个问题并且把更新提交到了Lighthouse的上游分支。如果你对这个bug的具体细节感兴趣,你可以在这里看到它的描述。然而,我们不会在这里提供我们的增强版AlleyCat源代码,因为把DrCov的解析器功能和通过代码覆盖进行函数过滤的实现集成到AlleyCat非常简单。)

样式表控制字结构体定义

        在深入研究漏洞的细节之前,我们需要首先了解RTF样式表的数据结构,这将有助于解释背后的漏洞。正如我们之前提到的,因为没有wwlib.dll的调试符号,所以我们只能通过逆向工程来识别其数据结构。

        通过进一步挖掘,我们能够确定样式表的数据结构。例如我们有一个定义了如下样式表控制字的RTF文件:

图 4:只包含样式表控制字的第一个RTF文件例子

        当使用WinDBG调试器是,它的原始数据可以在如下的内存转储中看到:

图 5: 样式表对象头的定义


图 6: 跟在对象头后面的样式表指针数组

        通过内存转储输出,我们了解到,我们定义在RTF里的样式表控制字,在内存地址0x1022af60处被存储为指针变量(0x5338768, 0xfdf2768 和 0xfb00768)。简而言之,我们可以把这个内存结构表示为下面的C语言数据结构。

    struct _strucStyleSheet{

        DWORD dwCountStyles;

        DWORD dwTotalStyles;

        DWORD dwSizeofPtr;

        DWORD dwSizeofHeader;

        DWORD dwUnknown1;

        DWORD dwUnknown2;

        void *pUnknown;

        DWORD dwUnknown3;

        void *pStyleDefs[dwTotalStyles];

    }strucStyleSheet;

        从内存布局中可以很容易地明白,pStyleDefs数组中下标0到14被用来存储默认的样式定义指针,在本文中里它们并不重要。我们感兴趣的是数组中下标大于等于15的,它们通常由RTF文档(如图4所示)里定义的样式定义指针(\s1,\s2和\s3)组成。我们也尝试去逆向这些数组指针指向内存的原始数据的意义。不幸的是,我们无法彻底解释pStyleDefs全部的数据结构,但是它的部分定义似乎已足够帮助我们了解背后的漏洞。所以我们开始围绕样式定义里的控制字展开,并且把第一个RTF示例修改成:

图 7: 带有\sbasedonN控制字的第二个RTF文件示例

        我们在第二个RTF例子里增加了\sbasedon1控制字。引用微软RTF格式规范的描述,\sbasedonN控制字定义了一个当前样式基于的样式句柄。简言之,我们告诉\s2,它应当继承\s1的样式。我们应该能注意到pStyleDefs[16]的变化。记住第一个样式定义\s1的数组下标为15,第二个样式定义\s2的数组下标为16,在内存转储的第2个偏移处:


图 8: 添加\sbasedonN控制字后pStyleDefs[16]的数据

        通过分析样式表解析程序,我们了解到继承的样式定义索引可以在样式定义指针偏移2处获取到。如图8所示,地址0xf6e0790的偏移2处的值,代表了pStyledefs的数组下标,由\sbasedonN控制字指定,这个值在右移4位后变成了0xF(0xF1 >> 4)。你可能会注意到,\sbasedon1指的是\s1。因此需要注意到pStyleDefs的数组下标总是从0xF,或者十进制的15开始,对应RTF文件里第一个样式定义控制字\sN, \sbasedonN, 和 \slinkN,这一点很重要。

CVE-2018-0797 UAF漏洞的根源分析

        UAF是指那种允许攻击者在内存被释放后再访问内存的漏洞,这可以导致程序崩溃,允许任意代码执行,甚至允许完整的远程代码执行。

事实上,定位CVE-2018-0797 UAF漏洞的补丁几乎没有花费我们的时间,这得益于我们的增强版AlleyCat脚本。在缩小了我们想要查找的函数的范围后,我们使用BinDiff来查看更新后的版本,并且发现在之前发生崩溃的函数,wwlib_cve_2018_0797,多了一个代码块。

图 9: 更新后的updated wwlib_cve_2018_0797函数增加的代码块

        这次我们很幸运,因为这个修复在崩溃发生的同一个函数内。基于我们过去的经验,补丁总是被放在不同的函数里,大部分时候,研究人员需要花费一些时间来找到并定位它。当然,有时候它也由漏洞的特性决定。无论如何,我们的发现又一次增强了我们的信心,我们一定可以从这个函数里揭开UAF漏洞的根本原因。

在分析补丁函数后,我们发现被增加的代码块的目的是保证循环总能拿到更新后的pStyleDefs指针。当遇到\sbasedonN的时候,pStyleDefs指针会被更新以分配更多的空间,以此来存储它继承的样式的额外信息。在底层,Microsoft Word传入一个更大的缓冲区去调用堆重分配函数,来替换pStyleDefs指针。确切地说,这里没有出现垂悬指针。但在修复前,不管堆重分配何时发生,旧的pStyleDefs指针都会被释放。因此,当一个函数尝试把一个样式与另一个联系在一起时,如列表1的标号(2)处所示,当函数回到调用者时,这个指针还是旧的pStyleDefs指针。之后,当pStyleDefs 在调用者函数的某个地方再次被引用时,UAF就会发生,如标号(1)处所示。作为解决方案,漏洞函数通过增加一个代码块来总是返回更新后的pStyleDefs指针给调用者,以确保pStyleDefs指针相应得到更新,如列表1高亮的代码所示。

列表 1: 打了补丁后的wwlib_cve_2018_0797伪代码

        下一个问题是如何触发wwlib_cve_2018_0797。经过深入分析,我们发现RTF解析器为样式定义索引初始化并维护了一个引用表。比如,图7的示例RTF有一个如表1的样式定义引用表:

表 1: 图7的RTF的样式定义引用表

        是这样的,在RTF解析器里有一个条件检查,它用来确保当前样式定义索引和引用表里的样式定义索引的完整性。事实上,确定这个引用表完整性的检查目的对我们来说相当有挑战性,但据推测当前样式定义的索引与样式定义引用表里初始化的索引不匹配时,解析器程序就会尝试重新构造样式表的数据结构,这最终导致了wwlib_cve_2018_0797。有很多方法来造成这种不一致。一个经典的办法是通过在\sN控制字里定义N=0,同时在RTF文件里定义额外的\stylesheet控制字:

图 10: 带有额外的样式表控制字的RTF文件示例

        图10中的RTF示例导致了新的样式定义引用表,看起来如下面这样:

表 2: 图10的RTF 样式定义引用表

        请注意,因为在更新了的RTF示例里\s0的定义,现在引用索引以0开始,如表2所示。需要说明的是,当RTF解析器解析第二个样式表控制字时,对于当前的样式定义(\s4),它的引用索引是0而不是4, 索引0本应是\s0的引用索引。当RTF解析器轮询引用表来获取当前样式定义的索引时,样式定义索引0xF会被返回,而事实上当前样式定义(\s4)的索引值是0x14。这个差异可以使执行流进入漏洞函数。

        总结一下,满足以下条件时这个UAF可以被触发:

  • 多个样式表控制字被定义,并且其中一个必须强制导致RTF解析器在引用表里初始化引用索引0
  • \sbasedonN控制字触发堆的重新分配,以扩展存储在数据结构里的样式属性。数据结构的扩展导致了初始的样式对象指针被释放并变得无效,与此同时,为了访问这个无效指针里的一些数据,该指针仍然在漏洞函数里被解引用

结论

        综上所述,在这篇博客里,我们分享了使用多个开源工具分析一个庞大的二进制文件的方法,这些工具帮助我们将分析时间从数周减少到数日。我们在了解这些开源的代码实现的同时,也发现了它们的一些问题和限制,我们通过向这些开源工具提交修复补丁和新功能向开源社区贡献了更新。最后,我们给出了关于这个漏洞根本原因的一些见解。基于我们的分析,我们认为重复的样式表控制字在设计时就是被允许的,这也是为什么当第二个样式表控制字被使用时,微软并没有尝试去修正样式定义引用表里的样式定义索引错位问题。然而,它可能值得我们进一步探究,以看看这是否会导致其它潜在的漏洞。


译注:

原文地址:https://www.fortinet.com/blog/threat-research/a-root-cause-analysis-of-cve-2018-0797---rich-text-format-styles.html

本文由 看雪翻译小组 jdlxy 翻译,银雁冰 校对


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回