Windows图形库漏洞考察:逆向分析与模糊测试技术

 无国界 2019-07-03 

对于Windows应用程序和组件的漏洞,攻击者通常都会垂涎三尺。其中,Recorded Future进行的一项研究表明,2018年漏洞利用率最高的10个漏洞中有8个是Microsoft Office的漏洞。是不是觉得难以置信?实际上这一点也不奇怪,举例来说,如果我们考察Microsoft Excel实例启动时所加载的模块,会发现竟然多达91个DLL——还是那句老话,系统越复杂,越容易出现安全漏洞。此外,notepad.exe或calc.exe出现要想正常运行,也需要大约30-40个DLL。

Process Explorer:考察Microsoft Excel的相关模块

实际上,任何一个模块中出现安全漏洞,都会危及Microsoft Excel自身。其中一种潜在的攻击向量,就是向用户提供恶意文档,并设法让用户打开这些文档。因此,对于攻击者来说,只需找到一个含有安全漏洞的模块,就能攻陷整个应用程序,进而搞定整个系统。这是攻击者的最大优势,他们拥有充足的时间,可以全面深入的研究目标,因此,通常总能找到相应的攻击方法。而对于系统安全来说,其安全性取决于系统中最薄弱的一个环节。

在本文中,我们通过一个具体的例子,为读者演示如何通过比对原始程序与打过补丁后的程序之间的差异和逆向工程来分析导致Microsoft相关的漏洞的根源,并展示让含有相关漏洞的应用程序发生崩溃所需的具体步骤——这对于安全研究人员来说是非常有帮助的,因为应用程序崩溃是成功利用漏洞的重要一步。

具体来说,这里使用的漏洞是CVE-2019-0618,这是一个Windows GDI+组件的远程执行代码漏洞。在某些情况下,将多个普通漏洞组合在一起也足以搞定整个系统。鉴于此,我们认为CVE-2019-0618很可能会成为一个被广泛利用的漏洞,因为它可用于在感兴趣的目标系统上执行恶意软件。有关攻击者使用的恶意软件免杀技术的详细概述,请参阅另一篇文章。

Windows GDI+

GDI+是一种基于C/C++类的API,使应用程序能够在视频显示器和打印机上使用图形和格式化文本。实际上,基于Microsoft Win32和Win64 API的应用程序是无法直接访问图形硬件的。相反,应用程序要想与设备驱动程序进行交互,必须借助于GDI+。另外,由于GDI+可用于所有基于Windows的应用程序,使得GDI+ API已经成为显示Windows表单并管理其内容的主要工具。

在撰写本文时,ZDI已经公布了9个针对GDI+的漏洞,其中包括本文所研究漏洞。从公布的ZDI报告来看,本文研究的是 一个基于堆的缓冲区溢出漏洞,它能够导致远程代码执行。同时,根据该报告来看,该漏洞的CVSSv2得分为9.2,同时还指出,该漏洞位于DoRotatedStretchBlt方法中,这对于我们的分析工作来说是一个非常有用的线索。除此之外,它没有公开披露与我们的调查相关的其他信息,也没有通过针对这个漏洞的概念证明(PoC)代码。

相关代码在打补丁前后的变化情况

首先,我们想了解漏洞是如何进行修复的。基于此,我们可以推断出漏洞的大致情况。为此,我们必须通过比较DLL打补丁后的版本与此前的版本的变化情况,从而缩小需要关注的代码的范围;具体的比对方法,请参考另一篇文章中的详细介绍。简而言之,我们需要将打补丁后的Windows 7 x86 GdiPlus DLL与未打补丁的Windows 7 x86 GdiPlus DLL进行比对。

Gdiplus.dll
Gdiplus.dll:未打补丁(左)vs已打补丁(右)

通过考察相关代码在打补丁前后的变化情况,我们可以推断出许多有用的信息。例如,观察打过补丁的方法的数量,可以推测该特定更新的重要性。通过观察和比较单个模块的连续补丁,可以帮我们了解其开发过程,例如特定组件中出现的问题/漏洞的类别等。同时,这种方法还可以帮助安全研究人员识别模糊测试的目标,以及哪些功能更可能存在漏洞。

利用IDA插件BinDiff,我们可以对比二进制代码在打补丁前后的变化情况,具体如下所示:
BinDiff的输出结果
BinDiff的输出结果:比较GdiPlus.dll在打补丁前后的变化情况

