Analysis of the jailbreakme v3 font exploit

Two weeks ago, comex released the third version of jailbreakme.
Two exploits are used to jailbreak Apple devices by opening a PDF file
in the MobileSafari browser: initial code execution is obtained through
a vulnerability in the Freetype Type 1 font parser, allowing subsequent
exploitation of a kernel vulnerability to disable code signing
enforcement, get root privileges and "install" the jailbreak. The
same kernel vulnerability is also exploited at each reboot to provide an
untethered jailbreak, using the Incomplete Codesign technique to
bootstrap the kernel exploit. The two vulnerabilities (and another
Freetype vulnerability not used by jailbreakme) were patched with the
release of iOS 4.3.4.

The vulnerability (CVE-2011-0226) is located in the interpreter for
Type 1 font programs. Vector font formats like Adobe Type 1 use small
interpreted programs to render characters outlines at different sizes.
In Freetype the t1_decoder_parse_charstrings function is
responsible for executing such programs (called charstrings).

To start our analysis we need to extract the font from the jailbreakme
PDF file, we can do this using Origami :

In order to disassemble the charstrings contained in the font, I could
not get t1disas to work so I wrote a minimal Python script to
disassemble Type 1 opcodes. The Type 1 specification is available on
Adobe's website.It is not necessary to understand everything about fonts
and font programs, we just need to figure out the primitives used in the
exploit and their effects on the interpreter state. The main structure
used by the interpreter is T1_DecoderRec, defined in
include/freetype/internal/psaux.h :

The main memory locations available to a legitimate font program and
used by the exploit are :

the operand/result stack (decoder->stack). This stack grows
"up".

two variables : x and y (local variables in
t1_decoder_parse_charstrings)

the decoder->buildchar array

The buildchar array is actually defined and initialized (all zeroes) by
the /BuildCharArray command in the font. Its size is set to
0x30000.

The vulnerable code and the main idea behind comex's exploit were
mentioned by windknown on twitter : a missing check on the
arg_cnt parameter for the callothersubr operation allows a
malicious font program to move the interpreter stack pointer outside of
its bounds, providing read and write access to various fields of the
T1_DecoderRec structure (and the "real" stack since this
structure is a local variable of the T1_Load_Glyph function).

The jailbreakme font program uses this vulnerability to read the
decoder->parse_callback field (and a few others), construct a ROP
payload in the decoder->buildchar array, and execute this payload by
overwriting decoder->parse_callback and triggering a call to this
function pointer. The following primitives are used to program the
interpreter (the "weird machine") :

op_setcurrentpoint : read 2 dwords from the stack into variables
x and y. This operation does not check the stack pointer.

callothersubr #00 : write the x and y variables on the
stack. This operation does not check the stack pointer but has
preconditions :
decoder->flex_state != 0 && decoder->num_flex_vectors == 7. The
argument count for this routine is 3 but only the first 2 parameters
are used (overwritten with x and y).

callothersubr #42 : calls an invalid subroutine with a negative
argument count to bring the stack pointer beyond the stack area. This
is the vulnerability that makes the exploit work.

op_hstem3, op_hmoveto, op_unknown15 instructions to
"bring down" the stack pointer back into its bounds

callothersubr #12 : reset the stack pointer
(top = decoder->stack)

callothersubr #20 and #21 : add/subtract values on the stack

callothersubr #23 and #24 : read/write dwords in the
buildchar array.

The PDF document contains a single page with the @ (at) character, using
the malicious font. The /at font program will be run to render this
character, this is the entry point of the exploit. 10 subroutines are
also defined in the font and used by the /at font program :

subroutine 0 : "fake" subroutine, contains a zlib compressed
mach-o binary that is dropped in /tmp/locutus and run by the ROP
payload once the kernel exploit is done

subroutine 1 : empty (op_return)

subroutine 2 : exit interpreter (op_endchar)

