browser-pwn cve-2020-6418漏洞分析
字数 1955 2025-08-26 22:11:57

CVE-2020-6418 V8漏洞分析与利用技术详解

基础知识:Pointer Compression

Pointer compression是V8 8.0中为提高64位机器内存利用率而引入的机制:

  1. 指针长度变化

    • 之前指针都是64位,现在压缩为32位
    • 高32位地址保存在根寄存器(r13)中
    • 访问对象时只需提供低32位地址,加上r13得到完整地址
  2. SMI(Small Integer)表示变化

    • 之前64位系统中SMI表示是value<<32
    • 现在表示为value<<1,占用32字节
    • 左移一位相当于乘以2
  3. 内存影响

    • 数组从SMI转DOUBLE时占用空间几乎翻倍
    • 从DOUBLE转object数组时占用空间缩小一半
    • 内存利用率提升接近40%

漏洞描述

CVE-2020-6418是V8的类型混淆漏洞:

  • 影响版本:Chrome 80.0.3987.122之前版本
  • 漏洞本质:a.pop调用时未考虑JSCreate结点的side-effect
  • 触发条件:回调函数改变数组类型(SMI→DOUBLE),但后续仍按SMI处理

漏洞分析

PoC分析

let a = [0, 1, 2, 3, 4];

function empty() {}

function f(p) {
  a.pop(Reflect.construct(empty, arguments, p));
}

let p = new Proxy(Object, {
    get: () => (a[0] = 1.1, Object.prototype)
});

function main(p) {
  f(p);
}

%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);

main(empty);
main(empty);
%OptimizeFunctionOnNextCall(main);
main(p);

关键点:

  1. Proxy回调中将数组a从PACKED_SMI_ELEMENTS改为PACKED_DOUBLE_ELEMENTS
  2. 优化后pop仍按SMI处理,导致类型混淆
  3. 实际输出应为2,但输出0(SMI读取DOUBLE数据)

源码分析

JSCallReducer中的builtin inlining

builtin inlining发生在两个阶段:

  1. inlining and native context specialization阶段
  2. typed lowering阶段

Array.prototype.pop在JSCallReducer中进行内联优化:

Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) {
  // 获取value/effect/control输入
  Node* receiver = NodeProperties::GetValueInput(node, 1);
  Node* effect = NodeProperties::GetEffectInput(node);
  Node* control = NodeProperties::GetControlInput(node);

  // 推断对象类型
  MapInference inference(broker(), receiver, effect);
  if (!inference.HaveMaps()) return NoChange();
  
  // 确定类型是否可靠
  inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
                                      control, p.feedback());
  
  // 根据类型实现pop功能
  // ...
}

MapInference机制

MapInference类负责:

  1. 推断传入对象的类型(MAP)
  2. 根据effect决定返回的MAP是否可靠(reliable)

InferReceiverMapsUnsafe函数判断MAP是否可靠:

  • 遍历effect链,检查是否有改变对象类型的操作
  • 无改变则返回kReliableReceiverMaps
  • 有改变则返回kUnreliableReceiverMaps

漏洞根源

漏洞版本中InferReceiverMapsUnsafekJSCreate结点处理不当:

  • 认为JSCreate没有side-effect
  • 实际Reflect.construct会转换成JSCreate结点
  • 通过Proxy可触发回调修改对象类型

补丁后代码在kJSCreate处强制标记为kUnreliableReceiverMaps

漏洞利用

利用思路

  1. 利用类型混淆覆盖float数组的length字段
  2. 通过越界读写找到布置的BigUint64Array
  3. 覆盖BigUint64Array的base_pointer/external_pointer实现任意读写

BigUint64Array结构

关键字段(均为64位无tag):

  • length
  • base_pointer
  • external_pointer

数据存储:

  • data_ptr = base_pointer + external_pointer
  • 初始时external_pointer是根寄存器r13的高32位

利用方式:

  1. 覆盖base_pointer实现4GB堆空间内相对地址读写
  2. 读取external_pointer获取根值
  3. 覆盖两者实现绝对地址读写

利用步骤

  1. 构造越界读写:
const MAX_ITERATIONS = 0x10000;
var maxSize = 1020*4;
var vulnArray = [,,,,,,,,,,,,,, 1.1, 2.2, 3.3];
// ... 触发类型混淆覆盖length
  1. 构造AAR/AAW原语:

    • 通过越界读写找到BigUint64Array
    • 覆盖其指针字段
  2. 构造AddrOf/FakeObj原语:

    • 覆盖object数组字段(32字节)
  3. 定位wasm对象rwx内存并执行shellcode

