DisARMing the iOS kernel
May 30, 2014
Disclaimer: The information this post pertains to iOS 6.x for the most part, some of it still applies to iOS 7.0, but not really for 7.1.1 and beyond. You are free to use the concepts presented for your own nefarious/’research’ purposes.
CVE-2014-1320
This bug relates to IOPlatformArgs
, a member that’s been published in the IOKit registry for over 13 years. It’s been present since the original OS X releases and was very useful for leaking the kernel base address.
In the case of iPhone OS, two booter arguments are leaked, boot-args and the head of the device tree. This is useful as the kernel bootstrap and system page tables are located at a fixed offset from the beginning of boot-args. (This offset is about 8 pages after the end of boot-args, at least in my tests). As this location is easily modifiable by a kernel write-what-where style vulnerability, one can simply write their own L1 page directory mappings into the translation table. By writing the entries, one can guarantee that the area will be translated successfully (provided the address is out of the reach of the user map) and that the system data/prefetch abort handlers will not be called as the translated entries do not exist in the processor’s TLB beforehand.
Remapping the kernel into userspace also means that user programs can directly modify kernel memory, allowing for easy patching of the code and also for shellcode insertion. This also removes the need for read, write and execute primitives (at least, pre-iOS 7.1.1). Please do note that as of the current release, the kernel page table base remains static.
I personally remap the kernel using 1MB section maps with attributes: Shareable, Non-cacheable (strongly ordered), User + Supervisor Read/Write.
The booter memory layout looks very much like this:
[Zero page (low resume exception vectors)]
[Kernel + prelinked kernel extensions]
[Device tree]
[RAM disk (if present)]
[Boot-args]
[Kernel bootstrap L1 table]
[Kernel bootstrap L2 tables (for extra memory, post iOS 4)]
[Kernel system L1 table]
[.....]
Easy Patching
Once the kernel is remapped into user memory, one can simply modify sysent[0]
to point to a user-supplied payload. However, common jailbreaks such as p0sixspwn
and evasi0n(7)
insist on modifying kernel page tables. This is not actually needed, as one can simply use the DACR
(domain access control register) instead. Utilizing the DACR
in manager mode for all domains allows one to bypass page permissions, including NX, and also results in far less kernel reads/writes (for page table walking) and TLB flushes.
Please note that interrupts (both FIQ and IRQ) must be disabled in order to suspend context-switching, as running iOS with DACR
set to manager mode ends up making it do incredibly Weird Things(tm).
If one is also using the backdoor mapped kernel technique in order to read kernel memory, please take care in order to zero out the mappings, as the kernel map is globally visible across all applications.
Additionally, the kernel ASLR virtual base can also be found by simply reading through boot-args in remapped memory (the address of the boot-args in physical memory space is stored in the zero page, to convert from physical to virtual, simply subtract the PA base and add the remap base.)
Modifying the kernel from userspace by directly copying patches in is viable, however, you may experience issues related to writeback caching. Patching the kernel directly from supervisor to the kernel virtual addresses is preferred for that reason.
Example shellcode
#include <mach/arm/asm.h>
.data
.code 16
.thumb_func
.align 2
.globl EXT(shellcode_begin)
.globl EXT(shellcode_end)
LEXT(shellcode_begin)
stmfd sp!, {r4-r7, lr}
// Disable interrupts, we don't want any timer/HW events during this critical section.
cpsid if
// Get DACR's original value
mrc p15, 0, r4, c3, c0, 0
// Switch over to manager mode
mov r5, #-1
mcr p15, 0, r5, c3, c0, 0
...
// Zero out the mappings
ldr r0, EXT(u_ttbBase)
ldr r1, EXT(u_ttbSize)
mov r2, #0
_ClearMap:
str r2, [r0], #4
subs r1, r1, #4
bgt _ClearMap
...
// Clear unified TLB and restore DACR and interrupts
mov r0, #0
mcr p15, 0, r0, c8, c7, 0
mcr p15, 0, r4, c3, c0, 0
isb sy
cpsie if
movs r0, #0
ldmfd sp!, {r4-r7, pc}
.align 2
...
.globl EXT(u_ttbSize)
.globl EXT(u_ttbBase)
LEXT(u_ttbBase)
.long 0
LEXT(u_ttbSize)
.long 0
...
LEXT(shellcode_end)
nop
Bruteforce
On another note, the VA to PA registers in coprocessor c7
can also be used to bruteforce the location of the kernel translation table base or ASLR base. Simply making a tight loop that involves going through the entire VA space and comparing the translated result physical address with actual the physical address will give you what you need.
Afterlife
That’s pretty much it, by utilizing the power of CVE-2014-1320, the ARM architecture and also a simple kernel write-what-where style vulnerability, one can own the iOS kernel and make a simple kernel exploitation binary for the sake of untethering or whatever.
These techniques should work on any iOS version, as the kernel L1 page directory base is always located at that same static offset from the beginning of physical RAM.
(It is easy to even calculate without knowing the contents of IOPlatformArgs too…)
The ARM architecture provides incredibly powerful, powerful components that make exploitation a lot easier. (If you know how they all work!)
:)
Share