Blasting Past Webp
NSO BLASTPASS iMessage漏洞利用分析
作者:Ian Beer,Google Project Zero
2023年9月7日,苹果发布了iOS的带外安全更新:
大约在同一时间,Citizen Lab发布了一篇博客文章,将iOS 16.6.1中修复的两个CVE与"在野外捕获的NSO Group零点击零日漏洞利用"联系起来:
“[目标]是受雇于一家总部位于华盛顿特区的国际民间社会组织的个人…
该漏洞利用链能够在受害者无需任何交互的情况下,入侵运行最新版iOS(16.6)的iPhone。
漏洞利用涉及包含恶意图像的PassKit附件,从攻击者iMessage账户发送给受害者。”
前一天,即2023年9月6日,苹果向WebP项目报告了一个漏洞,并在报告中表示他们计划在第二天为苹果客户发布定制修复程序。
WebP团队第二天在公共git仓库中发布了他们的第一个建议修复程序,五天后即9月12日,谷歌发布了包含WebP修复程序的新Chrome稳定版本。苹果和谷歌都将此问题标记为在野外被利用,提醒其他WebP集成商应迅速集成修复程序,并引起安全研究社区的更密切关注…
几周后的2023年9月21日,前Project Zero团队负责人Ben Hawkes(与@mistymntncop合作)在Isosceles博客上发布了关于漏洞根本原因的第一份详细分析报告。几个月后的11月3日,一个名为Dark Navy的组织发布了他们的第一篇博客文章:对WebP漏洞的两部分分析(第一部分 - 第二部分)以及针对Chrome的概念验证利用(CVE-2023-4863)。
虽然Isosceles和Dark Navy的帖子详细解释了底层的内存损坏漏洞,但他们未能解决另一个有趣的部分:究竟如何在一次性、零点击设置中成功利用此漏洞?正如我们很快将看到的,损坏原语非常有限。在没有访问样本的情况下,几乎不可能知道。
11月中旬,在与国际特赦组织安全实验室的合作下,我能够获得一些BLASTPASS PKPass样本文件以及失败的漏洞利用尝试的崩溃日志。
这篇博客文章涵盖了我对这些样本的分析,以及弄清楚NSO最近的零点击iOS漏洞利用真正如何工作的旅程。对我来说,这段旅程始于立即休了三个月的陪产假,并在2024年3月恢复,故事从这里开始:
场景设置
关于WebP漏洞的根本原因及其产生的原语的详细分析,我建议先阅读我之前提到的三篇博客文章(Isosceles、Dark Navy 1、Dark Navy 2)。我不会在这里重述他们的分析(既因为你应该阅读他们的原创工作,也因为相当复杂!)相反,我将简要讨论WebP和漏洞产生的损坏原语。
WebP
WebP是一个相对现代的图像文件格式,首次发布于2010年。实际上WebP实际上是两种完全不同的图像格式:基于VP8视频编解码器的有损格式和单独的无损格式。除了都使用RIFF容器和字符串WEBP作为第一个块名外,这两种格式没有任何共同之处。从那时起(文件中的12字节),它们完全不同。漏洞存在于无损格式中,RIFF块名为VP8L。
无损WebP广泛使用霍夫曼编码;BLASTPASS样本中至少存在10个霍夫曼树。在文件中,它们存储为规范霍夫曼树,意味着只保留代码长度。在解压缩时,这些长度直接转换为两级霍夫曼解码表,五个最大的表都被挤入同一个预分配缓冲区中。这些表的最大大小(结果证明不完全)根据它们编码的符号数量预先计算。如果你读到这部分并且有点困惑,上面引用的其他三篇博客文章详细解释了这一点。
通过控制符号长度,可以定义各种奇怪的树,其中许多是无效的。根本问题是WebP代码只在构建解码表后检查树的有效性。但解码表的预计算大小仅对有效树是正确的。
正如Isosceles博客文章指出的,这意味着漏洞的一个基本部分是触发错误会被检测到,尽管在内存损坏之后,图像解析在几行代码后停止。这提出了另一个利用谜题:在零点击上下文中,如何利用一个每次触发问题都会停止解析任何攻击者控制数据的错误?
第二个谜题涉及实际的损坏原语。漏洞将在霍夫曼表缓冲区末尾的已知偏移处写入一个HuffmanCode结构:
|
|
正如DarkNavy指出的,虽然bits和value字段名义上由攻击者控制,但实际上灵活性不大。第五个霍夫曼表(位于预分配缓冲区末尾的那个,部分可以越界写入)只有40个符号,将value限制为最大值39(0x27),bits将在1和7之间(对于第二级表条目)。bits和value之间有一个填充字节,使得可能越界写入的最大值为0x00270007。而碰巧这正是漏洞利用写入的值——他们可能没有太多选择。
霍夫曼表分配大小的灵活性也不大。漏洞利用中的表分配为12072(0x2F28)字节,将向上舍入以适应0x3000字节的libmalloc小区域。选择代码长度以使溢出如下发生:
总结:32位值0x270007将被写入0x3000字节霍夫曼表分配末尾的0x58字节处。然后WebP解析将失败,解码器将退出。
似曾相识?
Project Zero博客的长期读者可能会在此时体验到似曾相识的感觉…我不是已经写过一篇关于NSO零点击iPhone零日的博客文章,利用了稍微晦涩的无损压缩格式中的漏洞,该格式解析自iMessage附件中的图像吗?
确实。
BLASTPASS与FORCEDENTRY有许多相似之处,我最初的直觉(结果完全错误)是这个漏洞利用可能采用类似的方法,使用一些更花哨的WebP功能构建一个怪异机器。为此,我开始编写一个WebP解析器,看看实际使用了哪些功能。
转换
与JBIG2非常相似,WebP也支持对输入像素数据的可逆转换:
我最初的理论是,漏洞利用可能以类似于FORCEDENTRY的方式操作,并应用这些转换序列超出图像缓冲区的边界来构建一个怪异机器。但在用python实现了足够的WebP格式来解析VP8L块的每一位之后,很明显它只触发霍夫曼表溢出,没有更多。VP8L块只有1052字节,几乎全部是触发溢出所需的10个霍夫曼表。
通行证里有什么?
虽然BLASTPASS通常被称为"WebP漏洞"的利用,但攻击者实际上并不只是发送WebP文件(尽管iMessage支持)。他们发送一个PassKit PKPass文件,其中包含一个WebP。这样做一定有原因。让我们退后一步,实际看看我收到的一个样本文件:
171K sample.pkpass
|
|
PKPass zip存档中有五个文件:
|
|
5.5MB的logo.png是WebP图像,只是扩展名为.png而不是.webp:
|
|
PKPass格式最接近的规范似乎是Wallet开发者指南,虽然它没有明确说明.png文件实际上应该是便携式网络图形图像,但这大概是意图。这是与FORCEDENTRY的另一个相似之处,其中使用了类似的技巧在尝试解析GIF时到达PDF解析器。
PKPass文件需要有效的签名,包含在manifest.json和signature中。签名有一个可能是假的名字和更多时间戳,表明PKPass很可能为每次漏洞利用尝试动态生成和签名。
pass.json只是这样:
|
|
最后是background.png:
|
|
奇怪。另一个具有误导性扩展名的文件;这次是一个带有.png扩展名的TIFF文件。
我们将在分析后期返回这个TIFF,因为它在漏洞利用流程中扮演关键角色,但现在我们将专注于WebP,有一个简短的转移:
Blastdoor
到目前为止,我只提到了WebP漏洞,但我在本文开头链接的苹果公告提到了两个独立的CVE:
第一个,ImageIO中的CVE-2023-41064,是WebP错误(尽管与上游WebP修复的CVE-2023-4863保持混淆 - 它们是相同的漏洞)。
第二个,“Wallet"中的CVE-2023-41061,在苹果公告中描述为:“恶意制作的附件可能导致任意代码执行”。
Isosceles博客文章假设:
“Citizen Lab称此攻击为’BLASTPASS’,因为攻击者找到了一种巧妙的方法来绕过’BlastDoor’ iMessage沙箱。我们没有完整的技术细节,但看起来通过将图像漏洞捆绑在PassKit附件中,恶意图像将在不同的、未沙箱化的进程中处理。这对应于苹果发布的第一个CVE,CVE-2023-41061。”
这个理论是有道理的——FORCEDENTRY有一个类似的技巧,其中JBIG2错误实际上在IMTranscoderAgent内部被利用,而不是在BlastDoor的限制性更强的沙箱中。但在我所有的实验以及我见过的所有野外崩溃日志中,这个假设似乎不成立。
PKPass文件和其中的图像确实在BlastDoor沙箱内部被解析,崩溃或有效负载执行发生在那里——稍后我们还将看到证据,表明最终被评估的NSExpression有效负载期望在BlastDoor内部运行。
我的猜测是,CVE-2023-41061更可能指的是宽松的PKPass解析,没有拒绝不是png的图像。
2024年底,我收到了另一组野外崩溃日志,包括两个确实强烈表明也存在一条路径可以在MobileSMS进程中触及WebP漏洞,在BlastDoor沙箱之外!有趣的是,时间戳表明这些设备在2023年11月成为目标,即漏洞被修补两个月后。
在这些情况下,WebP代码通过ChatKit CKPassPreviewMediaObject在MobileSMS进程内部到达,由CKAttachmentMessagePartChatItem创建。
WebP里有什么?
我提到WebP文件中的VP8L块只有大约1KB。然而在上面的文件列表中,WebP文件是5.5MB!那么其余部分是什么?扩展我的WebP解析器,我们看到还有一个RIFF块:
|
|
这是一个(非常非常大的)EXIF——相机用于存储图像元数据的标准格式——比如相机型号、曝光时间、光圈等。
它是一个基于标签的格式,几乎全部5.5MB都在一个id为0x927c的标签内。那么那是什么?
查看在线EXIF标签列表,在镜头焦距标签下方和用户评论标签上方,我们发现了0x927c:
这是听起来非常模糊但迷人的:“MakerNote - 制造商特定信息。”
在维基百科上寻求一些关于这实际上是什么的澄清,我们了解到
“‘MakerNote’标签包含通常为专有二进制格式的信息。”
修改webp解析器以现在转储MakerNote标签,我们看到:
|
|
苹果选择的"专有二进制格式"格式是二进制plist!
确实:在IDA中查看ImageIO库,在WebP解析器、EXIF解析器、MakerNote解析器和二进制plist解析器之间有一条清晰的路径。
unbplisting
我在之前的博客文章中介绍了二进制plist格式。那是我第二次必须分析大型bplist。第一次(对于FORCEDENTRY沙箱逃逸)大部分是手工完成的,只是使用plutil的人类可读输出。去年,对于Safari沙箱逃逸分析,bplist是437KB,我必须编写一个自定义bplist解析器来弄清楚发生了什么。保持指数曲线,今年bplist又大了10倍。
在这种情况下,相当清楚bplist必须是一个堆塑造——并且为5.5MB,可能是一个相当复杂的堆塑造。那么它在做什么?
切换视图
我有一个直觉,bplist将使用重复的字典键作为堆塑造的基本构建块,但运行我的解析器它没有输出任何…直到我意识到我的工具在转储它们之前将解析的字典直接存储为python字典。修复工具以改为保留键和值的列表,很明显有重复的键。很多:
修改解析器以发出格式良好的花括号和缩进,然后依赖VS Code的自动代码折叠被证明足够好地浏览和感受塑造对象的结构。
有时正确的可视化技术足以弄清楚漏洞利用试图做什么。在这种情况下,原语是基于堆的缓冲区溢出,塑造将不可避免地尝试将两个东西放在内存中相邻的位置,我想知道"哪两个东西?”
但无论我盯着和滚动多久,我都无法弄清楚任何事情。是时候尝试一些不同的东西了。
检测
我写了一个小助手来使用与MakerNote解析器相同的API加载bplist,并使用Mac Instruments应用程序运行它:
解析单个5.5MB bplist导致近五十万次分配,消耗近千兆字节的内存。仅仅查看这个分配摘要,很明显有很多CFString和CFData对象,可能用于堆整形。进一步查看列表,还有其他有趣的数字:
最后一行中的20'000太圆了,不可能是巧合。这个数字与分配的__NSDictionaryM对象的数量匹配:
最后,在列表的最底部,还有两个更突出的分配模式:
有两组非常大的分配:八十个1MB分配和四十四个4MB分配。
我再次修改了我的bplist工具,以转储每个唯一的字符串或数据缓冲区,以及看到它的次数和它的哈希值。查看文件列表,有一个清晰的模式:
对象大小 | 计数 |
---|---|
0x3FFFFF | 44 |
0xFFFFF | 80 |
0x3FFF | 20 |
0x26A9 | 24978 |
0x2554 | 44 |
0x23FF | 5822 |
0x22A9 | 4 |
0x1FFF | 2 |
0x1EA9 | 26 |
0x1D54 | 40 |
0x17FF | 66 |
0x13FF | 66 |
0x3FF | 322 |
0x3D7 | 404 |
0xF | 112882 |
0x8 | 3 |
有大量分配刚好落在十六进制"round"数字之下:0x3ff、0x13ff、0x17ff、0x1fff、0x23ff、0x3fff…这强烈暗示它们的大小正好落在某些分配器大小桶内。
几乎所有的分配都只填充了零或’A’。但1MB的那个非常不同:
|
|