其他注意事项

为什么需要外层main函数:

  • 直接调用f会被转换成JSCallForwardVarargs结点
  • 外层函数会保持JSConstructWithArrayLike结点
  • 确保生成JSCreate结点触发漏洞

总结

  1. 漏洞本质:JSCreate结点side-effect判断错误导致类型混淆
  2. 利用关键:通过Proxy回调改变数组类型
  3. 新挑战:Pointer compression下的利用技术
  4. 影响范围:Chrome 80.0.3987.122之前版本

参考链接

  1. Pointer Compression in V8
  2. V8 release v8.0
  3. Compressed pointers in V8
  4. Fix bug in receiver maps inference
CVE-2020-6418 V8漏洞分析与利用技术详解 基础知识:Pointer Compression Pointer compression是V8 8.0中为提高64位机器内存利用率而引入的机制: 指针长度变化 : 之前指针都是64位,现在压缩为32位 高32位地址保存在根寄存器(r13)中 访问对象时只需提供低32位地址,加上r13得到完整地址 SMI(Small Integer)表示变化 : 之前64位系统中SMI表示是value< <32 现在表示为value< <1,占用32字节 左移一位相当于乘以2 内存影响 : 数组从SMI转DOUBLE时占用空间几乎翻倍 从DOUBLE转object数组时占用空间缩小一半 内存利用率提升接近40% 漏洞描述 CVE-2020-6418是V8的类型混淆漏洞: 影响版本:Chrome 80.0.3987.122之前版本 漏洞本质: a.pop 调用时未考虑JSCreate结点的side-effect 触发条件:回调函数改变数组类型(SMI→DOUBLE),但后续仍按SMI处理 漏洞分析 PoC分析 关键点: Proxy回调中将数组a从PACKED_ SMI_ ELEMENTS改为PACKED_ DOUBLE_ ELEMENTS 优化后pop仍按SMI处理,导致类型混淆 实际输出应为2,但输出0(SMI读取DOUBLE数据) 源码分析 JSCallReducer中的builtin inlining builtin inlining发生在两个阶段: inlining and native context specialization阶段 typed lowering阶段 Array.prototype.pop 在JSCallReducer中进行内联优化: MapInference机制 MapInference 类负责: 推断传入对象的类型(MAP) 根据effect决定返回的MAP是否可靠(reliable) InferReceiverMapsUnsafe 函数判断MAP是否可靠: 遍历effect链,检查是否有改变对象类型的操作 无改变则返回kReliableReceiverMaps 有改变则返回kUnreliableReceiverMaps 漏洞根源 漏洞版本中 InferReceiverMapsUnsafe 对 kJSCreate 结点处理不当: 认为 JSCreate 没有side-effect 实际 Reflect.construct 会转换成 JSCreate 结点 通过Proxy可触发回调修改对象类型 补丁后代码在 kJSCreate 处强制标记为 kUnreliableReceiverMaps 漏洞利用 利用思路 利用类型混淆覆盖float数组的length字段 通过越界读写找到布置的BigUint64Array 覆盖BigUint64Array的base_ pointer/external_ pointer实现任意读写 BigUint64Array结构 关键字段(均为64位无tag): length base_ pointer external_ pointer 数据存储: data_ ptr = base_ pointer + external_ pointer 初始时external_ pointer是根寄存器r13的高32位 利用方式: 覆盖base_ pointer实现4GB堆空间内相对地址读写 读取external_ pointer获取根值 覆盖两者实现绝对地址读写 利用步骤 构造越界读写: 构造AAR/AAW原语: 通过越界读写找到BigUint64Array 覆盖其指针字段 构造AddrOf/FakeObj原语: 覆盖object数组字段(32字节) 定位wasm对象rwx内存并执行shellcode 其他注意事项 为什么需要外层main函数: 直接调用f会被转换成JSCallForwardVarargs结点 外层函数会保持JSConstructWithArrayLike结点 确保生成JSCreate结点触发漏洞 总结 漏洞本质:JSCreate结点side-effect判断错误导致类型混淆 利用关键:通过Proxy回调改变数组类型 新挑战:Pointer compression下的利用技术 影响范围:Chrome 80.0.3987.122之前版本 参考链接 Pointer Compression in V8 V8 release v8.0 Compressed pointers in V8 Fix bug in receiver maps inference