Apple Mac OSX / iOS - Unsandboxable Kernel Code Exection Due to iokit Double Release in IOKit

EDB-ID:

39357




Platform:

OSX

Date:

2016-01-28


Source: https://code.google.com/p/google-security-research/issues/detail?id=620

I wanted to demonstrate that these iOS/OS X kernel race condition really are exploitable so here's a PoC
which gets RIP on OS X. The same techniques should transfer smoothly to iOS :)

The bug is here:

void IORegistryIterator::reset( void )
{
    while( exitEntry())
    {}
    
    if( done) {
        done->release();
        done = 0;
    }
    
    where->current = root;
    options &= ~kIORegistryIteratorInvalidFlag;
}

We can call this from userspace via the IOIteratorReset method.

done is an OSOrderedSet* and we only hold one reference on it; therefore we can race two threads
to both see the same value of done, one will free it but before it sets done to NULL the other will
call ->release on the now free'd OSOrderedSet.

How to get instruction pointer control?

The XNU kernel heap seems to have been designed to make this super easy :) When the first thread frees
done zalloc will overwrite the first qword of the allocation with the freelist next pointer (and the last qword
with that pointer xor'd with a secret.) This means that what used to be the vtable pointer gets overwritten
with a valid pointer pointing to the last object freed to this zone. If we can control that object then
the qword at +0x28 will be called (release is at offset +0x28 in the OSObject vtable which is the base
of all IOKit objects including OSOrderedSet.)

This PoC uses OSUnserializeXML to unserialize an OSData object with controlled contents then free it, which
puts a controlled heap allocation at the head of the kalloc.80 freelist giving us pretty easy instruction pointer control.

I've attached a panic log showing kernel RIP at 0xffffff8041414141. You will probably have to fiddle with the
PoC a bit to get it to work, it's only a PoC but it does work! (I have marked the value to fiddle with :) )

As a hardening measure I would strongly suggest at the very least flipping the location of the obfuscated and
unobfuscate freelist pointers such that the valid freelist pointer doesn't overlap with the location of the
vtable pointer.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39357.zip