Decrypting iOS binaries

iOS applications downloaded from the App Store are encrypted. In order to conduct decent offline analysis we need to decrypt the binaries. Here we’ll take a typical binary (in this case from the Good for Enterprise client application) and decrypt the content in four easy steps so we can analyse it later using tools such as class-dump.

Stage 1: Extract the files for offline analysis

Before we begin, identify the application on the device and scp the files off. The application will be in a location as shown below:


Where UUID is a universally unique identifier for the application. Good for Enterprise for example on my device lives here:


Stage 2: Locate the binary of interest

Mach-O supports fat (i.e. multiple) binaries for different architectures. The file itself will be in the application’s folder (e.g. Good.App) and the structure is identical to that of an OSX package. We can identify the binary with otool, included as part of XCode. steve$ otool -arch all -Vh Good
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags

We can see above that there’s one Arm v6 binary.

Stage 3: Identify encrypted segments

As App Store-sourced iOS binaries are encrypted, we need to decrypt and fix up the application. In order for the program to execute on the CPU it needs to be decrypted in memory, so we can dump the process from memory with a debugger and patch the binary so we can inspect it in a disassembler, or with other binary analysis tools.

We can use otool to print out the load commands and look for the LC_ENCRYPTION_INFO entry. Load commands specify the structure and layout of the file in virtual memory and form part of the Mach-O binary. The Mach-O file format is really interesting (possibly as interesting as the PE binary format) and if time allows will be covered in a blog post at a later date. LC_ENCRYPTION_INFO is used by the Mach-O loader to determine which parts of the file on the device are encrypted with Apple’s FairPlay DRM. steve$ otool -arch all -Vl Good | grep -A5 LC_ENCRYP
cmdsize 20
cryptoff 8192
cryptsize 10715136
cryptid 1
Load command 12

The cryptoff field tells us that the encrypted data starts 8192 bytes into the file. The cryptsize field tells us that 10715136 bytes (starting at 8192) are encrypted. The cryptid value of 1 signifies that the binary is encrypted. steve$ otool -arch all -Vl Good
Load command 0
cmdsize 56
segname __PAGEZERO
vmaddr 0x00000000
vmsize 0x00001000
fileoff 0
filesize 0
maxprot ---
initprot ---
nsects 0
flags (none)

The output is quite long. We’re interested in the first load command for now. The LC_SEGMENT for the _TEXT segment file offset 0 is mapped to the virtual memory address 0x00001000. So this means that the start of the file is mapped to 0x1000. Our decrypted data starts at 8192, or 0x2000 so we add the memory address to the data offset and get 12288 or 0x3000 and the decrypted data is going to start at 0x1000 + 0x2000 = 0x3000. Lets dump the memory.

root# gdb -quiet ./
Display all 491 possibilities? (y or n)
Poopsmith:/private/var/mobile/Applications/5478DEC9-A056-45B2-A67D-491542EE5337 root# gdb -quiet ./
Reading symbols for shared libraries ... done
(gdb) b UIApplicationMain
Function "UIApplicationMain" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (UIApplicationMain) pending.
(gdb) r
Starting program: /private/var/mobile/Applications/5478DEC9-A056-45B2-A67D-491542EE5337/
Removing symbols for unused shared libraries . done
Reading symbols for shared libraries .+..................+............................................................................................................... done
Breakpoint 1 at 0x3766b30a
Pending breakpoint 1 - "UIApplicationMain" resolved

Breakpoint 1, 0x3766b30a in UIApplicationMain ()
(gdb) dump binary memory mem_dump_good.bin 0x3000 (0x3000 + 10715136)
(gdb) q
The program is running. Exit anyway? (y or n) y

Looking at the above, I set a breakpoint at UIApplicationMain so that we know the program has been fully decrypted (as if it was still encrypted we wouldn’t be able to jump to it). Once the breakpoint was triggered I dumped the memory from 0x3000 to 0xA3B258 (12888 + 10715136 == 10728024) based on the cryptoff and cryptsize fields in the LC_ENCRYPTION_INFO entry.

Stage 4: Patching the binary

Make sure you have a copy of the encrypted binary. To do this we need to copy the decrypted data into the binary and disable the encryption load command. Copying the data can be done with dd, as shown below.

cp Good patched-good
dd bs=1 seek=8192 conv=notrunc if= mem_dump_good.bin of=patched-good
canderous:good-file steve$ ls -l
total 70432
-rwxr-xr-x 1 steve staff 12670032 4 Feb 14:16 Good
-rw-r--r-- 1 steve staff 10715136 4 Feb 14:13 mem_dump_good.bin
-rwxr-xr-x 1 steve staff 12670032 4 Feb 14:18 patched-good
canderous:good-file steve$ dd bs=1 seek=8192 conv=notrunc if=mem_dump_good.bin of=patched-good
10715136+0 records in
10715136+0 records out
10715136 bytes transferred in 13.635779 secs (785810 bytes/sec)
canderous:good-file steve$ ls -l
total 70432
-rwxr-xr-x 1 steve staff 12670032 4 Feb 14:16 Good
-rw-r--r-- 1 steve staff 10715136 4 Feb 14:13 mem_dump_good.bin
-rwxr-xr-x 1 steve staff 12670032 4 Feb 14:20 patched-good

To disable the encryption load command we set the cryptid field to 0x0. To find the offset, use MachOView.

Once you have the absolute offset, you can use a hex editor to change the value to 0, after which tools like class-dump and hopper should now work just fine.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s