TippingPoint Digital Vaccine Laboratories
DID YOU KNOW... In December of 2007, Microsoft released seven security bulletins which fixed 11 new security vulnerabilities. TippingPoint and ZDI were credited with discovering a total of four of those vulnerabilities.

Reverse Engineering iPhone AppStore Binaries

I recently had the need to peek under the hood of an iPhone application I purchased through the AppStore and quickly came to discover that getting started takes a bit more effort then simply dragging and dropping into IDA. I'm certainly not the first person to have done this, but when faced with a new challenge I like to figure it out the hard way at first, to better understand the fine details. This blog entry details how to get an application into a reversable state.

iPhone apps purchased through the AppStore live in your iTunes library under the folder "Mobile Applications". Each app is stored in a zip archive with a .IPA extension. You can simply rename the file to .ZIP and decompress to view the contents. I'll use the game Fieldrunners as the example in this blog, which is in my opinion, the best iPhone game available. Decompressing and loading Payload\Fieldrunners.app\Fieldruners into IDA 5.4 will properly parse the Mach-O binary, list some symbols and provide you with very little and very odd looking disassembled code. Examining the string table reveals next to nothing. This is because the binary is encrypted, the app is in an unacceptable state for reverse engineering. The iPhone loader is responsible for decryption at run-time so I figured my best bet would be to jailbreak my phone and get on the actual device. Jailbreaking is an impressively easy operation these days, requiring only a few minutes with QuickPWN and installing some basic necessities like OpenSSH and GDB. Once on the device, you have to find your target applications directory and make a working copy of it:
# cd /private/var/mobile/Applications/
# find ./ -iname \*.app | grep Field
The executable is a 32-bit Mach-O file which consists of 3 main regions. A header, followed by load commands, followed by segments/sections. Here is an illustration (not my own, found it on Google):

One of the load commands is responsible for actually decrypting the binary. Let's take a look through the load commands via the 'otool' utility. The output shown here is from the load command specific to crypto:
# otool -l Fieldrunners
 Load command 9
           cmd LC_ENCRYPTION_INFO
       cmdsize 20
  cryptoff  4096
  cryptsize 258048
  cryptid   1
