关于 browser-exploitation-v8
browser-exploitation-v8 是研究 V8 与 Chrome 利用技术的安全技能,讲解 JIT 漏洞、内存原语、指针压缩限制,以及从渲染器失陷到 Chrome 沙箱逃逸的过程。
主要功能
- 01V8 编译流程与对象表示
- 02JIT 漏洞类型及触发方式
- 03addrof 与 fakeobj 原语
- 04ArrayBuffer 与 WASM 内存技术
- 05V8 沙箱与 Chrome 逃逸概述
适用场景
- 01学习 V8 利用相关概念
- 02分析 JIT 类型混淆漏洞
- 03理解指针压缩的限制
- 04梳理渲染器漏洞到沙箱逃逸的路径
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill browser-exploitation-v8安装后在 Claude Code 中输入/browser-exploitation-v8确认已加载。
附带文件V8_EXPLOITATION_PATTERNS.md
详细介绍
技能:浏览器 / V8 利用——专家攻击手册
AI 加载指令:专家级 V8/Chrome 利用技术。涵盖 V8 编译流水线、JIT 类型混淆、addrof/fakeobj 原语、ArrayBuffer 损坏、WASM RWX 页面、V8 沙箱(指针压缩)以及 Chrome 沙箱逃逸概述。内容提炼自 ctf-wiki 浏览器章节、Project Zero 研究和 CTF 竞赛中的常见模式。基础模型经常混淆 V8 对象表示细节,并忽略指针压缩带来的限制。
0. 相关路由
- 沙箱逃逸技术 — 通过 IPC/Mojo 逃逸 Chrome 渲染器沙箱
- 堆利用 — 适用于 V8 堆的一般堆概念
- 栈溢出与 ROP — V8 逃逸后执行原生代码所需的 ROP 概念
- 二进制保护绕过 — 浏览器环境中的 ASLR/NX 绕过
高级参考
在需要以下内容时,加载 V8_EXPLOITATION_PATTERNS.md:
- 详细的利用模式和代码模板
- 堆布局操纵和 GC 交互
- V8 沙箱绕过技术
- 对象 map 混淆模式
1. V8 架构
编译流水线
JavaScript Source
↓ Parser
AST (Abstract Syntax Tree)
↓ Ignition
Bytecode (interpreted, profiling)
↓ Sparkplug (non-optimizing baseline, V8 ≥ 9.1)
Baseline code (fast startup)
↓ Maglev (mid-tier, V8 ≥ 10.2)
Mid-optimized code
↓ TurboFan (optimizing JIT)
Optimized machine code (with speculative optimizations)
↓ Deoptimization (if speculation fails)
Back to Ignition bytecode
V8 核心概念
| 概念 | 描述 |
|---|---|
| 带标签的指针 | SMI(小整数):value << 1,HeapObject:ptr | 1 |
| 指针压缩 | V8 ≥ 8.0:通过相对于 cage 基址的 32 位偏移量寻址对象(4GB 沙箱) |
| Map(隐藏类) | 定义对象形状:属性名称、类型、偏移量 |
| Elements kinds | 内部数组类型:PACKED_SMI_ELEMENTS、PACKED_DOUBLE_ELEMENTS、PACKED_ELEMENTS 等 |
| 写屏障 | 写入堆指针时进行 GC 记账 |
| 垃圾回收 | Orinoco GC:次要回收(Scavenge)和主要回收(Mark-Compact) |
对象表示(64 位,指针压缩)
HeapObject in V8 heap (compressed):
+0x00: Map pointer (compressed, 32-bit offset)
+0x04: Properties/Hash
+0x08: Elements pointer (compressed)
+0x0C: Length (for arrays)
+0x10: Inline properties or backing store data
2. 常见 V8 漏洞类别
| 漏洞类别 | 描述 | 示例 |
|---|---|---|
| JIT 类型混淆 | TurboFan 在优化后假定类型错误 | 推测类型检查被消除,导致应用了错误操作 |
| 错误的边界检查消除 | JIT 基于错误的范围分析移除数组边界检查 | CheckBounds 节点被消除 → 越界访问 |
| 原型链混淆 | 优化假定原型稳定,但原型变更会使该假定失效 | 优化后原型发生变化 → 属性访问错误 |
| Turbofan 归约错误 | 强度归约或常量折叠错误 | 范围分析中的整数溢出 |
| 竞态条件 | SharedArrayBuffer 与工作线程发生竞态 | 通过并发修改导致类型混淆 |
| 内置函数中的差一错误 | 内置函数实现中的边界错误 | 字符串 / 数组边界 |
| Typer 错误 | TurboFan 中类型范围计算错误 | Typer 认为值在 [0, N] 范围内,但实际可能是 N+1 |
触发 JIT 优化
function vuln(arr) {
// ... vulnerable code path ...
}
// Force optimization by calling many times
for (let i = 0; i < 100000; i++) {
vuln(arr);
}
// Or use V8 intrinsics (d8 only):
%OptimizeFunctionOnNextCall(vuln);
vuln(arr);
3. 利用原语
addrof — 泄露对象地址
// Goal: get the raw heap address of a JavaScript object
// Method: type confusion between object array and float array
// If we can confuse PACKED_ELEMENTS array with PACKED_DOUBLE_ELEMENTS:
// - Write object reference to element of object array
// - Read same element as double from confused float array
// - Float bits = compressed pointer of the object
function addrof(obj) {
// Setup depends on specific bug
// Typically: trigger type confusion so array reads obj ref as float
object_array[0] = obj;
return ftoi(confused_float_array[0]); // float-to-int conversion
}
fakeobj — 创建伪造的对象引用
// Goal: create a JS reference to an arbitrary heap address
// Method: reverse of addrof — write float (raw pointer bits) to float array,
// read from confused object array → treated as object reference
function fakeobj(addr) {
confused_float_array[0] = itof(addr); // int-to-float conversion
return object_array[0]; // now a "pointer" to addr
}
通过 addrof + fakeobj 构建任意读 / 写
// 1. Create a Float64Array with known layout
let rw_array = new Float64Array(0x100);
let rw_array_addr = addrof(rw_array);
// 2. Fake a Float64Array object at controlled address with modified backing_store
// 3. Corrupt backing_store pointer to target address
// 4. Read/write through the fake Float64Array → arbitrary R/W
function read64(addr) {
// Set fake array's backing_store = addr
write_to_fake_backingstore(addr);
return fake_float64array[0];
}
function write64(addr, value) {
write_to_fake_backingstore(addr);
fake_float64array[0] = value;
}
4. 通过混淆数组边界实现越界读 / 写
当 TurboFan 错误地消除边界检查时:
function trigger(arr, idx) {
// TurboFan thinks idx is always < arr.length
// But due to bug, idx can exceed bounds
return arr[idx]; // OOB read
}
// OOB read adjacent memory (next heap object's metadata)
// OOB write to corrupt next object's map/elements/length
V8 堆中相邻的是什么?
对象在 V8 的新生代(new space)中按顺序分配。通过控制分配顺序:
let arr1 = new Array(0x10); // spray object
let arr2 = new Float64Array(0x10); // target: adjacent to arr1
// OOB from arr1 can reach arr2's metadata
// Corrupt arr2's length → unconstrained OOB on arr2
5. ArrayBuffer 任意读 / 写
ArrayBuffer 的 backing store 是指向已分配内存的原始指针。破坏它即可实现绝对内存读 / 写。
let ab = new ArrayBuffer(0x100);
let view = new DataView(ab);
// If we can overwrite ab's backing_store pointer:
// ab.backing_store = target_addr
// view.getFloat64(0) → reads 8 bytes from target_addr
// view.setFloat64(0, val) → writes to target_addr
V8 沙箱(指针压缩)的影响
由于 V8 ≥ 8.0(指针压缩)以及 V8 沙箱(≥ 11.x):
ArrayBuffer.backing_store是一个沙箱指针(位于 V8 cage 的 4GB 区域内)- 无法直接指向 V8 cage 之外
- 需要逃逸沙箱才能访问完整的进程内存
6. WASM RWX 页面
在某些平台上,WebAssembly JIT 代码会放置在 RWX(可读、可写、可执行)页面中。
// Allocate WASM module → JIT compiles to RWX page
let wasm_code = new Uint8Array([0x00, 0x61, 0x73, 0x6d, ...]);
let mod = new WebAssembly.Module(wasm_code);
let instance = new WebAssembly.Instance(mod);
// instance.exports.func → points to RWX page
// If we can find and write to this page:
// 1. addrof(instance) → find WASM instance object
// 2. Follow pointers: instance → jump_table_start → RWX page
// 3. Use arbitrary write to overwrite RWX page with shellcode
// 4. Call instance.exports.func() → executes shellcode
现代 Chrome:W^X 强制机制意味着 WASM 页面只能是 RW 或 RX,不能同时为 RWX。JIT 代码先以 RW 模式写入,再切换为 RX。要利用这一点,需要找到可写窗口或使用 JIT 喷射。
7. V8 沙箱
架构(V8 ≥ 11.x)
Process Virtual Address Space:
┌──────────────────────────────────────┐
│ V8 Sandbox Cage (4GB region) │
│ ├── V8 Heap (JS objects) │
│ ├── ArrayBuffer backing stores │
│ ├── WASM memory │
│ └── External pointer table │
├──────────────────────────────────────┤
│ Process memory outside cage │
│ ├── libc, Chrome code │
│ ├── Stack │
│ └── Other allocations │
└──────────────────────────────────────┘
沙箱逃逸途径
| 途径 | 方法 |
|---|---|
| 外部指针表 | 篡改外部指针表中的条目,使其指向任意地址 |
| WASM 代码指针 | 覆盖 WASM 函数入口,使其跳转到受控的 shellcode |
| JIT 代码破坏 | 通过竞争条件或混淆指针写入 JIT 代码页面 |
| Mojo IPC(Chrome) | 利用 Chrome IPC,从受攻击的渲染器攻击浏览器进程 |
| 后备存储封印绕过 | 利用类型混淆获取未受沙箱限制的指针 |
8. Chrome 沙箱逃逸(概述)
通过 V8 漏洞利用实现渲染器 RCE 后,进程仍处于沙箱中。要实现完全攻陷,还需要:
| 阶段 | 目标 | 示例 |
|---|---|---|
| 渲染器漏洞利用 | V8 / Blink DOM | 类型混淆 → shellcode |
| IPC/Mojo 漏洞 | Chrome IPC 层 | Mojo 接口中的释放后使用漏洞 |
| 浏览器进程漏洞利用 | 特权浏览器进程 | 在沙箱外执行代码 |
Mojo 接口(Chrome 的 IPC)暴露了攻击面:寻找 Mojo 消息处理程序中的 UAF 或类型混淆。
9. 工具
# V8 debugging
d8 --allow-natives-syntax exploit.js # Enable V8 intrinsics (%DebugPrint, etc.)
d8 --trace-turbo exploit.js # Dump TurboFan IR
d8 --print-opt-code exploit.js # Print optimized machine code
# Turbolizer: visual TurboFan IR graph
# Chrome DevTools Memory panel: heap snapshots
# Build V8 for debugging
git clone https://chromium.googlesource.com/v8/v8.git
gclient sync
gn gen out/debug --args='is_debug=true v8_enable_sandbox=false'
ninja -C out/debug d8
10. 决策树
V8 vulnerability identified
├── Bug type?
│ ├── JIT type confusion → trigger optimization, confuse array element kinds
│ ├── Bounds check elimination → OOB read/write on array
│ ├── Typer bug → incorrect range leads to OOB
│ └── Builtin bug → direct memory corruption primitive
│
├── Build primitives
│ ├── Can confuse object array ↔ float array?
│ │ └── addrof + fakeobj → arbitrary R/W within V8 heap
│ ├── OOB on array?
│ │ └── Corrupt adjacent object (length/backing_store) → expand to full R/W
│ └── Direct write primitive?
│ └── Target WASM instance or ArrayBuffer metadata
│
├── V8 sandbox enabled?
│ ├── YES (modern Chrome) →
│ │ ├── R/W limited to V8 cage (4GB)
│ │ ├── Need sandbox escape: external pointer table corruption,
│ │ │ WASM code pointer overwrite, or Mojo bug
│ │ └── Then proceed to shellcode execution
│ └── NO (older V8, CTF, d8) →
│ ├── Corrupt ArrayBuffer backing_store → absolute R/W
│ └── Overwrite WASM RWX page → shellcode
│
├── Code execution method
│ ├── WASM RWX page available? → write shellcode, call WASM func
│ ├── JIT code writable? → overwrite JIT code
│ └── ROP needed? → corrupt stack or return address
│
└── Full browser exploit chain
├── Stage 1: V8 bug → renderer RCE
├── Stage 2: Mojo IPC bug → browser process compromise
└── Stage 3: OS-level escalation (if needed)