-
-
[翻译]CVE-2018-0797—RTF样式表释放后重用漏洞的根源分析
-
发表于: 2018-4-16 17:28 4152
-
过去几个月,微软安全响应中心(MSRC)发布了多个Windows更新来修复FortiGuard实验室发现的多个UAF漏洞。正如我们先前的博客所述,我们将对其中一个被MSRC归类为critical级别的UAF漏洞写一篇技术分析。这个漏洞是CVE-2018-0797。在这篇博客中,我们将分享一些我们定位这个漏洞的根源所用到的方法,同时也将分析一下微软对这个UAF的修复方式。
请注意,接下来的分析基于运行在32位Windows7操作系统上的Microsoft Word 2010。
漏洞研究人员都知道,安全审计十分复杂,尤其对于代码量很大的软件。更具挑战性的是微软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使用下面步骤产生的。
显然,生成的调用图十分复杂,因为默认情况下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];
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!