模糊化ImageIO

2020-05-12 15:05:28

这篇博客讨论了一个老问题,图像格式解析器中的漏洞,在一个新的(呃)上下文中:在流行的Messenger应用程序中的无交互代码路径上。本研究的重点是Apple生态系统及其提供的图像解析API:ImageIO框架。在图像解析代码中发现了多个漏洞,报告给了Apple或各自的开源图像库维护人员,并随后进行了修复。在这项研究中,为封闭源码二进制文件实现了一种轻量级、低开销的引导模糊方法,并与本文一起发布。

重申一点重要的是,本博客中描述的漏洞可以通过流行的信使访问,但不是其代码库的一部分。因此,修复它们不是信使供应商的责任。

在对流行的Messenger应用程序进行反向工程时,我在无需用户交互即可访问的代码路径上遇到了以下代码(手动反编译为objc并略微简化):

此代码对作为来自发送方的传入消息的一部分接收的二进制数据进行解密,并从该数据实例化一个UIImage实例。然后,UIImage构造函数将尝试自动确定图像格式。然后,将接收到的图像传递给以下代码:

此代码的目的是呈现输入图像的较小尺寸版本,以用作用户通知中的缩略图。不出所料,类似的代码也可以在其他Messenger应用程序中找到。本质上,上面所示的代码会将Apple的UIImage图像解析和CoreGraphics图像渲染代码变成0click攻击面。

从开发针对iMessage漏洞的攻击中获得的一个见解是,如果满足以下前提条件,则很可能会使用所描述的技术利用内存损坏漏洞进行攻击:

在这种情况下,例如,该漏洞可以用来破坏指向objc对象(或类似对象)的指针,然后构造一个崩溃的预言来绕过ASLR,然后获得代码执行。

在当前的攻击场景中,所有的前提条件都得到了满足,从而促使人们对暴露的图像解析代码的健壮性进行了一些研究。查看UImage的文档可以发现这样一句话:“您使用图像对象来表示各种类型的图像数据,UIImage类能够管理底层平台支持的所有图像格式的数据”。因此,下一步是准确确定底层平台支持哪些图像格式。

传递给UIImage的图像数据的解析是在ImageIO框架中实现的。因此,可以通过对MacOS上的ImageIO库(/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO或iOS上的DYLDSHARED_CACHE的一部分进行反向工程来枚举支持的映像格式)。

在ImageIO框架中,每种受支持的图像格式都有一个专用的IIO_Reader子类。每个IIO_Reader子类都应该实现一个testHeader函数,当给定一个字节块时,该函数应该决定这些字节是否表示读取器支持的格式的图像。下面显示了LibJPEG阅读器的testHeader实现的示例实现。它只是测试输入的几个字节,以检测JPEG报头的魔力。

因此,通过列出不同的testHeader实现,可以编译ImageIO库支持的文件格式列表。名单如下:

虽然这个列表包含许多熟悉的格式(jpeg、png、gif、…)。还有许多相当异国情调的游戏(KTX和ASTC,显然用于纹理或AI:Adobe Illustrator Artwork),还有一些似乎特定于苹果生态系统(ICNS用于图标,ATX可能用于Animojis)。

对不同格式的支持也各不相同。有些格式似乎完全受支持,并且通常使用开源解析库来实现,这些解析库可以在MacOS上的/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources中找到:libGIF.dylib、libJP2.dylib、libJPEG.dylib、libOpenEXR.dylib、libPng.dylib、libRadiance.dylib和libTIFF.dylib。对于处理最常见的情况,其他格式似乎只有基本的支持。

最后,一些格式(如psd)似乎也支持进程外解码(在MacOS上,这是由/System/Library/Frameworks/ImageIO.framework/Versions/A/XPCServices/ImageIOXPCService.xpc)处理的,这可以帮助解析器中的沙盒漏洞。然而,似乎不可能在公共API中指定应该在进程内还是进程外执行解析,并且没有尝试更改默认行为。

考虑到可用的图像格式范围很广,而且大多数代码都没有源代码可用,模糊处理似乎是显而易见的选择。

选择使用哪种模糊和模糊方法并不是那么明显。因为大多数目标代码不是开源的,所以许多标准工具不能直接适用。此外,为了简单起见,我决定将模糊功能限制在一台Mac Mini上。因此,绒毛器应该:

以尽可能少的开销运行,以充分利用可用的计算资源,并且。

最后,我决定在洪福兹的基础上自己实现一些东西。模糊方法的思想大致基于这篇论文:全速模糊:通过覆盖率引导的跟踪减少模糊开销。

枚举程序/库中每个基本块的起始偏移量。这是通过一个简单的IDAPython脚本完成的。

在运行时,在模糊化过程中,用断点指令(Intel上的int3)替换每个未发现的基本块的第一个字节。覆盖位图中的原始字节和对应的偏移被存储在专用影子存储器映射中,该映射的地址可以根据修改的库的地址来计算,并且。

