Fuzzing ImageIO:iOS图像解析0click攻击

 

 

0x00 前言

这篇博客将讨论图像格式解析器这个老套的漏洞,以流行的Messenger应用无交互触发这一崭新的方式。这项研究的重点是Apple生态系统及其提供的图像解析API:ImageIO框架。研究发现了图像解析代码中的多个漏洞,并将其报告给Apple或相应开源图像库维护者,随后问题得以修复。在这项研究中,针对闭源二进制文件的轻量级、低开销的fuzzing方法得以实现和发布,并与本博文一起发布。

重申一个要点,通篇博客中描述的漏洞可以通过流行的Messenger来触发,但并不属于其代码库。因此Messenger开发商不负责修复这些漏洞。

 

0x01 背景介绍

在对流行的Messenger应用进行逆向工程时,在0click的情况下触发了以下代码逻辑(手动反编译为ObjC并稍作简化)。

NSData* payload = [handler decryptData:encryptedDataFromSender, ...];
if (isImagePayload) {
    UIImage* img = [UIImage imageWithData:payload];
    ...;
}

这段代码解密从发送方收到的二进制数据,并将其实例化为UIImage对象。然后UIImage构造函数将尝试自动确定图像格式,接着将接收到的图像传递给以下代码:

CGImageRef cgImage = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef cgContext = CGBitmapContextCreate(0, thumbnailWidth, thumbnailHeight, ...);
CGContextDrawImage(cgContext, cgImage, ...);
CGImageRef outImage = CGBitmapContextCreateImage(cgContext);
UIImage* thumbnail = [UIImage imageWithCGImage:outImage];

这段代码的目的是渲染缩小的原始图像作为通知用户的缩略图,可以想见在其他通讯应用中也可以找到类似代码。正是上述代码将Apple的UIImage图像解析和CoreGraphics图像渲染代码暴露给0click攻击。

通过iMessage漏洞exploit收获了一项经验,即如果满足下述先决条件便可以通过相仿的技术手段进行内存破坏exploit。

  1. 处理消息的进程具有自动发送回执
  2. 每次启动ASLR需要内存映射
  3. 自动重启服务

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

在当前攻击场景中所有先决条件都得到满足,因此驱使我对图像解析代码的健壮性进行研究。查阅UIImage文档,可以发现以下描述:“您使用图像对象来呈现各种图像数据,同时UIImage类能够管理底层平台支持的所有图像格式数据”。那么下一步便要明确底层平台支持哪些图像格式。

 

0x02 ImageIO框架介绍

在ImageIO框架中实现了解析传递到UIImage的图像数据。因此可以通过对ImageIO框架(在macOS上为/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO或在iOS上dyld_shared_cache的一部分)逆向工程来枚举支持的图像格式。

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

bool IIO_Reader_LibJPEG::testHeader(IIO_Reader_LibJPEG *this, const unsigned __int8 *a2, unsigned __int64 a3, const __CFString *a4)
{
  return *a2 == 0xFF && a2[1] == 0xD8 && a2[2] == 0xFF;
}

通过罗列不同的testHeader实现可以汇集出ImageIO库支持的文件格式列表。列表如下:

IIORawCamera_Reader::testHeader
IIO_Reader_AI::testHeader
IIO_Reader_ASTC::testHeader
IIO_Reader_ATX::testHeader
IIO_Reader_AppleJPEG::testHeader
IIO_Reader_BC::testHeader
IIO_Reader_BMP::testHeader
IIO_Reader_CUR::testHeader
IIO_Reader_GIF::testHeader
IIO_Reader_HEIF::testHeader
IIO_Reader_ICNS::testHeader
IIO_Reader_ICO::testHeader
IIO_Reader_JP2::testHeader
IIO_Reader_KTX::testHeader
IIO_Reader_LibJPEG::testHeader
IIO_Reader_MPO::testHeader
IIO_Reader_OpenEXR::testHeader
IIO_Reader_PBM::testHeader
IIO_Reader_PDF::testHeader
IIO_Reader_PICT::testHeader  (macOS only)
IIO_Reader_PNG::testHeader
IIO_Reader_PSD::testHeader
IIO_Reader_PVR::testHeader
IIO_Reader_RAD::testHeader
IIO_Reader_SGI::testHeader  (macOS only)
IIO_Reader_TGA::testHeader
IIO_Reader_TIFF::testHeader

此列表包含许多熟悉的格式(JPEG,PNG,GIF等),也有许多相当奇特的格式(KTX和ASTC,显然用于纹理或AI:Adobe Illustrator图稿),其中某些专用于Apple生态系统(图标使用ICNS,Animojis使用ATX)

不同图像格式受到的支持有所不同。某些格式使用开源解析库,受完全支持,这些库可以在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中在进程内还是进程外执行解析。

 

0x03 Fuzzing 闭源图像解析器

考虑到受支持的图像格式广泛并且大多数没有源代码,fuzzing似乎是显而易见的选择。

使用哪种fuzzer和fuzzing方法难以决断。由于大多数目标代码不开源,因而许多标准工具不能直接应用。此外为简化起见我决定将fuzzing限制为单台Mac Mini。因此fuzzer应满足:

  1. 以尽可能少的开销运行以充分利用可用的计算资源。
  2. 利用代码覆盖率的指导。

