Over a million developers have joined DZone.

Anatomy of an Exploit: iOS Race Conditions, Part II

DZone's Guide to

Anatomy of an Exploit: iOS Race Conditions, Part II

Check out the second half of the author's dive into Todesco's source code on ghostbin that exploits a double-free bug in a kernel extension in iOS.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

In the first part of this series, I outlined the structure of Luca's double free exploit and how the code was structured. I ended the piece at pwn_this_b***h(.)

Today, I'm going to look closely at pwn_this_b***h(.)


So remember, pwn_this_b***h(.) is called from thr(.), in a loop, with ports yielding overlapped memory allocations. And how did we determine that? Well, we checked for a value that we had set in the previous sections of code where we were message bombing the service from a bunch of threads we had spawned. So anyway, at this point, we've detected the flawed memory state and we're going to try to access kernel memory. That's what pwn_this_b***h(.) is designed to do.

Kernel Memory Pages

So, one of the first things we do is send some mach messages between this thread and whatever thread the thr(.) method is running in. Here, we send a message from our current method, on our current thread, that's picked up by the thread running thr(.). There, the address is set to 0x13371337, and we verify the change when we receive a message back via this chunk of code:

mach_msg( (mach_msg_header_t *) msg, MACH_SEND_MSG, sizeof(oolmsg_t), 0, 0, 0, 0 );
assert(*(uint32_t*)(msg->desc[0].address) == 0x13371337);

As far as I can tell, this just shows that we are communicating via mach primitives, and doesn't serve any other purpose. Next, we spray and receive a bunch of data, and use the wonderfully undocumented call io_service_open_extended(.), which we call with a handle to the AppleHDQGasGaugeControl kernel module. This is the module with the faulty memory handling. Now things are getting interesting.

Finding a Leak

Now we enter a conditional. The first part of the conditional sets the stage, finding a leaked chunk of memory. The second leaks a kernel allocated page. Let's start with the first block. 

Here, we grab a specific kernel object. We've flagged this object via a call to io_service_open_extended(.), as far as I can tell, via the earlier call. We use this object to allocate data that we later use via the heap_leak_ptr. Note there's a for loop that loops eight times; also, we submit a PLIST data structure defined in the bf variable via the first call to io_service_open_extended(.).  That structure contains eight keys, named '1' to '8', and one named 'step 1'. This last one is the object we compromise.

Next, we loop through various properties defined with that object until we find one that we've previously defined via io_service_open_extended(.). Then, we run a check against the buffer, and use this as the leaked pointer. Here, I found this interesting,

if (*(uint32_t*)(sbuffer) != 0x3) {

as the only place we seem to set this value is in the next block.

The final block uses lots of undocumented functions, and bombs kernel data structures with data and calls to io_service_open_get_extended(.):

char* bf = (char*) [[NSString stringWithFormat:@"<dict><key>1</key><data>%@</data><key>2</key><data>%@</data><key>3</key><data>%@</data><key>4</key><data>%@</data><key>5</key><data>%@</data><key>6</key><data>%@</data><key>7</key><data>%@</data><key>8</key><data>%@</data><key>step1</key><data></data></dict>",bd,bd,bd,bd,bd,bd,bd,bd] UTF8String];

for (int i = 0; i < 128; i++) {
    mach_port_t ptz[2];
    io_connect_t ff=0;
    io_service_open_extended(gg, mach_task_self(), 0, NDR_record, bf, strlen(bf)+1, &err, &ff);

Then, we make an external call that seemingly fails, and we have a reference to a leaked kernel heap page!

So, this is pretty dense code, in that it uses tons of function calls that are clearly private, apple-internal calls that are completely undocumented. But hey, why not guess a bit at what they do?

First, we have io_service_open_extended(.). This, we seed with PLIST like values, and it seems that properties are automagically defined on kernel objects. That's pretty cool stuff right there. Also, there seems to be a link between IOConnectCallMethod(.) and io_connect_method_scalarI_scalarO(.). Note that each call to IOConnectCallMethod(.) uses the ordinal 12, the same ordinal used in io_connect_method_scalarI_scalarO(.). Interesting.

Well, anyway, there you have it. That's the essence of Luca's exploit. Remember, the kernel in any operating system is a really big place, and there's bound to be bugs. 

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

exploits ,ios

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}