9月22日,我们在“「 特别预警」这个新iMessage 0-Click漏洞可能影响你”中,首次提醒业界,基础组件 libwebp 漏洞(CVE-2023-4863)会影响广泛的全球软件厂商。随后其他国外机构也发布测试消息进行了佐证,9 月 27 日该漏洞被 CVSS 调整危害评级至满分 10 分。
目前,根据公开信息和深蓝的复测,主流操作系统和流行软件已经完成了对该漏洞的安全修复。该漏洞复现验证和利用难度极大,一些小微软件厂商可能尚未得到充分测试验证、影响修复更新,我们特发布本技术报告,助力业界科学研判。
本报告首发于 DARKNAVY Blog (https://blog.darknavy.com/)。
以下为报告大纲及全文。
Exploiting the libwebp Vulnerability
01
Playing with Huffman Code
漏洞定位
漏洞分析
漏洞利用
如何构造PoC并绕过检查?
如何触发crash?
如何构建利用原语?
如何控制写入的数据?
如何控制写入的位置?
02
Diving into Chrome Blink
信****息泄露
寻找对象
Cross-Thread堆占位
从OOB到UAF
Cross-Bucket分配
RCE
结论
DARKNAVY在Chrome上的利用复现
01 漏洞定位
在初始的漏洞分析阶段,由于缺少现成的PoC或详细分析报告,我们首先尝试阅读并理解webmproject/libwebp上游仓库针对CVE-2023-4863的修复代码[1]。然而,WebM Project官方的修补过程相对复杂,这使得我们难以精确地锁定漏洞的原始触发点。
于是,我们将目光转向了Apple对CVE-2023-41064的官方补丁,并使用BinDiff对更新前后的ImageIO组件进行了对比。我们注意到Apple的补丁代码变更相对较少,易于理解,并且非常“简单粗暴”:
简而言之,Apple的修复方案是在WebP解码器中增加了一项检查:如果在构建霍夫曼编码表 (Huffman Table) 时越界,就会直接返回错误,而不是继续解码。
`diff --git a/src/dec/vp8l_dec.c b/src/dec/vp8l_dec.c``index 45012162..06b142bc 100644``--- a/src/dec/vp8l_dec.c``+++ b/src/dec/vp8l_dec.c``@@ -438,6 +438,7 @@ static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,``goto Error;` `}`` `` ``+ bound = &huffman_tables[num_htree_groups * table_size];` `huffman_table = huffman_tables;``for (i = 0; i < num_htree_groups_max; ++i) {``// If the index "i" is unused in the Huffman image, just make sure the``diff --git a/src/utils/huffman_utils.c b/src/utils/huffman_utils.c``index 90c2fbf7..13054715 100644``--- a/src/utils/huffman_utils.c``+++ b/src/utils/huffman_utils.c``@@ -191,6 +191,7 @@ static int BuildHuffmanTable(HuffmanCode* const root_table, int root_bits,` `}` `code.bits = (uint8_t)(len - root_bits);` `code.value = (uint16_t)sorted[symbol++];``+ if (bound && &table[key >> root_bits + table_size] >= bound) return 0;` `ReplicateValue(&table[key >> root_bits], step, table_size, code);` `key = GetNextKey(key, len);` `}`
因此,漏洞很可能是由于在构建Huffman Table时,没有对输入的数据进行有效性检查,从而导致分配给表的内存区域被溢写,即出现了缓冲区溢出。
02 漏洞分析
漏洞根源位于WebP图片的处理代码逻辑中。在解析一个无损WebP图片时,解码器会采用顺序数据压缩算法 (LZ77)、范式霍夫曼编码算法 (Canonical Huffman Coding) 和颜色缓存 (Color Cache) 来压缩图像的ARGB数据,有关这一过程的具体细节,Google在其技术文档[2]中有详尽的描述,并提供了WebP文件格式的清晰结构规范。
在这一处理流程中,解码器首先会从图片数据流中读取前缀编码的数据,并基于这些数据构建完整的霍夫曼编码表。接着,解码器会根据该表对数据流中的压缩数据进行解码,还原出原始图像。对于霍夫曼编码的算法设计细节,由于已有众多相关介绍性文章,本文不再赘述。
按照规范霍夫曼编码算法,在构建霍夫曼编码表时,首先会使用一级表,该表用于查询长度小于N位(默认为8位)的霍夫曼编码;如果出现长度超过N位的编码,则解码器会分配二级表来查询这部分超长编码。
解码器在分配霍夫曼编码表的内存空间时,会一次性预留足够容纳所有一级表和二级表的空间,其内存大小是固定的:
`// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha``// and distance alphabets are constant (256 for red, blue and alpha, 40 for``// distance) and lookup table sizes for them in worst case are 630 and 410``// respectively. Size of green alphabet depends on color cache size and is equal``// to 256 (green component values) + 24 (length prefix values)``// + color_cache_size (between 0 and 2048).``// All values computed for 8-bit first level lookup with Mark Adler's tool:``// https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c``#define FIXED_TABLE_SIZE (630 * 3 + 410)``static const uint16_t kTableSize[12] = {` `FIXED_TABLE_SIZE + 654,` `FIXED_TABLE_SIZE + 656,` `FIXED_TABLE_SIZE + 658,` `FIXED_TABLE_SIZE + 662,` `FIXED_TABLE_SIZE + 670,` `FIXED_TABLE_SIZE + 686,` `FIXED_TABLE_SIZE + 718,` `FIXED_TABLE_SIZE + 782,` `FIXED_TABLE_SIZE + 912,` `FIXED_TABLE_SIZE + 1168,` `FIXED_TABLE_SIZE + 1680,` `FIXED_TABLE_SIZE + 2704``};`` ``const int table_size = kTableSize[color_cache_bits];``huffman_tables = (HuffmanCode*)WebPSafeMalloc(num_htree_groups * table_size, sizeof(*huffman_tables));`
从这段代码可以看出,编码表由五个部分组成,分别对应红色、绿色、蓝色、alpha和distance通道的编码表。其中,红色、蓝色、alpha通道的编码表大小固定为630,distance通道的编码表大小固定为410,而绿色通道的编码表大小则取决于颜色缓存的大小,这些大小加在一起便构成了整个编码表的总大小。此外,作者也提供了一个来自zlib库的工具enough.c,可以在指定编码长度和颜色缓存大小的情况下计算编码表的最大可能大小。
问题在于,解码器默认图片中保存的霍夫曼编码表数据是合理的,因此它会基于这一假设提前计算最大内存长度。这里的核心前提是编码数据必须符合范式霍夫曼编码的规范,即霍夫曼树应该是完整的二叉树,每个叶节点都对应一个前缀编码,不存在未使用的悬挂叶节点。但由于霍夫曼编码表数据来自不受信任的源,且可能被攻击者任意构造,解码器在没有对这些数据进行有效性检查的情况下解析了一个不完整的二叉树,可能会分配过多的二级表,导致总内存占用超出了预分配的大小,发生堆缓冲区溢出。
以绿色通道为例,在color cache大小为0的情况下,其编码表的最大大小为654。结合enough工具,我们可以得到其霍夫曼表的结构可能为(参考lifthrasiir[3]的可视化表达):
`Len Code range # Root entry Overhead #` `--- ------------------------------------ --- ----------------- -------- ---` `1 0 1 0xxxxxxx 0 128` `9 10000000:0 .. 11110110:1 238 10000000-11110110 2^1 119` `11110111:0 1 11110111 2^2 1` `10 11110111:10 .. 11110111:11 2` `11111000:00 .. 11111110:11 28 11111000-11111110 2^2 7` `11111111:00 .. 11111111:10 3 11111111 2^7 1` `11 11111111:110 1` `12 11111111:1110 1` `13 11111111:11110 1` `15 11111111:1111100 .. 11111111:1111111 4`
在这一情况下,霍夫曼编码表的空间大小为 256(一级表) + 2*119 + 4*8 + 128 = 654,恰好与硬编码中的最大值相吻合。
然而,如果我们构造一个不完整的霍夫曼树,包含大量的长编码,解码器在没有进行越界检查的情况下,将不受限制地分配二级表:
`Len Code range # Root entry Overhead #` `--- ------------------------------------ --- ----------------- -------- ---` `9 00000000:0 1 00000000 2^7 1` `10 00000000:10 1` `11 00000000:110 1` `12 00000000:1110 1` `13 00000000:11110 1` `14 00000000:111110 1` `15 00000000:1111110 .. 00000000:1111111 2` `00000001:0000000 .. 00000010:1111111 256 00000001-00000010 2^7 2` `00000011:0000000 .. 00000011:0001111 1 00000011 2^7 1`
在这一情况下,霍夫曼编码表的空间大小应为 256(一级表) + 128*4 = 768,这无疑超过了硬编码中的最大大小。
随后,解码器将调用BuildHuffmanTable函数,在初始分配的内存空间中构建霍夫曼表,通过ReplicateValue函数向内存中写入倒序前缀码。由于分配的二级表数量超过预期,那么就会发生越界写入,进而可能允许攻击者执行任意代码。
`// Fill in 2nd level tables and add pointers to root table.` `for (len = root_bits + 1, step = 2; len <= MAX_ALLOWED_CODE_LENGTH;` `++len, step <<= 1) {` `// ... snip ...` `for (; count[len] > 0; --count[len]) {` `// ... snip ...` `code.bits = (uint8_t)(len - root_bits);` `code.value = (uint16_t)sorted[symbol++];` `ReplicateValue(&table[key >> root_bits], step, table_size, code); // overflow here` `key = GetNextKey(key, len);` `}` `}`
03 漏洞利用
3.1 如何构造PoC并绕过检查?
我们很自然地想到,如果能够直接构造一个足够大的霍夫曼表,是否就能够溢出程序分配的内存空间了呢?
答案是否定的。因为单个霍夫曼表的溢出长度受到限制,不足以覆盖整个huffman_tables。此外,每次在构建完霍夫曼表后,程序都会检查霍夫曼树的完整性,不完整的树会导致解码过程的中断:
`// Check if tree is full.` `if (num_nodes != 2 * offset[MAX_ALLOWED_CODE_LENGTH] - 1) {` `return 0;` `}`
因此,一个可行的方法是:在保证WebP图片格式合规的情况下,先构建四个正常的霍夫曼表,分别对应绿色、红色、蓝色和alpha通道,并保证它们能够到达各自最大的分配空间。随后,在distance通道构造一个不完整的霍夫曼树,使得所有霍夫曼表的累积大小超出了预定的内存容量,从而导致内存溢出。
3.2 如何触发crash?
我们发现,尽管PoC能触发ASAN的错误报告或造成基于glibc的dwebp工具崩溃,但它并不影响Safari、Chrome等主流浏览器。
经过一番调试,我们发现,默认的霍夫曼表大小为2954个表条目,所分配的空间为2954*4=11816 (0x2e28) 个字节。而在Chromium和WebKit中,malloc(0x2e28)最终会分配出0x3000的内存空间,而样本的溢出长度在0x190字节左右,这导致了我们的溢出并不足以越过分配的内存边界,因此不会触发崩溃。
因此,我们需要使分配出来的霍夫曼表的大小尽可能接近0x3000,这如何做到呢?在漏洞分析章节中,我们提到kTableSize中的绿色通道空间是可变长的,其大小会受到color cache的影响。如果我们设置color cache的大小为6,霍夫曼表的大小就会变成 (630 * 3 + 410 + 718) * 4 = 12072 = 0x2f28 个字节,这样我们就能够覆盖相邻堆块的内容了。
3.3 如何构建利用原语?
为了能够实现可控且稳定的堆越界写,我们需要构建一个强大的利用原语,该原语需要达到的效果是:攻击者能够在任意指定的堆偏移 (Offset) 上写入可控数据 (Value)。
然而,实现这两点在WebP漏洞中都存在一定的困难。一方面,霍夫曼表写入的单位是HuffmanCode,其中只有部分的数据是攻击者可控的;另一方面,HuffmanCode并不是顺序写入霍夫曼表的,其索引是依据倒序哈夫曼编码的规则计算得到的,因此无法直接控制其写入的位置。
3.3.1 如何控制写入的数据?
我们首先关注写入的数据解构,霍夫曼表实际上是包含多个HuffmanCode的数组,其中每个HuffmanCode的结构如下:
`typedef struct {` `uint8_t bits; // number of bits used for this symbol` `uint16_t value; // symbol value or table offset``} HuffmanCode;`
该结构体的内存分布如下:
| bits (1 byte) | padding (1 byte) | value (2 bytes) |
其中,bits字段表示当前HuffmanCode的编码长度,value字段表示当前HuffmanCode的值,每个HuffmanCode占4-bytes大小的内存空间。其中,bits的取值是受到前缀码长度限制的,其取值范围为[1, 15];而value是当前HuffmanCode的实际编码值,该编码值是可以由攻击者控制的,其取值范围取决于编码对象的取值范围,例如对于RGB颜色编码,其取值范围为[0, 255]。
由于我们是在distance通道构造溢出样本,其编码编码符号数为40,因此,我们可以做到写入一个4字节的对象,其中高地址的2字节可控,取值范围是[0, 39]。
3.3.2 如何控制写入的位置?
阅读代码,我们发现HuffmanCode在ReplicateValue函数中被写入到内存中,而其写入的位置是在GetNextKey函数中计算的:
`// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the``// bit-wise reversal of the len least significant bits of key.``static WEBP_INLINE uint32_t GetNextKey(uint32_t key, int len) {` `uint32_t step = 1 << (len - 1);` `while (key & step) {` `step >>= 1;` `}` `return step ? (key & (step - 1)) + step : key;``}`` `` ``code.bits = (uint8_t)(len - root_bits);``code.value = (uint16_t)sorted[symbol++];``ReplicateValue(&table[key >> root_bits], step, table_size, code);`
可以发现,HuffmanCode并非是以顺序写入霍夫曼表,其index是通过reversed prefix code计算得到的。为了能够控制写入的index,我们计算了在15 bits的前缀码长度下(编码域为2^(15-8)=128),其二级霍夫曼表中每个HuffmanCode的index顺序为:
`0x0 0x40 0x20 0x60 0x10 0x50 0x30 0x70 0x8 0x48 0x28 0x68 0x18 0x58 0x38 0x78``0x4 0x44 0x24 0x64 0x14 0x54 0x34 0x74 0xc 0x4c 0x2c 0x6c 0x1c 0x5c 0x3c 0x7c``0x2 0x42 0x22 0x62 0x12 0x52 0x32 0x72 0xa 0x4a 0x2a 0x6a 0x1a 0x5a 0x3a 0x7a``0x6 0x46 0x26 0x66 0x16 0x56 0x36 0x76 0xe 0x4e 0x2e 0x6e 0x1e 0x5e 0x3e 0x7e``0x1 0x41 0x21 0x61 0x11 0x51 0x31 0x71 0x9 0x49 0x29 0x69 0x19 0x59 0x39 0x79``0x5 0x45 0x25 0x65 0x15 0x55 0x35 0x75 0xd 0x4d 0x2d 0x6d 0x1d 0x5d 0x3d 0x7d``0x3 0x43 0x23 0x63 0x13 0x53 0x33 0x73 0xb 0x4b 0x2b 0x6b 0x1b 0x5b 0x3b 0x7b``0x7 0x47 0x27 0x67 0x17 0x57 0x37 0x77 0xf 0x4f 0x2f 0x6f 0x1f 0x5f 0x3f 0x7f`
我们的想法是:在溢出的霍夫曼表中构造4个15-bit的编码,使得其第4个HuffmanCode写入到0x60的index上,使其能够覆盖下一个堆块的数据。另外,我们还可以构造多个9-bit的编码(其二级霍夫曼表的大小为2),使得该index能够以2为单位进行调整,从而实现index可控。
如下图所示:
因此,我们最终实现的原语效果为:攻击者能够以8字节倍数的可控偏移,写入一个部分可控的4字节数据。
01 前言
当我们把这样一个在三方库中的漏洞放到真实的环境中再看时,会发现漏洞所处的环境往往有很多复杂的变量,想要利用这个漏洞并非想象般那么容易。
我们所已知的信息有:
我们溢出的变量 huffman_tables[4] ,大小为0x2f28;
该堆块在renderer的ThreadPool中分配,而大多数对象在主线程中分配;
我们可以以8字节倍数的offset,写入一个部分可控的4字节int
Chrome中不同大小的堆块被存储在不同的bucket当中,不同大小的对象因为这个机制被安全地隔离开。通常来说,在chrome中的堆利用需要找到同样大小的对象进行布局,再通过UAF或是OOB篡改其他的对象,从而造成信息泄露或者控制流劫持。接下来我们会分享我们所发现的对象,同时试图去绕过这个机制。
02 信息泄露
2.1 寻找对象
我们首先想要寻找的是一个合适的对象能够被OOB所改写,由于我们的越界写并不能很好地控制值,所以写指针基本被排除,最好的情况是能够改掉类似length这样的字段,对于值没有精确的要求,但是能够引发进一步更好利用的内存问题。
HuffmanCode在libwebp中是用malloc分配的,在chrome中实际是被PartitionAlloc最终接管分配。在renderer中一共有四个partition[5],LayoutObject partition, Buffer partition, ArrayBuffer partition, FastMalloc partition。FastMalloc实际上最终调用的就是malloc,因此我们想要找的对象可以用FastMalloc来分配。
我们首先用了Man Yue Mo在博客[6]中提到的codeql查询,由于溢出在0x3000的bucket当中,可以选择的对象大小范围为0x2800 - 0x3000。但非常遗憾的是,查询结果为空,这个size下的对象几乎完全不存在。另一个思路是用溢出对象本身,但是这个对象被改掉后不会产生特别的破坏效果,libwebp中也没有其他好的候选对象。到了这里似乎令人觉得有些绝望,利用的第一步就被卡住了。
那么我们还有什么思路呢,一个想法是使用可变长的对象,如Man Yue Mo提到过的AudioArray,但是这个对象是纯数据,被改掉也没有用。查看所有FastMalloc的调用,最终我们发现了这个对象[7]:
`class CORE_EXPORT CSSVariableData : public RefCounted<CSSVariableData> {` `USING_FAST_MALLOC(CSSVariableData);`
此对象的大小为动态的:
`wtf_size_t bytes_needed =` `sizeof(CSSVariableData) + (original_text.Is8Bit()` `? original_text.length()` `: 2 * original_text.length());` `// ... snip ...` `void* buf = WTF::Partitions::FastMalloc(` `bytes_needed, WTF::GetStringWithTypeName<CSSVariableData>());`
该对象代表了CSS中的变量[8],可以通过以下方式来定义:
`element {` `foo: var(--my-var, bar);``}`
blink会根据CSS变量的内容动态分配CSSVariableData的内存。还有一个好消息是,JavaScript中也可以便捷地操作CSS变量。
`// add a CSS variable``element.style.setProperty('foo', 'bar');``// remove a CSS variable``element.style.removeProperty('foo');``// get the value of a CSS variable``getComputedStyle(element).getPropertyValue('foo');`
2.2 Cross-Thread堆占位
我们可以控制CSSVariableData的大小,使其分配至与HuffmanCode同样大小的bucket中。一个自然而然的计划是,分配一堆CSSVariableData,然后free其中一个,再用HuffmanCode占位,如下图所示。
然而设想很美好,实际上PartitionAlloc中使用了ThreadCache[9],对象的分配和释放都会优先在ThreadCache中进行。由于两个对象不在同一个线程中分配,我们需要想办法将CSSVariableData从ThreadCache中移出。阅读ThreadCache的源码[10],我们找到了一个途径:
`uint8_t limit = bucket.limit.load(std::memory_order_relaxed);` `// Batched deallocation, amortizing lock acquisitions.` `if (PA_UNLIKELY(bucket.count > limit)) {` `ClearBucket(bucket, limit / 2);` `}`
当bucket被填满时,有一半的slot会被移出至原来的SlotSpan中。对于0x3000大小的bucket,limit为16。因此我们在释放16个CSSVariableData触发ClearBucket后,再分配HuffmanCode即可占到CSSVariableData的空位,为了保证HuffmanCode后面为想要改的CSSVariableData,需要隔几个释放一次,示意图如下(实际利用中为每7个释放一个)。
2.3 从OOB到UAF
至此我们可以成功分配HuffmanCode至被free的CSSVariableData处,我们需要探究该对象有什么值得更改的字段。
CSSVariableData的内存布局:
回想一下我们漏洞的原语——以8字节倍数的偏移写入4字节。对象中的string改掉也没有意义,那么我们能改的东西就只剩下ref_count_字段了。围绕ref_count_能做的文章有什么?一个自然的想法就是将此原语转化为UAF来进行后续利用。通过OOB将ref_count_改小,再触发减少ref_count_的操作,即可造出一个UAF的对象。
但是OOB写入的值并非完全可控,我们需要先用某种方式增大CSSVariableData的ref_count_至某个特定的值:
`let rs = getComputedStyle(div0);` `// add ref_count with kOverwriteRef` `for (let i = 0; i < kOverwriteRef; i++) {` `rs.getPropertyValue(kTargetCSSVar);` `}`
测试发现,调用getPropertyValue即可临时增加CSSVariableData的ref_count_。而经过GC之后,临时增加的ref_count_会被恢复。因此造出一个UAF的对象需要以下步骤:
1. 分配CSSVariableData,其初始的ref_count_为2
2. 调用getPropertyValue kOverwriteRef次,此时ref_count_为kOverwriteRef + 2。
3. 触发webp的漏洞,将ref_count_改为kOverwriteRef 。
4. 触发GC,CSSVariableData被free。
5. 再次调用getPropertyValue即可触发UAF。
在getPropertyValue时,blink会根据length_构造string返回到js中。因此我们只需分配一个数据完全可控的对象(AudioArray[11]),伪造CSSVariableData的length_字段,即可达到在堆上越界读的效果。
_03_ Cross-Bucket分配
我们将OOB转化成了UAF,但是这个UAF的对象仅能造成堆上越界读的效果,假设这样可以解决信息泄露的问题(其实此时还并未解决),但仍无法完成进一步的利用。
在以往的blink堆利用中,占位后往往将目光聚焦于同样大小的对象,因为他们天生被分配在一起。但是此刻,0x3000的bucket内已经没有其他更好的对象能够利用,那么我们能否攻击其他大小的对象?经过我们的研究,答案是肯定的。
PartitionAlloc将堆的metadata(SlotSpanMetadata)放在被隔离开的页上,在用户分配的堆块上唯一剩下的管理信息就是freelist指针,如果我们可以将此指针更改,即可达到任意地址分配的效果。在常规的SlotSpan中free时,有double free的检查:
`PA_ALWAYS_INLINE void SlotSpanMetadata::Free(uintptr_t slot_start,` `PartitionRoot* root)` `// ... snip ...` `auto* entry = static_cast<internal::EncodedNextFreelistEntry*>(` `SlotStartAddr2Ptr(slot_start));` `// Catches an immediate double free.` `PA_CHECK(entry != freelist_head);`
在ThreadCache中,没有double free的检查,我们可以任意多次free相同地址。而分配时有检查,注释中说得很清楚:
`PA_ALWAYS_INLINE static bool IsSane(const EncodedNextFreelistEntry* here,` `const EncodedNextFreelistEntry* next,` `bool for_thread_cache) {` `// Don't allow the freelist to be blindly followed to any location.` `// Checks two constraints:` `// - here and next must belong to the same superpage, unless this is in the` `// thread cache (they even always belong to the same slot span).` `// - next cannot point inside the metadata area.` `//` `// Also, the lightweight UaF detection (pointer shadow) is checked.`
我们想要任意分配的地方不能属于metadata(曾经用此手段可获得任意地址读写的能力,参考[12]),且要和原来的slot处于同一个superpage内,而这两点都可以轻易满足。
因此,我们假设CSSVariableData为A,占位的AudioArray为B(A和B实际为相同的地址),我们做一个类似经典的fastbin attack,即可做到任意地址分配。
1. free(A)
2. free(B)
3. malloc(C),修改freelist为地址0xdeadbeef
4. malloc(D)
5. malloc(E),此时分配到的E即为地址0xdeadbeef
那么要分配到哪里呢?
PartitionAlloc不同大小的对象通过bucket来管理,bucket通过SlotSpan来管理同样大小的slot,SlotSpan的单位为partition page,具体的概念及策略可参考官方文档[13],如0x3000 slot size的SlotSpan由3个partition page组成,总大小0xc000,共可以分配4个0x3000的slot。
在内存中管理不同slot大小的SlotSpan实际可能是相邻的。那么我们只需要将我们感兴趣的对象所在的SlotSpan分配到0x3000 slot size的SlotSpan附近,即可完成信息泄露+对象劫持。我们参考Man Yue Mo的博客,最终选取了HRTFPanner(slot size为0x500)作为我们要攻击的对象。
我们在内存中以如下方式喷对象,更改freelist指针即可做到从0x3000的slot分配到0x500的slot上。
_04_ RCE
结合之前所有的知识,我们串联一下最终的步骤
1. 堆喷大量0x3000和0x500的SlotSpan。
2. 触发webp漏洞,转化为CSSVariableData的UAF。
3. 使用AudioArray占上被free的CSSVariableData,利用UAF获得信息泄露。
4. Cross-Bucket分配到HRTFPanner上,伪造HRTFPanner对象。
5. 触发HRTFPanner的析构获得任意代码执行。
_05_ 结论
在本篇博客中,我们详细讨论了如何在Chrome中利用一个品相并非那么好的OOB写漏洞。本次测试仅为了展示该漏洞的可利用性,并未对成功率做过多优化。这个漏洞如何在iOS环境下转化并在PAC等缓解机制下利用仍然是一个开放问题。
从这个案例中明显可以看出,随着各种缓解机制的引入,漏洞严重性评判越来越复杂,单一尺度评级的局限性愈发明显。
深蓝公开以上对libwebp漏洞定位、分析、预警、利用重现的闭环研究, 是希望这种从攻击者视角出发、结合环境特性的对抗性研判研究,进一步推动漏洞科学研判的发展。
参 考:
[1] https://github.com/webmproject/libwebp/commit/902bc9190331343b2017211debcec8d2ab87e17a
[2] https://developers.google.com/speed/webp/docs/webp\_lossless\_bitstream\_specification
[3] https://news.ycombinator.com/item?id=37483981
[6] https://securitylab.github.com/research/one\_day\_short\_of\_a\_fullchain\_renderer/
[8] https://developer.mozilla.org/en-US/docs/Web/CSS/Using\_CSS\_custom\_properties
[12] https://securelist.com/the-zero-day-exploits-of-operation-wizardopium/97086/
点击“阅读原文”
直达 DARKNAVY 技术博客