One major change in iOS 10 is the lack of encryption for 64bit iOS kernelcaches. Prior to iOS 10 the standard technique to investigate the iOS kernel was to dump the running kernel out of memory (or decrypt it if an bootloader vulnerability was available but that’s for another post). Unforunately these dumped kernels were post KASLR slide (which causes issues in IDA) but more importantly they had their symbols jettisoned as part of the kernel boot process. This lack of symbols definitely slows down reverse engineering and tended to make things a bit more of a PITA.
Now that iOS 10 is shipping with decrypted kernelcaches, this blog will discuss the method I was using to dump fully symboled kernel caches long before iOS 10.
64bit Kernel loading (Pre-iOS 10):
- iBoot reads the encrypted kernelcache from disk and verifies the signatures/manifest
- iBoot decrypts and decompresses the kernelcache into physical memory.
- iBoot parses the kernelcache, reads some random data to determine slide offset, and sets up a basic memory map.
- iBoot copies the kernel segments to the randomly slide base, does some light segment fixups, then jumps to/begins executing the kernel at the slid kernel base.
- Once the kernel is executing (as long as the “keepsyms” boot-arg is not set) it will automatically jettison its symbol tables.
Pretty straightforward, yeah? If you spend most of your time auditing iOS from a user->kernel (not iBoot) perspective then typically you probably would just dump the post slide/symbol-less kernelcache using a arbitrary read primative.
Now here is where the fun part begins. Try to imagine the kernel loading processes from an iBoot perspective. Can you spot the “vulnerability”?
64bit Kernel Dumping (Pre-iOS 10):
Did you spot it? The issue is somewhat subtle…
After step #2 listed above the kernelcache that iBoot initially decrypted is not unmapped/overwritten from physical memory.
So all that is required to recover the symboled kernelcache is to dump it from its original decrypted location in physical memory -
0x804000000 (iOS 8) or
0x810000000 (iOS 9).
There are a few different memory reading strategies which can be used to read it - and all the ones I have used seem to work fine. The only thing I have noticed is it seems more stable on A8 and above devices. I suspect it may have to do with the ammount of physical memory - but I havent spent too much time on it. So if you run into issues dumping on your 5s - try on a 6 or 6s.
Here for example is the kernel from an iPhone 6s running iOS 9.3.4 -
As can be seen - its far from a complex issue. But one that has been extremely helpful in past research.
Always remember kids… memory corruption vulns are fun but logic flaws are lethal!