检索出错地址并计算库中的偏移量以及影子存储器中相应条目的地址。

由于只检测未发现的基本块,并且每个断点只触发一次,因此运行时开销很快接近于零。然而,应该注意的是,该方法仅实现了基本块覆盖,而不是例如由AFL使用的边缘覆盖,并且对于封闭源目标,这可以通过动态二进制插装来实现,尽管具有一些性能开销。因此,它将更加“粗粒度”,例如,将到同一基本块的不同过渡视为相等,而AFL则不同。因此,在迭代次数相同的情况下,此方法可能会发现较少的漏洞。我认为这是可以接受的,因为本研究的目标不是彻底发现所有漏洞,而是快速测试图像解析代码的健壮性并突出攻击向量。在任何情况下,彻底的模糊总是由具有源代码访问权限的维护人员执行得最好。

通过修补honggfuzz的客户端工具代码并编写一个IDAPython脚本来枚举基本块偏移量,所描述的方法非常容易实现。补丁和IDAPython脚本都可以在这里找到。

然后,Fuzzer从一个由大约700个种子图像组成的小语料库开始,涵盖了支持的图像格式,并运行了几个星期。最后,发现了以下漏洞:

ImageIO对libTiff的使用中存在错误,导致控制数据写入超过内存缓冲区末尾。没有为这个问题分配CVE,可能是因为在我们报告之前,苹果内部已经发现了这个问题。

可能是PVR解码逻辑中的偏差-1错误,导致额外的像素数据行被写入超出输出缓冲器末端的边界。

PVR解码器中的相关错误导致越界读取,可能与1974年P0问题具有相同的根本原因,因此分配了相同的CVE编号。

最后一个问题有些特殊,因为它出现在与ImageIO捆绑在一起的第三方代码中,即OpenEXR库。由于该库是开放源码的,我决定将其单独模糊化。

OpenEXR是“一种高动态范围(HDR)图像文件格式[.]。用于计算机成像应用“。解析器是用C和C++实现的,可以在GitHub上找到。

如上所述,OpenEXR库通过Apple的ImageIO框架公开,因此通过苹果设备上各种流行的信使应用程序暴露为0click攻击面。虽然我还没有进行更多的研究来支持这一说法,但攻击表面很可能并不局限于即时通讯应用程序。

由于该库是开源的,所以“传统的”引导模糊更容易执行。我使用了一款谷歌内部的、覆盖导向的模糊器,在大约500个内核上运行了大约两周。模糊器以使用llvm的SanitizerCoverage的边缘覆盖为导向,通过使用常见的二进制突变策略对现有的图像进行突变,并从一组大约80张现有的openexr图像作为种子开始生成新的输入。

发现了8个可能的独特漏洞,并将其作为P0问题1987报告给OpenEXR维护人员,然后在2.4.1版本中进行了修复。下面对它们进行简要总结:

导致std::Vector被读出边界的错误。之后,调用代码将写入此向量的元素槽,因此可能会损坏内存。

一种越界memcpy,该memcpy正在读取越界数据,然后可能也会将其写入越界。

堆栈上的越界读取,可能是由于以前覆盖堆栈上的字符串空终止符时出现按一取一的错误。

有趣的是,ImageIO Fuzzer最初发现的崩溃(1988年版)似乎无法在上游OpenEXR库中重现,因此直接报告给了Apple。一种可能的解释是,苹果发布了一个过时版本的OpenEXR库,在此期间,该漏洞已被上游修复。

媒体格式解析仍然是一个重要的问题。其他研究人员和供应商建议也证明了这一点,以下两点立即浮现在脑海中:

当然,这表明输入解析器的持续模糊测试应该在供应商/代码维护人员端进行。此外,允许像ImageIO这样的库的客户端限制允许的输入格式,并有可能选择进程外解码,这有助于防止利用漏洞。

在信使端,一种建议是通过将接收器限制为少量受支持的图像格式(至少对于不需要交互的消息预览)来减少攻击面。在这种情况下,发送方随后会在将任何不支持的图像格式发送给接收方之前对其进行重新编码。在ImageIO的情况下,这将把攻击面从大约25种图像格式减少到几种或更少。

这篇博客文章描述了图像解析代码作为操作系统或第三方库的一部分,最终如何通过流行的信使暴露于0click攻击面。对暴露的代码进行模糊处理发现了许多新的漏洞,这些漏洞已被修复。很可能,只要付出足够的努力(以及由于自动重新启动服务而允许的攻击尝试),某些已发现的漏洞就可以在0click攻击方案中用于RCE。不幸的是,其他错误也很可能仍然存在或将在未来引入。因此,建议在操作系统库(在本例中为ImageIO)和Messenger应用程序(通过限制接收器上可接受的图像格式的数量)中对该代码和类似的媒体格式解析代码进行持续模糊测试,并积极减少攻击面。