
Lehrreicher Proof-of-Concept für CVE-2024-4947, eine V8-Maglev-Type-Confusion, die eine vollständige Kette vom Trigger bis zur Ausführung beliebigen Codes auf einem nicht gesandboxten d8-Build demonstriert.
Eine vollständige, eigenständige Exploitation-Kette für CVE-2024-4947 (V8-Maglev-Type-Confusion), vom anfänglichen Type-Confusion-Trigger bis zur beliebigen Codeausführung. Die PoC läuft gegen einen nicht-sandboxed d8-Build und gibt CVE-2024-4947-PWNED als Beweis der Codeausführung auf stdout aus.
⚠️ Dies ist eine Forschungs-/Bildungs-PoC für eine gepatchte, öffentliche Schwachstelle. Sie zielt auf einen Entwicklungs-Build der V8-Shell (
d8 --allow-natives-syntax) — nicht auf einen echten Chrome-Browser. Siehe Disclaimer.
$ ./v8-build-nosandbox.sh # build the vulnerable d8 (WSL2 Ubuntu, ~10-20 min)
$ d8 --allow-natives-syntax --module exploit/exploit_rce.mjs
[engine] dblData0=0004f470 class=0
[inst] addr=0x001dc274 trusted_data(tagged)=0x00202bd5 td=0x00202bd4
[jt] jump_table_start = 0x00000967426cd000 (external code space)
[bridge] memory0_start -> jt, memory0_size -> huge (r/w reach jt+off)
[slot0] before: e9 3b 08 00 00
[shell] wrote 52 bytes @ jt+0x0100: VERIFIED
CVE-2024-4947-PWNED
$ echo $? # 0
Der 52-Byte-Shellcode ist write(1, "CVE-2024-4947-PWNED\n", 20); exit_group(0).
CVE-2024-4947 ist eine Type Confusion im V8-Maglev-JIT-Compiler, die in Chrome 125.0.6422.60 (Commit b3c01ac1e60a) behoben wurde. Sie wurde von der Lazarus-APT-Gruppe in freier Wildbahn ausgenutzt. Wenn Maglev einen Store auf ein Modul-Namespace-Objekt (JSModuleNamespace) kompiliert, verwendet es eine falsche AccessInfo — der Store wird als einfaches mov [[obj + 4], rax] kompiliert, das einen kontrollierten Wert in das map-Feld eines benachbarten Objekts schreibt, anstatt den korrekten Property-Store-Pfad zu durchlaufen.
Die Ausnutzung erfolgt wie folgt:
NAME_DICTIONARY_TYPE-Map (0xB2), wodurch sich ändert, wo V8 den Hash des Objekts speichert.new WeakRef(...), sodass der Hash-Schreibvorgang im Length-Slot eines benachbarten Objekts landet → Out-of-Bounds-Zugriff.Von dort aus haben wir das klassische V8-Toolkit: addrOf/fakeObj, danach beliebiges Lesen/Schreiben innerhalb des Cage.
CVE-2024-4947 trigger (fake NAME_DICTIONARY map + WeakRef hash write)
└─► OOB write ─► corrupt doubleArray length
└─► in-cage 4/8-byte arbitrary R/W (the "engine")
└─► overwrite WasmTrustedInstanceData.memory0_start (+0x18)
& memory0_size (+0x20) → huge
└─► wasm load8_u / store8 = clean 64-bit R/W bridge
(no software bounds check in compiled code)
└─► read jump_table_start (+0x38) — external code space
└─► jump table region is RWX (this build)
└─► write shellcode into the slack (jt+0x100)
└─► repoint func0's `e9 rel32` slot at it
└─► call func0 → shellcode → RCE
| # | Stufe | Detail |
|---|---|---|
| 1 | Trigger | opt() schreibt eine gefälschte Map über den fehlerhaften Store; new WeakRef(m) erzeugt einen OOB-Zugriff auf corruptArray.length. |
| 2 | Engine | addrOf/fakeObj; die Länge von doubleArray korrumpieren → beliebiges 4-Byte-Lesen/Schreiben an jeder 4-ausgerichteten Adresse im Cage. |
| 3 | 64-Bit-Brücke | WasmTrustedInstanceData.memory0_start (+0x18) ist ein roher 64-Bit-Zeiger, der von kompiliertem Wasm i32.load8_u/i32.store8 ohne Software-Bounds-Check verwendet wird (ein Zugriff außerhalb des Bereichs trifft auf eine Guard-Page → SIGSEGV → Wasm-Trap). Leite ihn auf eine beliebige Adresse um und setze memory0_size (+0x20) auf einen riesigen Wert → beliebiges 64-Bit-Lesen/Schreiben. |
| 4 | Code-Space finden | jump_table_start (+0x38) ist ein roher 64-Bit-Zeiger in den externen Code-Space (außerhalb des 4-GB-Cage). |
| 5 | Shellcode schreiben | Bei diesem Build ist die Jump-Table-Region RWX (V8 patcht Einträge zur Laufzeit): Schreibe den 52-Byte-Shellcode in den Slack bei jt+0x100. |
| 6 | Slot neu ausrichten | Der Jump-Table-Slot von func0 ist ein 5-Byte-e9 <rel32> (Ziel = Slot + 5 + rel32). Setze rel32 → jt+0x100. |
| 7 | Dispatch auslösen | inst.exports.r(0) → JSToWasmWrapper führt den Dispatch über Slot 0 aus → Shellcode wird ausgeführt. |
Die meisten öffentlichen CVE-2024-4947-PoCs zielen auf das gesandboxte d8 oder einen echten Chrome-Renderer (v8_enable_sandbox=true) und bleiben bei der Type-Confusion-/OOB-Primitive stehen. Diese PoC zielt auf ein No-Sandbox-d8 und führt die Kette bis zur Codeausführung. Die Unterschiede, die zählen:
| Dimension | Üblicher Ansatz öffentlicher PoCs | Diese PoC |
|---|---|---|
| Ziel-Build | gesandboxtes d8 / Chrome-Renderer | v8_enable_sandbox=false d8 — Trusted-Pointer sind direkte Zeiger, externer Code-Space ist aktiv |
| 64-Bit-R/W-Brücke | JSTypedArray.external_pointer überschreiben | Dieser Pfad führt beim Lesen des Code-Range zu einem Absturz (empirisch verifiziert); stattdessen memory0_start überschreiben und rohe Wasm-Loads/Stores verwenden |
| Letzter Weg zur Codeausführung | Code-Space ist W^X → JIT-Spray + indirekte Umleitung | Jump-Table-Region ist RWX → direkter Shellcode-Write + e9 rel32-Slot-Patch |
| Jump-Table-Slot-Format | Doku nimmt üblicherweise movabs rax, imm64; jmp rax an (12 Bytes) | gemessen: 5-Byte-e9 <rel32>, Ziel = Slot + 5 + rel32 |
| Feld-Offsets | Layout des Sandbox-Builds | No-Sandbox-WasmTrustedInstanceData: jump_table_start@+0x38, memory0_start@+0x18, memory0_size@+0x20; WasmInstanceObject.trusted_data@+0x0c |
| Sauberer Exit | — | d8 ist multi-threaded: exit_group (231) verwenden, nicht exit (60), sonst hängt der Prozess |
Siehe docs/walkthrough.md für den vollständigen technischen Bericht, einschließlich der empirischen Erkenntnisse hinter jeder Zeile.
.
├── exploit/
│ ├── Module.mjs # module namespace object corrupted by the trigger
│ ├── exploit_rce.mjs # the full chain (trigger → arbitrary R/W → RCE)
│ └── shellcode.S # assembly source for the 52-byte payload
├── build/
│ └── v8-build-nosandbox.sh # build the vulnerable no-sandbox d8 from V8 source
└── docs/
└── walkthrough.md # deep dive: bridge mechanics, layouts, gotchas
Das Exploit importiert Module.mjs (das angreifbare Modul-Namespace-Objekt) aus seinem eigenen Verzeichnis. Behalte die beiden Dateien also zusammen (oder passe den import-Pfad an).
git und ~10 GB freiem Speicherplatzdepot_tools (git clone https://chromium.googlesource.com/chromium/tools/depot_tools)# 1. fetch V8 at the vulnerable tag (12.4.254.16 is the pre-fix release)
export PATH="$HOME/depot_tools:$PATH"
cd ~/v8w && fetch v8 && cd v8
git checkout 12.4.254.16 # or the commit just before b3c01ac1e60a
# 2. first build a normal release d8 (needed to seed args.gn), then:
./v8-build-nosandbox.sh # copies args.gn and appends v8_enable_sandbox = false
v8-build-nosandbox.sh verwendet ninja -C out.gn/x64.release_nosandbox -j6 d8.
out.gn/x64.release_nosandbox/d8 --allow-natives-syntax --module exploit/exploit_rce.mjs
Die erwartete Ausgabe endet mit:
CVE-2024-4947-PWNED
und der Prozess beendet sich mit Status 0.
--allow-natives-syntaxwird für die Runtime-Aufrufe%PrepareFunctionForOptimization/%OptimizeMaglevOnNextCallbenötigt. Deshalb kann die PoC nicht in einem echten Browser ausgeführt werden — sie ist bewusst ein Proof-of-Concept für die d8-Shell.
Die wirklich wiederverwendbaren (und nicht offensichtlichen) Bausteine für die V8-Exploitation-Forschung:
JSTypedArray.external_pointer in einer Zielregion fehlschlägt, leite stattdessen WasmTrustedInstanceData.memory0_start um. Kompilierte Wasm-Byte-Loads/-Stores haben keinen Software-Bounds-Check; der Signal-Trap-Handler wandelt nur Guard-Page-Fehler um, sodass gemappte, aber nicht beschreibbare Regionen abstürzen statt einen Trap auszulösen.try/catch; ein Wasm-„Out of Bounds“-Trap bedeutet eine Guard-Page (nicht gemappt), ein echtes SIGSEGV bedeutet gemappt, aber RX.e9 rel32-Slot-Format — 5-Byte-Relativsprung, nicht die movabs-Form, die viele Write-ups annehmen. Verifiziert gegen das Disassembly von --print-wasm-code.Dieses Repository wird ausschließlich für Bildungs- und Defensiv-Sicherheitsforschung bereitgestellt. Es demonstriert die Ausnutzung einer gepatchten Schwachstelle gegen einen nur für die Entwicklung gedachten V8-Build. Es ist keine Waffe gegen modernes Chrome, umgeht die V8-Sandbox nicht und erfordert das nicht-standardmäßige Flag --allow-natives-syntax. Der Autor ist für jeglichen Missbrauch nicht verantwortlich.
Die Trigger-Primitive (gefälschte NAME_DICTIONARY_TYPE-Map + WeakRef-Hash-Write) folgt den öffentlichen Analysen des In-the-Wild-Lazarus-Exploits, die nach dem Patch veröffentlicht wurden — einschließlich der CVE-2024-4947-Write-ups von Google und Exodus Intelligence. Die späteren Stufen (Jump-Table-Probing, Brücke, Slot-Patching) wurden im Rahmen dieser Arbeit empirisch gegen diesen spezifischen Build ermittelt. Das Konzept des JIT-Spray-Fallbacks stützt sich auf öffentliche V8-Exploit-Techniken (z. B. das make_array-Muster von CVE-2024-5830).
MIT — siehe LICENSE.