The cryptsize field is important for us as we'll see in a minute. If we wanted to strip the crypto entirely we would have to null out the cryptid, but as we are just interested in getting to the code we will leave it alone. The next step is to load the binary under GDB, set a breakpoint at the start address of 0x2000, let the loader decrypt and then dump the decrypted code. This tactic is very similar to how you would handle most executable packers such as UPX, ASPack, etc...
# gdb ./Fieldrunners 
GNU gdb (Tue Nov 11 11:06:48 UTC 2008)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "--host=arm-apple-darwin9 --target="...
warning: --arch option not supported in this gdb.
Reading symbols for shared libraries ................ done
gdb$ bp *0x2000
Breakpoint 1 at 0x2000
gdb$ r
Removing symbols for unused shared libraries . done
Reading symbols for shared libraries .++...................... done
Breakpoint 1, 0x00002000 in ?? ()
Ok we're at the start address, let's take a look at the first batch of instructions and see if we're dealing with legitimate code:
# before decryption:
gdb$ x/20i 0x2000
0x2000: stfcse  f7, [pc], {151}
0x2004: cdp     4, 5, cr9, cr5, cr1, {4}
0x2008: strcsb  r2, [r8, #-2813]!
0x200c: ldrvsbt r2, [r6], r0, lsr #1
0x2010: strmib  sp, [r0, #-116]!
0x2014: ldmlsdb ip!, {r1, r3, r5, r6, r7, r8, r9, sl, pc}^
0x2018: cdppl   4, 4, cr10, cr15, cr5, {4}
0x201c: cmnhi   r0, pc, lsl ip
0x2020: eorvc   r2, r9, #50331648       ; 0x3000000
0x2024: undefined instruction 0xf8faa207
0x2028: strgt   r5, [fp], -r5, ror #1
0x202c: bleq    0x1becd8c
0x2030: ldrnebt lr, [r0], #2163
0x2034: stclsl  9, cr11, [r5, #-4]
0x2038: swine   0x006f99a2
0x203c: strplb  r0, [lr, #584]
0x2040: undefined instruction 0xf755c9a3
0x2044: andvs   ip, lr, #176    ; 0xb0
0x2048: smlalttge       ip, fp, sl, sp
0x204c: ldrhih  lr, [r9], pc

# after decryption:
gdb$ x/20i 0x2000
0x2000: ldr     r0, [sp]
0x2004: add     r1, sp, #4      ; 0x4
0x2008: add     r4, r0, #1      ; 0x1
0x200c: add     r2, r1, r4, lsl #2
0x2010: bic     sp, sp, #7      ; 0x7
0x2014: mov     r3, r2
0x2018: ldr     r4, [r3], #4
0x201c: cmp     r4, #0  ; 0x0
0x2020: bne     0x2018
0x2024: ldr     ip, [pc, #24]   ; 0x2044
0x2028: add     ip, pc, ip
0x202c: ldr     ip, [ip]
0x2030: blx     ip
0x2034: ldr     ip, [pc, #12]   ; 0x2048
0x2038: add     ip, pc, ip
0x203c: ldr     ip, [ip]
0x2040: bx      ip
0x2044: ldreqd  lr, [r3], -r0
0x2048: andeq   lr, r3, r4, asr #31
0x204c: str     ip, [sp, #-4]!
You can clearly see the difference, now that we have the decrypted code we need to dump it out of memory from 0x2000 through 0x2000 + 'cryptsize', or 0x41000:
gdb$ dump binary memory /var/root/Fieldrunners.app/dump 0x2000 0x41000
# ls -l dump 
-rw-r--r-- 1 root wheel 258048 Mar  5 12:16 dump
Having verified that the dump size matches the previous cryptsize we can now write the decrypted code back into the Mach-O binary using dd:
# dd seek=4096 bs=1 conv=notrunc if=dump of=Fieldrunners 
258048+0 records in
258048+0 records out
258048 bytes (258 kB) copied, 18.5638 s, 13.9 kB/s
Now we have a suitable file for reverse engineering. Objective-C code is a completely different animal than what I'm used to. You're pretty much dead in the water with cross-references as almost every call you see is siphoned through msgSend(). For some further information on reversing Mac binaries in general, take a look at the slides from Cameron's talk at RECON.
Published On: 2009-03-06 13:09:14

Comments post a comment

  1. Anonymous commented on 2009-05-16 @ 15:42

    Could you first pull and modify the iPhone emulator from the iPhone SDK to emulate a jailbroken iPhone? Then continue with the rest of the process.

  2. Pedram commented on 2009-06-03 @ 18:17

    @Anonymous: I'm actually not sure as I have never looked at the SDK or emulator.

  3. romain commented on 2009-09-01 @ 14:57

    You cannot load external apps with the Simulator afaik. The simulator runs Intel code...

  4. Houli commented on 2010-04-13 @ 15:11

    Does this still work? I get to the point of running the app but I just get this Removing symbols for unused shared libraries . done
    Reading symbols for shared libraries ................................................ done
    Reading symbols for shared libraries .. done
    and then the cursor just keeps blinking.

  5. Pedram Amini commented on 2010-07-16 @ 10:19

    @Houli: I have not tried it since so it is very possible the start address has moved and the breakpoint needs to be relocated.

Links To This Post

  1. There is no hiding the source (and use SSL while you’re at it) « Pwn Home Research
    linked on 2009-05-31 @ 09:54 Show Comment

    There is no hiding the source (and use SSL while you’re at it) Posted on May 31, 2009 by pwnhome Security through obscurity is never a good thing and will always be broken. We can see this with the iPhone’s encrypted AppStore binaries. Once the phone is jailbroken, it is trivial to decrypt the apps; you simply run the program with gdb and set a breakpoint after the decryption software has run. Viola! you have the decrypted app. The virus writers do a much better job at this.