作者:启明星辰ADLab
原文链接:https://mp.weixin.qq.com/s/xy4SA9MDe5cPchoc8TJQ0w
2023年9月7日,Citizen Lab [1]宣布早在一周前,他们发现工作在华盛顿一国际办事处机构员工的设备上存在活跃的iMessage 0-click攻击,该利用通过发送包含恶意图片的Apple PassKit部署著名军火商NSO 研发的Pegasus 间谍软件。漏洞利用链由于可以绕过BlastDoor的沙箱,因此被称之为BLASTPASS。当天Apple 发布了两条漏洞的修复公告[2]。其中漏洞CVE-2023-41061存在于Apple Wallet中,恶意构造的附件可使得任意代码执行。另一漏洞CVE-2023-41064存在于ImageIO中,当解析恶意的图片可导致任意代码执行。这两个漏洞在iOS 16.6.1 和iPadOS 16.6.1 中修复。截止目前,漏洞细节并未公开。
Apple ImageIO框架中包含了对WebP数据的支持,9月6日Apple安全团队向Google报告了WebP安全漏洞(CVE-2023-4863)。9月11日,Google紧急发布一则关于WebP的安全修复[3],并指出Google已经意识到该漏洞存在在野利用。该漏洞极有可能就是BLASTPASS攻击中使用的漏洞。据不完全统计,WebP组件的下游软件可能超过百万款,或将使其成为下一个Log4Shell漏洞,建议使用WebP组件的厂商及时更新到最新版。本文主要对漏洞CVE-2023-4863进行分析复现。
使用address sanitizer选项编译WebP代码(7ba44f80f3b94fc),当加载恶意图片时,程序在解析WebP图片时因堆溢出崩溃:
WebP容器文件格式基于RIFF(Resource Interchage File Format)文件格式,支持无损压缩(VP8L)。基本的文件格式由WebP头(绿色部分)和VP8L(蓝色部分)块组成。文件头由RIFF标识、文件大小以及容器名WEBP文件标识组成,VP8L块包括VP8L标识以及其数据部分(bitsream)[5]:
结合poc构造,可以看到VP8L的数据流头部包含特征(0x2f)、width、height、has_alpha(false)、version等信息。
在函数DecodeImageStream中,将transform(line1426)和 color cache(line1432)设置为0,并且在函数ReadHuffmanCodes中绕过if判断(line381),以便于直接进入哈夫曼编码的解析(ReadHuffmanCodes):
哈夫曼编码是一类带权路径长度(WPL)最短的树,是一种传输效率最高的二进制编码。在实现中使用表代替树。其核心思想是使用变长编码表对源符号进行编码,出现频率高的符号使用较短的编码,出现频率低的符号使用较长的编码,这使得字符串的平均长度、期望值降低[4]。
在poc中,对(1-15)进行哈夫曼编码,每个值出现的情况使用二维数组(code_lengths_counts)统计,创建5个哈夫曼编码,每个哈夫曼编码使用的符号集大小分别为{280, 256, 256, 256, 40}:
在创建哈夫曼树的过程中,先按照使用频次对节点排序,然后调用函数calculate_code_lengths计算每个节点编码长度(code_lengths):
在函数ConvertBitDepthsToSymbols中,根据获得编码长度(code_lengths)和编码深度计数(depth_count)计算出每个节点的key值(存储在codes):
就第一个哈夫曼编码计算出的每个节点的编码长度以及每个节点的key值如下:
在ReadHuffmanCodes函数中开始了哈夫曼编码解析之旅,前述的5个哈夫曼编码将存储到表中,这里简称哈夫曼表(huffman_tables )。在ReadHuffmanCodes函数中首先确定表的大小(table_size),该值由color_cache_bits(这里为0)索引数组(kTableSize)确定(line377),即数组的第一个元素。
当给定符号集的大小、根表的大小以及最大的编码长度,数组(kTableSize)中的大小可使用工具enough.c提前确定,该工具可根据前面给定的参数生成最大有效的完全的哈夫曼编码表[6]。kTableSize 的值如下:
在ReadHuffmanCodes函数中根据上述确定的表的大小分配huffman_tables 缓存(line432):
然后调用函数ReadHuffmanCode依次解析5个haffman编码:
函数ReadHuffmanCode调用VP8LBuildHuffmanTable构建哈夫曼表,该表使用2级存储,根表的大小是8位,多余8位的存储到二级表中。
前述的key值作为哈夫曼表的索引,当编码长度大于根表的大小(8bits),key值的低位用来索引根表的位置,高位值用来索引二级表来确定存储的位置。调用ReplicateValue对哈夫曼编码(HuffmanCode)存储:
解析前4个哈夫曼编码时,哈夫曼表都可如常构建,当对第5个哈夫曼编码解析时,生成的key值索引二级表的存储位置时,超出哈夫曼表(haffman_tables)预先分配缓存大小,导致程序出现崩溃。
第5个哈夫曼编码的key值如下:
造成这种情况的原因是开发者使用enough工具预测缓存的大小,该工具预测的缓存的大小是根据完全有效的哈夫曼树计算出来的,并未考虑不平衡的编码情况。在poc中给定{0, 1, 1, 1, 1, 1, 0, 0, 0, 11, 5, 1, 10, 4, 2, 2},并设定最大的编码长度为15,根表的大小为8时,许多内部节点作为叶节点,创建的是一棵无效的哈夫曼树。
结合前述分析,key值是根据节点的深度的计数以及编码长度计算得出,当使用无效的哈夫曼树计算时得到的key值去索引预定的缓存时,就超出了预期。
漏洞补丁[7]对于每个哈夫曼编码,在函数VP8LBuildHuffmanTable中,两次调用函数BuildHuffmanTable,第一次将参数root_table 设置为NULL计算需要编码的段的总大小(total_size),如果大小不满足,将重新分配缓存。第二次调用函数BuildHuffmanTable才执行实际的哈夫曼表的建立。
当创建完哈夫曼表后,如果是无效的哈夫曼树,返回的大小为0,后续直接抛出错误。
使用chrome加载构造的WebP图像,浏览器因为堆溢出崩溃。WebP解析库应用广泛,在实际测试中,没有崩溃不代表没有影响,受影响的厂商应及时更新到最新版本。
https://chromereleases.googleblog.com/2023/09/stable-channel-update-for-desktop_11.html
https://developers.google.com/speed/webp/docs/riff_container
https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c
https://chromium.googlesource.com/webm/libwebp/+/902bc9190331343b2017211debcec8d2ab87e17a%5E%21/
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3056/