browser-exploitation-v8 について
browser-exploitation-v8 は、V8 と Chrome の攻撃手法を学ぶためのセキュリティスキルです。JIT の脆弱性、メモリ操作プリミティブ、ポインター圧縮の制約、レンダラー侵害から Chrome サンドボックス脱出までを解説します。
主な機能
- 01V8 のコンパイル工程とオブジェクト表現
- 02JIT バグの種類と発生方法
- 03addrof と fakeobj のプリミティブ
- 04ArrayBuffer と WASM のメモリ操作
- 05V8 サンドボックスと Chrome 脱出の概要
活用シーン
- 01V8 の攻撃概念の学習
- 02JIT 型混同脆弱性の分析
- 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 サンドボックスの回避技術
- オブジェクトマップの混同パターン
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:オブジェクトはケージのベースからの 32 ビットオフセットを使ってアドレス指定されます(4GB のサンドボックス) |
| マップ(隠しクラス) | オブジェクトの形状を定義します。プロパティ名、型、オフセットが含まれます |
| 要素の種類 | 内部配列型: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 ノードが削除され、OOB アクセスが発生します |
| プロトタイプチェーンの混同 | 最適化でプロトタイプが安定していると仮定しますが、変更によって前提が無効になります | 最適化後にプロトタイプが変更され、誤ったプロパティアクセスが発生します |
| TurboFan の縮約バグ | 強度縮約や定数畳み込みが誤っています | 範囲解析での整数オーバーフロー |
| 競合状態 | SharedArrayBuffer とワーカースレッドによる競合 | 並行変更による型混同 |
| 組み込み機能の off-by-one | 組み込み関数の実装に境界エラーがあります | 文字列/配列の境界 |
| 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. 配列境界の混同による OOB 読み書き
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 のバッキングストアは、割り当てられたメモリーへの生ポインターです。これを破損させると、任意のメモリーの読み書きが可能になります。
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 ケージ内、4GB 領域)- V8 ケージの外部を直接指すことはできません
- プロセスのメモリー全体にアクセスするにはサンドボックスエスケープが必要です
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 関数のエントリーを上書きし、制御可能なシェルコードへジャンプさせる |
| JIT コードの破損 | 競合状態またはポインターの取り違えにより、JIT コードページに書き込む |
| Mojo IPC(Chrome) | Chrome IPC を悪用し、侵害されたレンダラーからブラウザープロセスを攻撃する |
| バッキングストアのシール回避 | 型の取り違えを見つけ、サンドボックス外のポインターを取得する |
8. CHROME サンドボックスからの脱出(概要)
レンダラープロセスは(V8 のエクスプロイトによって)RCE が発生した後も、サンドボックス内にあります。完全に侵害するには、次の手順が必要です。
| 段階 | 対象 | 例 |
|---|---|---|
| レンダラーのエクスプロイト | V8 / Blink DOM | 型の取り違え → シェルコード |
| IPC/Mojo のバグ | Chrome IPC レイヤー | Mojo インターフェイスの解放後使用 |
| ブラウザープロセスのエクスプロイト | 特権を持つブラウザープロセス | サンドボックス外でのコード実行 |
Mojo インターフェイス(Chrome の IPC)は攻撃対象領域となります。Mojo メッセージハンドラーで解放後使用や型の取り違えを探してください。
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)