iOS 6 Kernel Security - A Hacker's Guide
Azimuth Security’s Mark Dowd and Tarjei Mandt presenting an overview of the defensive and offensive strategies for iOS 6 platform at Hack in the Box event.
Tarjei Mandt works as a senior vulnerability researcher at Azimuth Security, and he has presented at Black Hat, Hackers 2 Hackers, and Hackito Ergo Sum.
Mark Dowd: Okay, my name is Mark Dowd and this is Tarjei Mandt. We’re here to talk about iOS 6 kernel security. We decided to look at the iOS 6 kernel because we heard that there were a lot of mitigations coming in and we wanted to see what strategies they employed and how they compared to other operating systems and stuff that we’ve looked at before.
This presentation is basically the result of that research, and we are going to run through the mitigations that iOS 6 has introduced, and also we are going to specifically focus on other security improvements regarding mitigation of kernel exploits which are critical part of most jailbreaks. We noticed that a lot of the strategies that they employed were targeting things that have been used in jailbreaks before or spoken publicly about before. So we wanted to provide an overview of the new kernel-based mitigations there are. And also we’ll discuss a few techniques that we came up with for attacking the iOS 6 kernel. And at the very end we will show a demo of installing Cydia on iOS 6 device and running, essentially, a jailbreak.
So, I’m going to pass it over to Tarjei to start talking about the heap hardening.
Tarjei Mandt: Alright, we’re going to begin to talk a bit about randomization algorithm, because this is what all the mitigations use for generating stack cookies, heap cookies, the kernel map ASLR, and obfuscating pointers which previously could be used to learn locations in kernel memory for objects.
So, this algorithm gets a seed generated by the boot loader (iBoot), and it combines the seed with the current time to get the random value. As you can see in this slide, you have this function which takes these parameters and shuffles them around in order to produce a 32-bit random value.
And before we go into this, I just want to recap a bit on the old exploitation techniques, or recap it a little bit on the zone allocators. So, the zone allocator allocates memory from these zones which hold fixed-size chunks, so you have, like, ‘kalloc.8’ or ‘kalloc.16’ or ‘kalloc.32768’. And there’re also specialized zones for specific tasks, so you have things like the ‘pmap_zone’ or ‘vm_map_copy_zone’, etc.
And when you allocate memory, these zones allocate pages on demand, so they are not contiguous in that sense – you can have a ‘kalloc.8’ zone located next to a ‘kalloc.16’ zone, etc. So, like I said, you allocate blocks of pages on demand; these pages are divided into same-sized blocks, and when you allocate a page in order to use as zone, you divide this block and put all the blocks in the freelists. And this is a singly linked list, so the first pointer in each chunk holds the pointer to the next entry, etc. And when you allocate something you simply take the first element on the list and serve it back to the user or the component that requested the allocation. This is an example of a ‘kalloc.8’ zone where you have the freelist, and some chunks are used, and some are not.
The previous exploitation techniques in this space involved overwriting these pointers since they’re singly linked. There was really no checking, you could just overwrite the pointer and have it return any memory, really. And the way this was leveraged in an exploit was that it would typically have it point to the syscall table, then use that to overwrite a syscall entry, and subsequently by invoking that syscall it would be able to divert execution to an arbitrary location.
We also have block poisoning; this is a mitigation introduced to prevent Use-After-Free-style attacks. So, whenever something is freed the strategy here is to fill the blocks with sentinel values – in this case it’s ‘0xdeadbeef’ – in order to prevent you from reusing values, like in Use-After-Free, or tainting these values. In this case, it’s only performed on selected blocks, so if the block size is smaller than the cache line size of the processor, it will automatically fill the chunk with ‘deadbeef’. If it’s larger, it won’t use this mitigation unless you have specific parameters set.
So, the freelist pointers at the top of a free block are now validated against these cookies. This is the heap cookie version introduced in iOS 6. Actually, in order to verify they do not change the top pointer but they store an additional pointer at the end of the chunk that is the encoded version.
In this case, the non-poisoned cookie is used if you are not using ‘deadbeef’, if you are not poisoning the chunk. And the poisoned cookie is used if the chunk is small enough to be fit into the cache line size of the processor.
And to validate this they ensure they try both cookies, so if the poisoned cookie matches they also check if all these values are ‘deadbeef’; if any of those values are not ‘deadbeef’ it will cause a panic. It’s also worth noting that when you allocate something these cookies are actually zeroed out or replaced with ‘deadbeef’ in order to prevent you from actually leaking any of these values.
This was an example he came up with, where you had all these references, and that would produce an object that was never freed in memory.
In iOS 6, they address this by making sure that if you have duplicate dictionary keys they no longer free the original key, which was one of the exploitation scenarios he came up with, because if you would be able to free original keys you could create holes and position the memory in that state you wanted. And the dictionary entries can also no longer be pinned to memory using these multiple references. And in both of these cases, the ‘plist’ dictionary is considered invalid. So, this technique is mostly dead now.
And they actually do this by both having a generated stack cookie placed directly after the saved registers on the stack, at the bottom of the stack; and they also store a pointer to that cookie, or to a location in the kernel data section, and compare those values.
So, this is what that basically looks like. You see that the stack cookie pointer (‘stack_cookie_ptr’) points to the kernel data section, and the verification is then to compare that value against the cookie value that is stored on the stack.
This is before a function returns the function ‘epilog’; it verifies the stack cookie by dereferencing the stack cookie pointer and comparing that to the stack value. And if this check fails it would obviously result in kernel panic. So, Mark is going to continue to talk a bit about information leaking mitigations.
Read next part: iOS 6 Kernel Security 2 - Data Leaking Mitigations and Kernel ASLR