Read previous part: iOS 6 Kernel Security 3 - Kernel Address Space Protection
Mark Dowd and Tarjei Mandt overview the more sophisticated iOS 6 attacks strategies where native protections may fail.
Tarjei Mandt: Alright, now we’re going to talk a bit about some attack strategies for iOS 6. As Mark mentioned, most of these protections now kill basically all the known attack vectors: you know, the syscall table overwrites, patching kernel code, attacking data structures, which you no longer can do to randomize locations.
So, we basically need something new. Generally, when you want to have an exploit you need to leak some information, especially if you want to bypass ASLR and stuff like that. And the corruption primitives, also, is what dictates the strategy: do we write in to adjacent buffer; do we write to relative location from the buffer; do we write to an arbitrary location; and stuff like that. We will look at the different primitives in this case separately.
Leaking the kernel base address is really useful because it has a lot of information in its data structures. Previously, you could use the API called ‘kext_request()’ to request information about kernel extensions, and this API was divided into two parts: you had active and passive operations. The active operations required privileged access, so, root. This is because it did stuff like load, unload, start, stop, etc. It should be mentioned that iOS which is configured with the secure kernel configuration removes the ability to load kernel extensions, so you can only do unload, start, stop and stuff like that.
Before iOS 6, there were no restrictions on accessing these passive operations. As you can see on this slide here, you can request the address of the kernel image by calling the ‘RequestPredicateGetKernelLoadAddress’, and there’s a few others there that allow you to request a lot of information about the kernel environment and the kernel extensions.
So, in iOS 6 they tried to address this, but they sort of didn’t get the check right. In this process, they obviously try to prevent you from getting the load address of the kernel, but in that process they allow all the other operations. So, while you should only access passive operations, they now allow you to also access active operations as well as all the other useful APIs regarding disclosing information about the kernel extension environment.
Now we can actually use this ‘kKextRequestPredicateGetLoaded’ which returns a dictionary on information about all the kernel extensions. You can do this in iOS 5 as well, but it’s useful to us because we have ASLR to deal with. So, this returns all the loaded addresses as well as mach-o header dumps for all the loaded modules, including the kernel. And although the load address and the segment headers in these mach-o headers are obscured to hide ASLR slide, section headers are not, so we can actually get any address to any section for any kernel extension, including the kernel.
We can simply just pass it a request like this one, we get a response and we decode that response and look at the section address which actually is the real address. This doesn’t require you to have root privileges, you can do this as non-privileged user, and so ASLR is very easily defeated.
Now Mark is going to talk a bit about heap corruption.
Mark Dowd: As we mentioned earlier, all the strategies that iOS 6 has employed – this has more or less made it such that standard heap overflow tricks are no longer really going to work out. Basically, you can overwrite a freelist pointer but validation will fail and there will be a kernel panic. So we required new strategies, and, really, the strategies that you employ depend on the properties of the bug you’re trying to exploit. So in this section we’re just going to mention how, obviously, since the freelist validation is in place and it’s hard to defeat, maybe we’ll target metadata instead – that’s quite useful.
There’s various control structures that can be quite useful; some of these attacks will require some level of heap grooming which may or may not be difficult, depending on block size and, again, the properties of the bug that you’re actually trying to exploit. I’m going to quickly present four primitives that we found to be quite useful, and then we’re probably going to do a demo of our thing.
The primitives that I came up with all revolve around this ‘vm_map_copy_t’ structure. You can see there it’s quite a simple structure, it’s got a ‘type’ and ‘offset’ size, and then a ‘union’ that has a header pointer to a ‘vm_object’ or a structure that has a pointer to some data and the ‘kalloc_size’.
This structure is quite useful, particularly because of various properties that it has. Basically, the kernel has this function called ‘vm_map_copyin()’, and it’s supposed to use this when it’s copying large chunks of data from one map to another. And whenever it’s transferring a large block of memory, they’ll use this ‘vm_map_copyin()’ structure which basically creates one of these ‘vm_map_copy_t’ structures. Rather than copying all the data at once, they’ll usually just create a data structure saying “Oh, this data from this map – we need to put it in this map,” or something like that. They use kernel buffers when the size is quite small, when it’s less than 4k; and we can actually create these really easily.
If you haven’t looked at mach messaging or mach ports before, it’s a really large part of XNU kernel, and we didn’t really have time to go into it in this presentation. But essentially you have these mach ports that you can create and send messages to and receive messages from. The messages that you send can contains things called ‘ool_descriptors’, or out-of-line descriptors. They more or less indicate when you are sending a message, like – “Oh, I want to send all this data over here,” but instead of copying it all into your message you just provide a pointer to it, and they’ll use this ‘vm_map_copyin()’ to copy it into the kernel, and when the mach port receives the message then they’ll copy it back out to the target process. So, basically, you can just create a mach port to receive messages, send a bunch of messages to them with these ‘ool_descriptors’ in them, and it will end up creating the ‘vm_map_copy_t’ structures with kernel buffers in them. And it turns out corrupting these structures is quite useful. The other good thing about them is, apart from the header there is completely user-controlled data under it, and anything under 4096 bytes – you can basically create that size, so you can target overflows that take place in a whole bunch of different ‘kalloc’ zones, depending on the bug that you’re trying to exploit.
Read next part: iOS 6 Kernel Security 5 - Conclusion