导语:在这篇文章中,我们分享了使用多个开源工具分析大型二进制文件的方法,分析了CVE-2018-0797漏洞的根本成因。
过去的几个月,微软安全响应中心(MSRC)发布了许多Windows更新,以修复FortiGuard实验室发现的多个Use-After-Free(UAF)漏洞。正如之前的博文所述,我们将为MSRC评定为严重的一个UAF漏洞提供技术报告,该漏洞ID号CVE-2018-0797。在这篇博文中,我们将分享确认漏洞根源的方法,以及分析微软发布的用于解决UAF漏洞的缓解措施。
请注意,以下分析是在运行Windows 7 32位的Microsoft Word 2010环境中进行的。
一、站在巨人的肩膀上——使用增强型AlleyCat,Lighthouse和BinDiff进行差异分析
漏洞研究人员应该知道,安全审计非常复杂,尤其是对于那些代码量巨大的软件。更具挑战的是,Microsoft Office不提供调试符号(这有助于识别已解析的函数名及其参数或本地变量)。因此,需要在逆向工程上花费比平常更多的时间来了解Microsoft Office补丁。
幸运的是,可以使用一些便利的工具来帮助我们减少分析大型二进制文件时可能遇到的一些困难。OK,让我们开启分析之旅。请注意,以下差异分析是在wwlib.dll 14.0.7191.5000和14.0.7192.5000之间进行的。
首先,让我们看看下面的BinDiff截图,它比较了wwlib.dll已修补和未修补版本:
图1: BinDiff 比较了wwlib.dll 14.0.7191.5000 vs 14.0.7192.5000
正如从BinDiff输出中看到的那样,补丁中有很多更新。实际上,可以看出比对结果的可信度较低,这表明BinDiff可能会对分析的某些结果产生误报。换句话说,我们不能仅仅依靠BinDiff来找到与我们的UAF漏洞相关的补丁函数。所以需要解决的困难如下:
· 将范围缩小到感兴趣的函数
· 查找并分析将我们引向存在漏洞函数的代码路径
感谢Embedi的博客,我们了解了由devttys0开发的名为AlleyCat的优秀IDA Pro脚本,它使IDA Pro能够自动查找两个或更多函数之间的路径。毫无疑问,这个脚本可以帮助我们解决第二个困难,但它仍然有其局限性,这是第一个困难引入的。一张图片胜过千言万语:
图2: 漏洞函数的调用图
图2所示的调用图是使用AlleyCat执行以下步骤生成的:
·首先,在有漏洞的Microsoft Word版本中运行我们的RTF POC文件,触发漏洞函数,该函数由红色框突出显示
·在漏洞函数wwlib_cve_2018_0197中,运行AlleyCat视图 – >图形 – >从当前函数查找路径… – >选择FMain – >确定
显然,生成的调用图很复杂,因为默认情况下,AlleyCat包含从入口点FMain开始的与wwlib_cve_2018_0197有直接和间接相关的所有函数。我们的最终目标是找到仅与POC相关的函数。好消息是,这可以通过代码覆盖轻松完成。基本上,可以使用DynamoRio的DrCov插件生成代码覆盖。我们需要做的是使用DynamoRio套件中的相应命令行工具(如drrun.exe)与我们的POC文档在有漏洞的Microsoft Word中一起执行。这样,drrun.exe将生成覆盖文件。然后,我们可以从Lighthouse插件获取解析器代码来解析由DrCov插件生成的覆盖信息,以此信息为过滤,将其用于之前收集的第一个调用图。最后,我们得到以下精致的调用图:
图3: 漏洞函数的改进调用图
正如在图3中看到的,我们已经标注了应该调查的函数。甚至可以使用BinDiff来确定哪个更新函数能够解决此特定漏洞。这个改进调用图的另一个好处是它允许我们只关注与POC相关的函数。例如,我们可以在回溯后快速识别函数sub_31B22D39和sub_31B25BD5中的RTF解析例程,这为我们节省了大量的分析时间。最重要的是,Lighthouse提供的覆盖绘画功能也使静态分析更加容易。
二、Stylesheet控制字结构
在深入探讨漏洞细节之前,我们需要先了解一下RTF stylesheet数据结构,这有助于解释漏洞。正如前面所提到的,由于没有wwlib.dll的调试符号,我们只能通过逆向工程识别其数据结构。
进一步挖掘,我们能够确定stylesheet数据结构。假设有一个RTF文件,其定义了以下控制字:
图4: 第一个例子——RTF文档只有stylesheet控制字
使用WinDBG调试时,可以在以下内存中看到其原始数据:
图5: stylesheet对象首部
图6: 首部之后的指针数组
根据内存dump的输出,我们知道在示例RTF中定义的styledef控制字在内存地址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的数组用于存储不重要的缺省styledef指针。相反,我们感兴趣的是索引15及以上的数组,它们通常由RTF文档中定义的styledef指针(\ s1,\ s2和\ s3)组成(如图4所示)。我们也尝试研究这些数组中原始数据的含义。不幸的是,我们无法完全理解pStyleDefs的完整数据结构,但其部分含义足以帮助我们理解漏洞。所以我们开始使用styledef中的控制字,并将第一个示例RTF修改为:
图7: 第二个RTF文档示例具有\sbasedonN控制字
基本上,在第二个RTF示例中添加了\ sbasedon1控制字。正如Microsoft RTF规范中所说的,\ sbasedonN控制字定义了当前stylesheet的基础stylesheet的句柄。简而言之,我们告诉\ s2它应该从\ s1继承样式。请注意pStyleDefs [16]中的更改。记住,第一个styledef \ s1的数组索引为15,第二个styledef \ s2的数组索引为16,位于内存dump中偏移量2处:
图8: 添加\sbasedonN 之后pStyleDefs[16]的数据
通过分析stylesheet分析例程,知道可以从位于偏移量2的styledef指针获得styledef索引。如图8所示,位于地址0xf6e0790的偏移量2处的值表示在\ sbasedonN控制字中指定的pStyledefs的数组索引,将该值右移4位(0xF1 >> 4)后为0xF。注意\ sbasedon1指的是\ s1。因此,重要的是要注意数组pStyleDefs的索引始终以0xF或十进制15开头,用于第一个styledef控制字,如RTF文档中使用的\ sN,\ sbasedonN和\ slinkN。
CVE-2018-0797 UAF漏洞的根本原因
UAF是指允许攻击者在内存释放后继续访问的漏洞,这可能导致程序崩溃,允许执行任意代码,甚而导致远程代码执行。
实际上,只需要很少的时间就能找到CVE-2018-0797 UAF漏洞的修补程序——增强版AlleyCat脚本的功劳!在缩小了想要查看的函数范围之后,使用BinDiff来查看补丁,注意到发生崩溃的那个更新函数wwlib_cve_2018_0797添加了代码块。
图9: 更新的wwlib_cve_2018_0797函数中添加的代码
这次我们很幸运,因为修复与崩溃发生在同一函数上。根据过去的经验,补丁通常位于不同的函数中,大多数时候研究人员需要花一些时间来寻找和定位补丁。当然,有时它也取决于漏洞的性质。无论如何,发现增强了我们的信心,我们可以从这个函数中揭示UAF的根源。
在分析补丁函数后,我们意识到添加代码块的目的是确保循环始终获取更新的pStyleDefs指针。当遇到\ sbasedonN时,会更新pStyleDefs指针分配更多空间,存储从其继承的styledef的其他信息。在底层,Microsoft Word用更大的缓冲区调用堆重新分配函数来替换pStyleDefs指针。要说清楚的是,这里没有空悬的指针。但在修复之前,只要发生堆重新分配,就会释放旧的pStyleDefs指针。因此,当一个函数试图将一个style sheet链接到另一个时,如清单1中的(2)所示,当返回给调用者时,它仍然保存着旧的pStyleDefs指针。如稍后(1)所示,当pStyleDefs之后在漏洞函数内的某个地方被引用时,就会发生UAF。作为一种解决方法,补丁函数通过添加代码块来确保更新相应地pStyleDefs指针,该代码块始终将更新后的pStyleDefs指针返回给调用方,如清单1中突出显示的代码所示。
清单1:打补丁后wwlib_cve_2018_0797的伪代码
接下来的问题是如何触发wwlib_cve_2018_0797。经过一些更深入的分析后,我们意识到RTF解析器初始化并维护styledef索引的参考表。例如,图7中的示例RTF有一个类似于表1的styledef参考表:
表1: 图7中RTF的Styledef参考表
事情是这样的:在RTF解析器中有一个条件检查,用于确定当前styledef索引的完整性以及参考表中的styledef索引。实际上,确定此参考表完整性检查的确实难度很大,但是当当前的styledef索引与参考表中初始化的styledef索引不匹配时,解析器例程会尝试重新构造stylesheet数据结构,最终触发wwlib_cve_2018_0797。有多种方式可以导致这种不匹配,典型的方法是通过在\ sN控制字中定义N = 0或者是RTF文件中的其他\stylesheet控制字中定义:
图10: RTF文档示例——其他stylesheet控制字中
图10中的示例RTF生成的新styledef参考表如下所示:
表2: 图10中RTF的Styledef参考表
请注意,如表2所示,参考索引从0开始,因为在更新的示例RTF中定义了\ s0。需要注意的是,当RTF解析器解析第二个\ stylesheet控制字时,对当前的styledef(\ s4)而言,其参考索引为0(被认为是\ s0的参考索引),而不是4。RTF解析器查询参考表以获取当前styledef的索引,将返回0xF,但实际上当前styledef索引(\ s4)值为0x14。由于此差异,就会引发漏洞。
总之,以下情况下可以触发UAF:
· 定义了多个 \stylesheet 控制字,其中一个stylesheet控制字必须强制使RTF解析器初始化引用表中的引用索引0。
· \ sbasedonN控制字触发堆重新分配,扩展存储在数据结构中的stylesheet属性。数据结构扩展导致stylesheet对象的初始指针在漏洞函数中不被引用时被释放且无效,这样就可以从此无效指针访问某些数据。
总结
总之,在这篇文章中,我们分享了使用多个开源工具分析大型二进制文件的方法,这些工具可帮助我们将分析时间从几周缩短到几天。在了解这个开源代码的实现的同时,我们也发现了一些问题和限制,所以我们将竭诚为这些开源工具提供修复和功能,将我们的更新回馈给开源社区。最后,我们就这个漏洞的根本原因提供了一些见解。根据我们的分析,假设设计允许复制stylesheet控制字;这就是为什么当使用第二个stylesheet控制字时,Microsoft并未尝试缓解styledef引用表中styledef索引的错位。这值得进一步研究,看看是否可能导致其他潜在的漏洞。