subroutine 3 : calls callothersubr #01 to set
decoder->flex_state=1 and executes callothersubr #02 seven
times to set decoder->num_flex_vectors=7 : this routine sets the
preconditions for the "write variables to stack unchecked"
primitive (callothersubr #00)

Subroutines 4 to 7 are the primitives for the ROP payload construction :

Subroutines 8 & 9 are responsible for writing a ROP payload to the
buildchar array : they contain some initialization code (described
shortly hereafter), followed by a sequence of calls to subroutines 4,5,6
and 7.

The program starts by initializing the variables x and y to the
values 0x3 and 0x0. The instruction
callothersubr #42 nargs=-347 exploits the bug to move the stack
pointer at the end of the T1DecoderRec structure, right after the
seac field, which is set to 0 when the structure is initialized. The
length of the buildchar array was set to 0x30000, which is chosen
specifically to make the len_buildchar and seac fields look like
the "stack frame" for the callothersubr #00 primitive
(0x30000 is 0x3 encoded in the 16.16 fixed point format used by
the interpreter). Hence, the following callothersubr instruction (at
offset 0xb) will write the x and y values over the
funcs.done and funcs.parse_charstrings fields. This overwrite is
a preparatory step for the end of the font program, where the
parse_callback field is overwritten using the same primitive.

The stack pointer is then decremented by 3 op_hmoveto instructions
to point to the funcs field (right after the parse_callback
field). The following op_setcurrentpoint operation will read the
hint_mode and parse_callback fields into the x and y
variables. The stack pointer is then decremented back in the stack area
to allow the next instructions to run without errors. In the process,
the value of the zone field (which points to zones[0]) is also
read into the x variable by the op_hmoveto instruction at offset
0x2e (it is added to the hint_mode value which is 0). The x
and y variables are then pushed onto the stack (using the subroutine
#03 to enable callothersubr #00), and stored in buildchar[0]
and buildchar[1].The next sequence, starting at offset 0x46 with
callothersubr #42 nargs=-151 follows the same pattern : it reads the
decoder->buildchar pointer and stores it in buildchar[2].

Next, the value 0x7918 is written to buildchar[3] : this will be
used as the index for the ROP payload building routine. This leaves
0x7918 * 4 bytes for the stack frames of functions called by the ROP
payload.

Another value is also leaked using callothersubr #42 nargs=-152 and
stored in buildchar[4], this is the value of the
__gxx_personality_sj0 symbol stored on the stack frame of the
calling function FT::font::load_glyph.Because the User-Agent field
only identifies iPhone/iPad/iPod and firmware version, but not the
specific model (i.e iPhone 3GS or iPhone 4), the font program contains
multiple ROP building subroutines. The correct function is chosen by
comparing the difference between __gxx_personality_sj0 and
T1_Parse_Glyph. Since the two symbols are located in different
shared libraries (libstdc++.6.dylib and libCGFreetype.A.dylib)
and because the order of those libraries in the shared cache is
different for the same firmware version on different devices (see Stefan
Esser's talk at POC 2010), this delta identifies the device (thanks
to comex for explaining this part).

The payload building subroutine number for the identified device is then
pushed on the stack using conditional instructions
(callothersubr #27). Depending on the device, subroutine 8 or 9 will
be called.

The ROP building subroutine starts by subtracting the default
T1_Parse_Glyph address from the one leaked in buildchar[1]. Now
buildchar[1] contains the shared cache slide (see Stefan Esser's
talk at HITB AMS 2011), which will be used by subroutine 5 to adjust
the gadgets addresses written to the ROP stack and successfully bypass
ASLR.Then, the address of a gadget (scale_QT+254) is computed and
placed in the y variable using op_setcurrentpoint. After that,
the ROP payload is constructed dword by dword, using subroutines 4,5,6
and 7. Some values that cannot be encoded directly in push operations
are computed using the subtract operation.

Once the ROP payload building is done, the \\at routine recopies the
first 7 dword values from the constructed ROP stack onto the decoder
stack. These 7 values will be used to perform the stack pivot to the
"main" ROP stack stored in the buildchar array. The last step is
to overwrite decoder->parse_callback with the y variable
contents. It is now that the funcs.done and
funcs.parse_charstrings values make sense : the
callothersubr #42 nargs=-337 (at offset 0x167) brings the stack
pointer at the end of the decoder->funcs structure, whose fields
were modified to be 0x30000 and 0x0, so the next
callothersubr instruction calls the write primitive
(callothersubr #00), that will overwrite the parse_callback
field with the gadget address stored in the y variable.

Finally, the font program ends with the op_seac instruction, that
triggers the following calls:

This ROP payload exploits a kernel vulnerability in the
IOMobileFramebuffer IOKit interface (CVE-2011-0227), using a kernel
ROP payload that recopies a shellcode at address 0x80000400
(executable slack space at the beginning of the kernelcache mapping).
The kernel shellcode patches various kernel functions to allow unsigned
applications to run. It also installs a handler for syscall 0 whose sole
purpose is to give root privileges to the calling process. This is used
at the start of the main funtion in the locutus binary. The
syscall handler is one-shot, it removes itself from the sysent array
as soon as it is called (and it is not present in the untether binary
since this is only required for the jailbreak installation process).

Once the kernel exploit is done, the mach-o binary contained in
subroutine 0 is decompressed into /tmp/locutus and run using the
posix_spawn function. Finally, the stack pointer is restored and
execution resumes at the t1_decoder_parse_charstrings epilog. The
R0 register is also set to 0x00000539 so that the font parser
exits with a 1337 error code :) The locutus binary then installs
Cydia and the untethered jailbreak package. A shared library is injected
into the SpringBoard process (using mach calls and the
thread_create_running function) to display the Cydia icon with a
progress bar, just like a regular application installation.

This new version of jailbreakme is really impressive, and features the
first public exploit that actively bypasses the ASLR mechanism
introduced with iOS 4.3. A homebrew patch is even provided (PDF
Patcher2) to avoid any controversy about "irresponsible disclosure".
Hats off to comex !