通过比较发现,DoRotatedStretchBlt方法确实发生了一些变化,并且这里还具有较高的置信度。下面,让我们详细看看圈起来部分的变化情况。
BinDiff的输出结果
BinDiff的输出结果:gdiplus!DoRotatedStretchBlt方法,已打补丁与未打补丁的情况

BinDiff插件的颜色的含义:

绿色节点表示在两个可执行文件中具有相同指令的基本块;

红色节点表示比较算法无法找到等价物的基本块;

黄色节点表示算法可以找到等价物的节点,但是在不同版本中,某些指令已经发生了变化。

在我们的例子中,我们可以看到打过补丁的DLL中有一些红色基本块,所以,我们可以使用IDA Hex-Rays反汇编程序来进一步查看实际的差异。

IDA的输出结果
IDA的输出结果:GdiPlus!DoRotatedStretchBlt方法,已打补丁的版本(左)vs未打补丁的版本(右)

通过观察这个补丁,可以看出它处理的是一种典型的安全问题——使用memcpy时没有进行适当边界检查。该补丁似乎是用于处置Size参数的不同值的,以避免发生溢出。奇怪的是,并非所有的情况都得到了相应的处理。这是因为,与Windows补丁程序的情况一样,补丁程序不会出现在漏洞所在的确切位置。进一步的研究表明,该补丁程序的另一部分位于StretchBLT记录的处理程序中:

IDA的输出结果
IDA的输出结果:GdiPlus!_bHandleStretchBlt方法,已打补丁的版本(左)vs未打补丁的版本(右)

我们注意到,除了GdiPlus!pfnIsValidEnhMetaRecordOffExt方法(该方法也存在于未打补丁的程序库中)之外,该补丁程序中还含有另一个验证方法,即GdiPlus!bValidateAndExpandBuffers方法。后者的作用是清理Src缓冲区(pjBits)和Ubytes(v21)参数。然后,如果满足某些条件的话,DoStretchBlt将调用存在漏洞的函数。

IDA的输出结果:GdiPlus!DoStretchBlt方法

IDA的输出结果:GdiPlus!DoStretchBlt方法

如果针对hdc_flag的AND运算的结果为true,则调用相关的函数。变量hdc存放的是一个Windows设备上下文的引用。而所谓的设备上下文其实就是一种结构体,用于定义一组图形对象及其相关属性,以及影响输出的图形模式。在我们的例子中,图形对象包含一个用于剪切、喷涂和绘制操作的区域。

总之,要利用这个漏洞,需要控制DoRotatedStretchBlt方法的Src和Ubytes参数。所以,我们首先必须设法满足这一要求。了解如何访问该函数的最佳方式,就是使用IDA的调用图功能。
针对gdiplus!DoRotatedStretchBlt函数的调用图
针对gdiplus!DoRotatedStretchBlt函数的调用图

如您所见,我们可以通过调用不同的处理程序(例如,bHandlePlgBlt或bHandleSetDIBitsToDev)来执行DoRotatedStretchBlt操作。这些似乎是不同EMF记录的处理程序。我们的问题是如何处理不同的EMF记录?

了解EMF格式

增强型图元文件格式(Enhanced metafile format,EMF)是一种用于存储图形图像的可移植文件格式。EMF元文件中存放的是顺序记录,这些记录经过解析和处理后,可以在任何输出设备上显示文件中存储的图像。这个程序库必须能够以某种方式解释这些记录的类型,这样,当处理EMF文件时,就能为其提供正确的处理程序。在二进制文件中进行快速搜索,就能找到_pdofnDrawingOrders方法:

GdiPlus!pdofnDrawingOrders存放每个记录处理程序的引用
GdiPlus!pdofnDrawingOrders存放每个记录处理程序的引用

我们可以发现,这里列举了所有受支持的记录,包括我们的bHandleStretchBlt记录处理程序。下面,我们将设法触发DrawingOrder操作。
IDA:GdiPlus!bParseWin32Metafile方法
IDA:GdiPlus!bParseWin32Metafile方法

GdiPlus!bParseWin32Metafile方法将解析WMF文件头部,如果其中存在有效的EMF记录,它将通过从_pdofnDrawingOrders调用正确的处理程序来解析它们。随后,该解析方法会被GdipConvertEmfToWmFBits调用。除此之外,我们没有发现针对该方法的其他交叉引用,这表明必定存在一些外部API。通过阅读Win GDI+文档,发现它似乎不推荐使用GdipConvertEmfToWmFBits,所以我们在Metafile类中为其提供了一个封装器,即Metafile::EmfToWmfBits.。目前来看,我们似乎胜利在望了,至少已经看到了曙光。

