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.
Conference Host: We’re ready to rock and roll. We’re having this presentation about iOS 6 security by Mark Dowd and Tarjei Mandt. Mark Dowd is the founder of Azimuth Security. He worked for years as a senior researcher at IBM ISS X-Force and as a principal security architect at McAfee.
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.
We’ve got two major parts of this talk. In the first part, we are going to talk about defense; specifically we are going to mention the heap hardening strategies that have been introduced in iOS 6; we are going to mention stack cookies; various information leaking mitigations that have been implemented; the address space layout randomization (ASLR) that has been added to the kernel; and also some User/Kernel address space hardening. And in part two, we are going to talk about some offensive techniques including some information leaking strategies to bypass some of the ASLR mitigations put in place in iOS 6. And we’ll also talk about a few primitives that we developed when developing exploits for the iOS 6 kernel that you can mix and match, and they should be useful for building exploits that will work against iOS 6.
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.
The heap hardening part is really what tries to address past attacks regarding zone allocations and attacks on freelists. There are basically three mitigations put in place: we have mitigations involving pointer validation, block poisoning, and freelist integrity verification. And this is, as mentioned, specific to the zone allocator, so it concerns all the functions like ‘zalloc()’, ‘kalloc()’, ‘MALLOC()’, ‘MALLOC_ZONE()’, etc.
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.
So, in iOS 6 they attempt to address this in multiple ways. One of the goals is to prevent invalid pointers from being entered into the zone freelists. And this is done by performing additional checks on the pointers before they are entered into the list itself. In this case, any pointer that is put in is verified to be in kernel memory, so 0x80000000 – up until this upper bound. If the ‘allows_foreign’ variable for this specific zone is set, no additional verification is performed, but if it’s not set it’s also checked if it’s within the ‘zone_map’ defined for that specific zone.
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.
The third one is more on preventing exploitation altogether by trying to address the overwriting of the freelist pointers. So, when you boot up the kernel, two random values are generated by a function called ‘zone_bootstrap()’: one is a 32-bit cookie for “poisoned blocks”, and the other one is a 31-bit cookie for “non-poisoned blocks”. These are used as validation cookies of the next_pointers themselves.
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.
Another thing concerning the hardening of the zone allocator is regarding primitives. Stefan Esser had a talk at SyScan and Black Hat called “iOS Kernel Heap Armageddon” where he proposed a technique of using ‘OSUnserializeXML()’ to basically manipulate the state of the ‘kalloc’ zone. This allowed you to precisely allocate and free ‘kalloc’ zone data. It was also possible to force persistent allocations by creating enough references, so, basically, if you create enough references you could freeze the reference count. So, when this, say, dictionary was no longer used you would still have these objects in memory, and this was useful in order to cause persistent allocations and things like that.
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.
That was on Heap. The stack cookies are also used, and this is obviously to prevent the stack overflow exploitation. It’s applied to functions with structures and buffers. The random value that is used to see this cookie is generated by the kernel on boot as well, in ‘arm_init()’. And although it’s a 32-bit value, the entropy is only 24-bit, because the second byte is actually zeroed out – this is presumably to prevent you from using a string copy operation to guess the cookie or bypass this in some way.
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.