最后我决定基于Honggfuzz自己实现一些功能。fuzzing方法大致基于这篇文章:Full-speed Fuzzing: Reducing Fuzzing Overhead through Coverage-guided Tracing ,并通过以下方式实现了闭源代码的轻量级、低开销利用代码覆盖率引导fuzzing:

  1. 通过简单的IDAPython脚本完成枚举程序/库中每个基本块的起始偏移量。
  2. 运行时在进行fuzzing的进程用断点指令(Intel上的int3)替换每个未发现的基本块的首字节。覆盖位图中的原始字节和对应的偏移量,存储在专用的影子内存(shadow memory)映射中,该映射的地址可以从修改后的库的地址计算得出。
  3. 安装一个SIGTRAP处理程序,它将:
    • 检索故障地址并计算库中的偏移量以及影子内存(shadow memory)中相应条目的地址
    • 将基本块标记为在全局覆盖位图中找到
    • 用原始字节替换断点
    • 恢复执行

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

通过修补honggfuzz的客户端工具代码并编写IDAPython脚本来枚举基本块偏移量,可以很容易地实现所描述的方法。补丁程序和IDAPython脚本都可以在此处找到。

然后,fuzzer从大约700个种子图像的小型语料库开始,覆盖支持的图像格式并运行了数周。最终确定了以下漏洞:

  • P0 Issue 1952

    ImageIO使用libTiff时发生的一个错误,导致受控数据被写入内存缓冲区末尾。没有为这个问题分配CVE,可能是因为在我们报告之前Apple已在内部发现它。

  • CVE-2020-3826/P0 Issue 1953

    处理尺寸参数无效的DDS图像时,堆上的读取越界。

  • CVE-2020-3827/P0 Issue 1956

    使用优化的解析器处理JPEG图像时,越界写入堆中。

  • CVE-2020-3878/P0 Issue 1974

    PVR解码逻辑中可能出现一对一的错误,导致越过输出缓冲区的末端越界写入了另一行像素数据。

  • CVE-2020-3878/P0 Issue 1984

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

  • CVE-2020-3880/P0 Issue 1988

    在处理OpenEXR映像期间越界读取。

最后一个问题有些特殊,因为它发生在与ImageIO捆绑在一起的第三方代码中,即OpenEXR库的问题。由于该库是开源的,因此我决定对其进行独立fuzzing。

 

0x04 OpenEXR

OpenEXR是“用于计算机成像应用程序的高动态范围(HDR)图像文件格式”。解析器用C和C++实现并托管在GitHub。
如上所述,OpenEXR库通过Apple的ImageIO框架公开,因此通过Apple设备上各种流行的通讯应用程序暴露给0click攻击。尽管我还没有进行其他研究来支持这种说法,但攻击面可能不仅限于通讯应用。

由于该库是开源的,因此“常规”引导的fuzzing更容易执行。我使用了一个运行在大约500个内核上的,覆盖率指导的Google内部fuzzer持续了大约两个星期。该fuzzer以使用llvm的SanitizerCoverage进行边缘覆盖为指导,并通过使用常见的二进制突变策略对现有信号进行突变,并从大约80个现有的OpenEXR图像作为种子开始生成新的输入。

确定了八个可能的不重复漏洞,并将其作为P0 issue 1987报告给OpenEXR维护人员,然后在2.4.1版本中得以修复。接下来简要概述它们:

  • CVE-2020-11764

    在copyIntoFrameBuffer函数中的堆上(可能是图像像素)越界写入。

  • CVE-2020-11763

    导致std::vector越界读取的错误。之后,调用代码将写入vector的元素插槽中,从而可能损坏内存。

  • CVE-2020-11762

    一个越界memcpy,正在读取数据越界,然后可能又将其写入越界。

  • CVE-2020-11760 ,CVE-2020-11761 ,CVE-2020-11758

    像素数据和其他数据结构的各种越界读取。

  • CVE-2020-11765

    在栈上的越界读取,可能是由于先前发生了一个off-by-one的错误,该错误先前覆盖了堆栈上的字符串NULL终止符。

  • CVE-2020-11759

    可能是整数溢出问题,导致写入野生指针。

有趣的是,最初由ImageIO fuzzer发现的崩溃(issue 1988)在上游OpenEXR库中似乎无法重现,因此直接报告给了Apple。可能的解释是Apple发行了过时的OpenEXR库版本并且该错误已在上游修复。

 

0x05 建议

媒体格式解析仍然是一个严峻的问题。其他研究人员和第三方咨询机构也证明了这一点,如以下浮现在脑海的内容:
https://awakened1712.github.io/hacking/hacking-whatsapp-gif-rce/
https://www.facebook.com/security/advisories/cve-2019-11931

显而易见,这表明供应商/代码维护者应该对输入解析器进行持续的fuzzing。此外允许像ImageIO这样的库的客户端限制所允许的输入格式,并增加进程外解码的可选项能够帮助防止exploit。

对于Messenger方面,建议通过减少接收方支持的图像格式(至少对于不需要交互的消息预览)来减少攻击面。这种情况下,发送方在发送不支持的图像格式给接收方之前需将其重新编码。对ImageIO而言这将使攻击面从大约25种图像格式减少到很少或消除。

 

0x06 结论

这篇博客文章描述了作为操作系统或部分第三方库的图像解析代码最终如何通过流行的Messenger暴露于0click攻击。对公开代码的fuzzing导致许多新漏洞被修复。

经过足够的努力(以及由于自动重启服务而获得的exploit尝试)很可能在0click攻击场景下可以将某些发现的漏洞用于RCE。不幸的是,很可能还会存在其他错误或者将来会引入其他错误,因此需要对此和类似媒体格式的解析代码进行持续的fuzzing并积极减少攻击面,

(完)