This blog post is part of a series of posts in which I will discuss several techniques to own XNU, the kernel used by Apple’s OS X and iOS. My focus will be on heap-based attacks, such as heap overflows, double frees, use-after-frees and zone confusion.
This post will be describing the zalloc() allocator, and how kalloc() (which is used for a lot of stuff including the IOKit C++ “new” operator) uses it. I’ll assume a page is 4096 bytes in your machine.
You can use zinit() to create a zone, which is essentially a “sub-heap”, completely independent of other heaps. A zone will always allocate a fixed-size memory area, and it does so by allocating a page and dividing by N bytes. For example, the kalloc.512 zone, which allocates 512 byte allocations, will take a 4096 byte page and divide it up in 8 free chunks. Each chunk references the previous chunk in a linked list, and every time you call zalloc() on it, the last chunk will be returned and the zone will move up an item in the linked list. zfree() will pop the chunk back into the list, and the zone will return it in the next zalloc(), unless another chunk is freed first. You can use “sudo zprint” to see all zones that have been zinit()ed, along with useful infos on allocations.
kalloc resembles much more libc’s malloc() in it’s interface, requiring only your allocation’s size as argument, but unlike libc’s free(), kfree (kalloc’s free equivalent) requires both the pointer to the allocation AND the allocation size, since neither zalloc nor kalloc store any metadata in allocated chunks, so kfree can not know what zone your chunk has to be popped back in.
So, to recap, zalloc uses multiple heaps, each heap is called zone, each zone always allocates same-sized allocs, kalloc registers up various zones for 16, 32, 64, 128, etc. allocations and will take any reasonable size and return an allocation in the zone with smallest enough size to satisfy the allocation (kalloc(15) returns a 16 byte chunk, kalloc(17) returns a 32 byte chunk). Another important thing to remember is that each zone has a linked list that acts as a LIFO, and when no more free chunks are available in a zone, a new page is allocated, split up in chunks and each chunk is added to the free list (which is held inline).
Hopefully this gives you a simple explanation on how the XNU kernel handles the heap. I skipped a lot of stuff and tried to keep this as simple as possible, but if you have any question regarding this feel free to contact me on Twitter, on IRC if you see me idling around some channel you’re in, via email at [email protected] or on XMPP (w/ OTR)[email protected]
P.S. Free chunk metadata used to be an easy write-what-where in case of heap overflows, but nowadays the unlink operation is hardened, plus I don’t like free chunk meta data attacks anyway, so I won’t cover free chunk metadata attacks at all.