CVE-2023-4863 漏洞:

WebP 在实现“无损压缩”技术(VP8L)时使用了霍夫曼编码算法,在解码不受信任图像时造成溢出霍夫曼表。

复现结果:

复现思路:

  • 核心代码:craft_webp 函数(craft.c 文件)
  • 作用:创建一个特殊的 WebP 图像文件,该文件包含一系列精心设计的 Huffman 编码表,触发WebP 解码器的缓冲区溢出错误。
  • 1. 二维数组 code_lengths_counts

  • 选择 code_lengths_counts 设计的原因:
    • 使 huffman 表溢出->huffman 编码很长->huffman 码长频率变小
  • 首先,定义了一个二维数组 code_lengths_counts,用于存储 Huffman 编码的码长的频率。每一行代表一个元数据码,每一列代表一个码长,数组中的值表示对应码长的频率。例如,

code_lengths_counts[i][j]表示第 i 个元数据码中码长为j 的频率。这个数组用于构建 Huffman 编码表,这是解码 WebP 图像文件的关键步骤。

  • 改变 code_lengths_counts 的值会影响到 Huffman 编码表的构建,从而影响到 WebP 图像文件的解码结果。因为 Huffman 编码表是根据像素值的频率来构建的,如果频率改变了,那么对应的编码也会改变。
  • 具体的影响取决于如何改变这些值。如果增加了某个码长的频率,那么对应的编码可能会变短,因为Huffman 编码算法会给频率高的值分配较短的编码。反之,如果减少了某个码长的频率,那么对应的编码可能会变长。
  • 2. 一维数组 kAlphabetSize

  • 定义了一个一维数组 kAlphabetSize,用于存储每个元数据码的符号数量。
  • Google 在 dev.c 中写道:“一个霍夫曼树组的查找表所需的内存。RED、BLUE、ALPHA 和 DIST 字母表是恒定的(RED、BLUE 和 ALPHA 为 256,DIST 为 40),在最坏的情况下,它们的查找表大小分别为 630 和 410。GREEN 字母表的大小取决于颜色缓存大小,等于 256(绿色分量值)+24(长度前缀值)+color_cache_size(介于 0 和 2048 之间)。 使用 Mark Adler 的工具为 8 位一级查找计算的所有值: //https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c

  • 所以,构建 4 个有效的霍夫曼树,导致 4 个最大大小的输出表,然后为最后一个表提供一个无效的霍夫曼树,让 ReplicateValue 函数(将特定的霍夫曼编码复制到霍夫曼表中的适当位置,以便在解码时可以快速查找)在对节点计数进行最终一致性检查之前从无效的起始键写入越界。设计前四个表均为最 大大小(630+24,630,630,630),第 5 个表>410,从而造成溢出
  • 3. 设计挑战
  • 设计挑战是找到一组 code_lengths_counts,使 BuildHuffmanTable 超过预先计算的缓冲区大小。更改 code_lengths_counts 数组以影响内部直方图(本质上是 BuildHuffmanTable 中的计数数组), 需要将关键变量增加到比预期更大的值。
  • 在 huffman_utils.c 中可见:在 VP8LBuildHuffmanTable 函数中,调用BuildHuffmanTable 函数。通过统计 code_lengths 数组中不同码字长度的出现次数 counts,构建了 Huffman 编码表的内部直方图。具体来说,直方图中的每个条目对应于一个码字长度,条目的值表示该码字长度在 code_lengths 数组中出现的次数。
  • 通过更改 code_lengths_counts 数组中的值,可以影响内部直方图的分布。更改code_lengths_counts 数组中的值会改变不同码字长度的出现次数,从而影响 Huffman 编码表的构建过程。这可能会导致不同码字长度的频率分布发生变化,进而影响到 Huffman 编码表的大小和性能。
  • 4. 挑战实现
  • 需要填充的 5 个表中的最后一个,即设计第 5 个表的 code_lengths_counts 数组。符号大小为 40,根表为 8 位,最大代码长度为 15,据 Google 称最大大小为 410。
  • 霍夫曼编码是一种前缀编码,它的主要特点是频率高的符号的编码长度短,频率低的符号的编码长度 长。
  • 如果一个符号的频率很高,那么它的霍夫曼编码长度会很短,可能直接在 root_table 中表示。这样, 查找这个符号的霍夫曼编码时,只需要查找 root_table,不需要查找 2nd level table,从而提高了查找效率。
  • 如果一个符号的频率很低,那么它的霍夫曼编码长度会很长,可能需要在 2nd level table 中表示。这样,查找这个符号的霍夫曼编码时,需要先查找 root_table,然后再查找 2nd level table,查找效率相对较低。
  • 注意到直方图中的前 9 个条目(例如 count[0]… count[8],称为“根表”)不会对 total_size 产生太大影响,但可能会影响后续计算的内部状态(例如将节点数量推得太高)。直方图中的最终条目(例如count[9]…count[15],称为“二级表”)对最终 total_size 值有直接影响。考虑到这一点,我创建了一些不同的统计分布,通常将根表的值保持在较低水平(通常求和小于 8),而二级表的值保持在较高水平。
  • 然后根据在 root_table 或 2nd level table 中复制其霍夫曼编码,这个过程使用了 ReplicateValue 函数。构建 4 个有效的霍夫曼树,导致 4 个最大大小的输出表,然后为最后一个表提供一个无效的霍夫曼树,能让 ReplicateValue 在对节点计数进行最终一致性检查之前从无效的起始键写入越界

  • 5. 构造解码错误的 webp 文件
  • 在 craft_webp 函数中,遍历 code_lengths_counts 和 kAlphabetSize 数组,为每个元数据码生成一个 Huffman 编码表。生成的方法是,首先根据 kAlphabetSize 分配一个 code_lengths 数组,然后根据 code_lengths_counts 填充这个数组,最后调用 write_code_lengths 函数将这个数组写入到WebP 文件中。关键的部分在于 code_lengths_counts 数组的最后一行,它定义了一个非常大的Huffman 编码表。这个表的大小超过了 WebP 解码器预期的最大大小,因此在解码这个文件时可能会触发缓冲区溢出错误。这个错误的触发点在于 write_code_lengths 函数,它将 code_lengths 数组写入到 WebP 文件中。如果code_lengths 数组的大小超过了解码器的预期,那么在解码这个数组时会导致缓冲区溢出。

多次实验发现:

更改并设计最后一组 code_lengths_counts 分量,发现<410 也能造成缓冲区溢出漏洞,但需要小心设计这些分量。这说明 Google 所言的查找表大小的最坏情况其实可以更小以至于小于 410,目前测试的最小值以及分量如下 :

测试最小一组 size 的结果如下:

参考文献:https://blog.isosceles.com/the-webp-0day/

Windows端漏洞复现现象:

将恶意webp图像放在桌面后用wps图像查看打开,发现wps闪退以及打不开的现象。多次点击尝试打开,发现wps无响应且电脑死机,以下是手机拍摄现象(因为电脑死机无法截屏):

OOYXXUSF~@N%LH1UM4IEODF_tmb

感谢阅读!

Comments

⬆︎TOP