Project Zero 关于 Fuzzing ImageIO 的研究
2020-08-30 11:20:00 Author: www.4hou.com(查看原文) 阅读量:326 收藏

导语:这篇博客文章讨论了在新的上下文中的图像格式解析器中的漏洞:流行的Messenger应用程序中的无交互代码路径。这项研究的重点是Apple生态系统及其提供的图像解析API:ImageIO框架。

这篇博客文章讨论了在新的上下文中的图像格式解析器中的漏洞:流行的Messenger应用程序中的无交互代码路径。这项研究的重点是Apple生态系统及其提供的图像解析API:ImageIO框架。我们发现了图像解析代码中的多个漏洞,并将其报告给Apple或相应的开源图像库维护者,然后进行了修复。在这项研究期间,针对闭源二进制文件的轻量级且开销低的引导Fuzzing方法得以实施,并与本博文一起发布。

整个博客中描述的漏洞可以通过Messenger来访问,但不属于其代码库。因此,供应商需要修复它们。

0x01  基础介绍

在对Messenger应用进行逆向时,我在无需用户交互就可以到达的代码路径上遇到了以下代码(手动反编译为ObjC并稍作简化):

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

此代码解密从发送方收到的作为传入消息一部分的二进制数据,并从中实例化UIImage实例。

 https://developer.apple.com/documentation/uikit/uiimage?language=objc

然后,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];

此代码的目的是渲染输入图像的较小尺寸的版本,以用作用户通知中的缩略图。毫不奇怪,在其他Messenger应用中也可以找到类似的代码。本质上,如上所示的代码将Apple的UIImage图像解析和CoreGraphics图像渲染代码转换为0click攻击面。

 https://googleprojectzero.blogspot.com/2020/01/remote-iphone-exploitation-part-1.html

如果满足以下先决条件,则可以使用所描述的技术来利用内存破坏漏洞:

1. 一种从处理邮件的过程发送的传输形式;

2. 每次启动ASLR至少需要一些内存映射;

3. 自动重启服务。

在那种情况下,该漏洞可能例如用于破坏指向ObjC对象(或类似对象)的指针,然后构造一个崩溃Oracle以绕过ASLR,然后再执行代码。

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

0x02 ImageIO.framework简介

在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 Magic。

 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)
 
 IIO_Reader_PNG :: testHeader
 
 IIO_Reader_PSD :: testHeader
 
 IIO_Reader_PVR :: testHeader
 
 IIO_Reader_RAD :: testHeader
 
 IIO_Reader_SGI :: testHeader(仅限macOS)
 
 IIO_Reader_TGA :: testHeader
 
 IIO_Reader_TIFF :: testHeader

尽管此列表包含许多熟悉的格式(JPEG,PNG,GIF等),但也有许多相当奇特的格式(KTX和ASTC),并且其中某些似乎特定于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。因此,模糊器应:

1. 以尽可能少的开销运行,以充分利用可用的计算资源;

2. 利用某种代码覆盖率指导。

最后,我决定在Honggfuzz之上自己实现一些功能。Fuzzing方法的想法大致基于以下文章:全速Fuzzing:通过覆盖率引导的跟踪减少Fuzzing的开销

 https://github.com/google/honggfuzz
 https://arxiv.org/abs/1812.11875

并通过以下方式实现了闭源代码的轻量级,低开销的引导Fuzzing:

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

2. 在运行时,在Fuzzing的过程中,用断点指令(Intel上的int3)替换每个未发现的基本块的第一个字节。覆盖位图中的原始字节和对应的偏移量存储在专用的影子内存映射中,该映射的地址可以从修改后的库的地址计算得出。

3. 安装一个SIGTRAP处理程序,它将:

      a. 检索故障地址并计算库中的偏移量以及影子存储器中相应条目的地址

      b. 将基本块标记为在全局覆盖位图中找到

      c. 用原始字节替换断点

      d. 恢复执行

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

 https://project.inria.fr/FranceJapanICST/files/2019/04/19-Kyoto-Fuzzing_Binaries_using_Dynamic_Instrumentation.pdf
 https://github.com/googleprojectzero/p0tools/tree/master/TrapFuzz

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

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

*P0问题1952**

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

CVE-2020-3826 / P0问题1953

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

CVE-2020-3827 / P0版本1956

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

CVE-2020-3878 / P0 1974年

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

CVE-2020-3878 / P0发行1984

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

CVE-2020-3880 / P0发行1988

在处理OpenEXR映像期间超出范围的读取。

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

0x04  OpenEXR

OpenEXR是“用于计算机成像应用程序的高动态范围(HDR)图像文件格式”。该解析器是用C和C ++实现的,可以在github上找到。

 https://github.com/AcademySoftwareFoundation/openexr

如上所述,OpenEXR库通过Apple的ImageIO框架公开,因此通过Apple设备上各种流行的Messenger应用程序公开为0click攻击面。尽管我还没有进行其他研究来支持这种说法,但攻击面可能不仅限于消息传递应用程序。

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

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

CVE-2020-11764

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

CVE-2020-11763

导致std :: vector超出边界读取的漏洞。之后,调用代码将写入此向量的元素插槽中,从而可能损坏内存。

CVE-2020-11762

一个越界内存,正在读取越界数据,然后又可能越界写入数据。

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

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

CVE-2020-11765

在堆栈上的越界读取,可能是由于先前发生了一个不正确的错误,该错误先前覆盖了堆栈上的字符串空终止符。

CVE-2020-11759

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

有趣的是,最初由ImageIO Fuzzer发现的崩溃(发行于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这样的库的客户端限制所允许的输入格式,并可能选择加入进程外解码,可以帮助防止利用。

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

这篇博客文章描述了作为操作系统或第三方库一部分的图像解析代码最终如何通过流行的Messenger暴露于0click攻击表面。对公开代码的模糊处理导致许多新漏洞被修复。经过足够的努力(以及由于自动重启服务而授予的利用尝试),很可能在0click攻击情形下可以将某些发现的漏洞用于RCE。不幸的是,很可能还会存在其他错误,或者将来会引入其他错误。因此,对这种和类似媒体格式的解析代码进行持续的Fuzzing,并积极减少攻击面是很有价值的。

本文翻译自:https://googleprojectzero.blogspot.com/2020/04/fuzzing-imageio.html如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/NpOK
如有侵权请联系:admin#unsafe.sh