让我们先暂停前进的步伐,回顾一下前面讲过的内容:

· 易受攻击的方法gdiplus!DoRotatedStretchBlt是由gdiplus!DoStretchBlt调用的,而后者是用于处理EMF_STRETCHBLT记录类型的。

· 如果为设备句柄设置了某个标志(值4),则会调用易受攻击的方法。

· 我们最终可以通过触发Metafile::EmfToWmfBits操作来调用易受攻击的方法,其中一个参数是包含EMF_STRETCHBLT记录的EMF文件。

现在,我们已经通过逆向分析获得了足够的信息,所以逆向工作可以告一段落了;接下来,我们要做的事情是,通过模糊测试找到一个能够导致易受攻击的方法发生崩溃的EMF文件,同时也希望能找到其他让人感兴趣的东西。


模糊测试的考虑事项

1.选择fuzzer

对于Windows二进制文件来说,当前最先进的模糊测试框架是WinAFL和Peach Fuzzing Framework。在本文中,我们将使用WinAFL,因为它是一种基于突变的、覆盖率反馈驱动型的模糊引擎。用于Linux应用程序的AFL的详细介绍,请参阅这篇文章。对于插桩技术,我们采用的是DynamoRio,这是一种运行时检测框架,能够在块覆盖率模式下运行。


2.创建测试工具

现在,必须创建一个使用易受攻击的GdiPlus.dll库的Windows GUI应用程序。最终的测试工具看起来像这样:

用于测试GdiPlus.dll库的测试工具
用于测试GdiPlus.dll库的测试工具

首先,我们会读取作为参数提供的EMF文件。然后,我们将启动一个EmfToWmfBits操作(将第136行),这将有望触发该漏洞。接着,触发针对EMF文件的Metafile::EmfToWmfBits操作后,通过释放使用过的组件并关闭打开的文件来清理环境。


3.样本的生成

为了得到一个包含EMR_STRETCHBLT记录的EMF文件,我们专门研究了WinGDI和GDI+文档,并找到了一个简单的生成器,具体如下所示:

用于生成包含EMR_STRETCHBLT记录的EMF文件的生成器
用于生成包含EMR_STRETCHBLT记录的EMF文件的生成器

我们还在初始语料库中的EMF文件中通过了其他记录类型以提高覆盖率,以增加导致其崩溃的机会。


4.语料库最小化与覆盖率

对模糊测试进行优化时,其中一个重要步骤就是语料库的最小化。该过程将删除无法显著改善代码覆盖率的样本,以防止在这些样本上浪费CPU周期。利用winafl-cmin.py脚本(它是WinAFL存储库的一部分)处理最初的32个样本的EMF语料库后,最终找到了10个重要的样本:

通过winafl-cmin.py脚本实现语料库最小化
通过winafl-cmin.py脚本实现语料库最小化

这里要做的是,在DynamoRio的运行时检测模式下,通过测试工具运行所有初始测试用例,同时,在每次运行时捕获感兴趣的库中已到达的基本块。

使用IDA的Lighthouse插件,可以查看最终语料库是否具有良好的覆盖率:

IDA Lighthouse插件:GdiPlus.dll的代码覆盖率
IDA Lighthouse插件:GdiPlus.dll的代码覆盖率

如您所见,我们的目标GdiPlus!GdipEmfToWmfBits具有很好的覆盖率,同时,程序库的大部分代码也具有很好的覆盖率。


5.最终结果

经过1天21小时的测试后,我们找到了80多个库崩溃:

WinAFL的运行结果
WinAFL的运行结果

使用BugID对它们进行分类,发现有多个样本能导致我们感兴趣的方法发生崩溃:

BugID的测试报告:GdiPlus!DoRotatedStretchBlt中可能存在RCE漏洞

使用崩溃样本启动WinDbg实例,结果:


WinDbg的输出:通过模糊测试获得的示例,触发与CVE-2019-0618相对应的崩溃;绿框内是stacktrace数据,红框内是崩溃分析数据

通过类似的方式,我们获得了与CVE-2019-0614、CVE-2019-0618和CVE-2019-0619相关的库崩溃。此外,我们在模糊测试过程中发现了一些其他方面的库漏洞,并将这些漏洞也提交给了微软。

 证书咨询

 电话咨询  在线咨询  预约报名