
Обучающий proof-of-concept для CVE-2024-4947, путаницы типов (type confusion) в V8 Maglev, демонстрирующий полную цепочку от триггера до произвольного выполнения кода на сборке d8 без песочницы.
Полная автономная цепочка эксплуатации для CVE-2024-4947 (type confusion в V8 Maglev) — от исходного триггера type confusion до выполнения произвольного кода. PoC запускается против сборки d8 без песочницы и выводит CVE-2024-4947-PWNED в stdout как доказательство выполнения кода.
⚠️ Это исследовательский / образовательный PoC для исправленной, публично известной уязвимости. Он нацелен на dev-сборку оболочки V8 (
d8 --allow-natives-syntax) — не на настоящий браузер Chrome. См. 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
52-байтовый шеллкод — это write(1, "CVE-2024-4947-PWNED\n", 20); exit_group(0).
CVE-2024-4947 — это type confusion в JIT-компиляторе V8 Maglev, исправленная в Chrome
125.0.6422.60 (коммит b3c01ac1e60a). Она эксплуатировалась в реальных атаках APT-группировкой Lazarus.
Когда Maglev компилирует запись в объект namespace модуля (JSModuleNamespace), он использует
некорректный AccessInfo — запись компилируется как обычная mov [[obj + 4], rax], которая
записывает управляемое значение в поле map соседнего объекта вместо корректного пути
свойства-записи.
Эксплуатация выполняется следующим образом:
NAME_DICTIONARY_TYPE (0xB2), из-за чего меняется место, где V8 хранит хеш объекта.new WeakRef(...), чтобы запись хеша попала в слот длины соседнего объекта → доступ out-of-bounds.Дальше получаем классический набор V8: addrOf/fakeObj, а затем произвольное чтение/запись в пределах 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
| # | Этап | Детали |
|---|---|---|
| 1 | Триггер | opt() записывает поддельную map через испорченную запись; new WeakRef(m) выводит corruptArray.length за границы. |
| 2 | Движок | addrOf/fakeObj; повреждение длины doubleArray → произвольное 4-байтовое чтение/запись по любому 4-байт-выровненному адресу в cage. |
| 3 | 64-битный мост | WasmTrustedInstanceData.memory0_start (+0x18) — это «сырой» 64-битный указатель, используемый скомпилированным wasm-кодом i32.load8_u/i32.store8 без программной проверки границ (выход за пределы попадает на guard-страницу → SIGSEGV → wasm-ловушка). Перенаправьте его на любой адрес и задайте огромный memory0_size (+0x20) → произвольное 64-битное чтение/запись. |
| 4 | Поиск code space | jump_table_start (+0x38) — это «сырой» 64-битный указатель во внешний code space (за пределами 4-ГБ cage). |
| 5 | Запись шеллкода | В этой сборке область jump-table RWX (V8 патчит записи в рантайме): запишите 52-байтовый шеллкод в слот свободного места по jt+0x100. |
| 6 | Перенаправление слота | Слот jump-table для func0 — это 5-байтовый e9 <rel32> (цель = slot + 5 + rel32). Установите rel32 → jt+0x100. |
| 7 | Запуск диспетчеризации | inst.exports.r(0) → JSToWasmWrapper диспетчеризует через слот 0 → выполняется шеллкод. |
Большинство публичных PoC для CVE-2024-4947 нацелены на песочный (sandboxed) d8 или настоящий renderer Chrome
(v8_enable_sandbox=true) и останавливаются на примитиве type confusion / OOB. Этот PoC
нацелен на d8 без песочницы и доводит цепочку до выполнения кода. Значимые отличия:
| Параметр | Типичный подход публичных PoC | Этот PoC |
|---|---|---|
| Целевая сборка | песочный d8 / renderer Chrome | v8_enable_sandbox=false d8 — доверенные указатели прямые, внешний code space включён |
| Мост 64-битного R/W | перезапись JSTypedArray.external_pointer | Этот путь падает при чтении code range (проверено эмпирически); вместо этого перезаписывается memory0_start с использованием сырых wasm load/store |
| Финальный путь к коду | code space — W^X → JIT-spray + косвенное перенаправление | область jump-table — RWX → прямая запись шеллкода + патч слота e9 rel32 |
| Формат слота jump-table | в документации обычно предполагается movabs rax, imm64; jmp rax (12 байт) | измерено: 5-байтовый e9 <rel32>, цель = slot + 5 + rel32 |
| Смещения полей | раскладка песочной сборки | no-sandbox WasmTrustedInstanceData: jump_table_start@+0x38, memory0_start@+0x18, memory0_size@+0x20; WasmInstanceObject.trusted_data@+0x0c |
| Чистый выход | — | d8 многопоточный: нужно использовать exit_group (231), а не exit (60), иначе процесс зависнет |
Полное техническое описание, включая эмпирические результаты, стоящие за каждой строкой, см. в docs/walkthrough.md.
.
├── 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
Эксплойт импортирует Module.mjs (уязвимый объект namespace модуля) из своей собственной директории, поэтому держите оба файла вместе (или поправьте путь в import).
git и ~10 ГБ свободного места на дискеdepot_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 использует ninja -C out.gn/x64.release_nosandbox -j6 d8.
out.gn/x64.release_nosandbox/d8 --allow-natives-syntax --module exploit/exploit_rce.mjs
Ожидаемый вывод заканчивается строкой:
CVE-2024-4947-PWNED
и процесс завершается с кодом 0.
Флаг
--allow-natives-syntaxобязателен для runtime-вызовов%PrepareFunctionForOptimization/%OptimizeMaglevOnNextCall. Именно поэтому PoC не может работать в настоящем браузере — это proof-of-concept для оболочки d8 по построению.
Действительно переиспользуемые (и неочевидные) элементы для исследований эксплуатации V8:
JSTypedArray.external_pointer
не срабатывает для целевой области, перенаправляйте вместо него WasmTrustedInstanceData.memory0_start.
Скомпилированные байтовые загрузки/сохранения wasm не несут программной проверки границ; обработчик сигнальной
ловушки преобразует только сбои на guard-странице, поэтому отображённые, но недоступные на запись области
вызывают SIGSEGV, а не ловушку.try/catch; ловушка wasm «out of bounds» означает guard-страницу (не отображена),
настоящий SIGSEGV — область отображена, но только RX.e9 rel32 — 5-байтовый относительный переход, а не форма movabs, предполагаемая
во многих разборах. Проверено по дизассемблеру --print-wasm-code.Этот репозиторий предоставлен только для образовательных и защитных исследований. Он
демонстрирует эксплуатацию исправленной уязвимости против исключительно разработочной
сборки V8. Это не оружие против современного Chrome, он не обходит песочницу V8
и требует нестандартного флага --allow-natives-syntax. Автор не несёт ответственности за любое неправомерное использование.
Триггерный примитив (поддельная map NAME_DICTIONARY_TYPE + запись хеша через WeakRef) следует публичным
разборам эксплойта Lazarus, использовавшегося в реальных атаках и опубликованным после исправления, —
включая разборы CVE-2024-4947 от Google и Exodus Intelligence. Более поздние стадии (проверка jump-table,
мост, патчинг слотов) были выведены эмпирически для данной конкретной сборки в ходе этой работы.
Концепция JIT-spray в качестве запасного варианта опирается на публичные техники эксплуатации V8
(например, паттерн make_array из CVE-2024-5830).
MIT — см. LICENSE.