Tuesday, January 29, 2019

voucher_swap: Exploiting MIG reference counting in iOS 12

Posted by Brandon Azad, Project Zero

In this post I'll describe how I discovered and exploited CVE-2019-6225, a MIG reference counting vulnerability in XNU's task_swap_mach_voucher() function. We'll see how to exploit this bug on iOS 12.1.2 to build a fake kernel task port, giving us cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to read and write arbitrary kernel memory. (This bug was independently discovered by @S0rryMybad.) In a later post, we'll look at how to use this bug as a starting point to analyze and bypass Apple's implementation of ARMv8.3 Pointer Aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication (PAC) on A12 devices like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iPhone XS.

A curious discovery

MIG is a tool that generates Mach message parsing code, and vulnerabilities resulting from violating MIG semantics are nothing new: for example, Ian Beer's async_wake exploited an issue where IOSurfaceRootUserClient would over-deallocate a Mach port managed by MIG semantics on iOS 11.1.2.

Most prior MIG-related issues have been cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 result of MIG service routines not obeying semantics around object lifetimes and ownership. Usually, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG ownership rules are expressed as follows:

  1. If a MIG service routine returns success, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it took ownership of all resources passed in.
  2. If a MIG service routine returns failure, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it took ownership of none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resources passed in.

Unfortunately, as we'll see, this description doesn't cover cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 full complexity of kernel objects managed by MIG, which can lead to unexpected bugs.

The journey started while investigating a reference count overflow in semaphore_destroy(), in which an error path through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function left cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 semaphore_t object with an additional reference. While looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 autogenerated MIG function _Xsemaphore_destroy() that wraps semaphore_destroy(), I noticed that this function seems to obey non-conventional semantics.

Here's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant code from _Xsemaphore_destroy():

    task = convert_port_to_task(In0P->Head.msgh_request_port);

    OutP->RetCode = semaphore_destroy(task,
            convert_port_to_semaphore(In0P->semaphore.name));
    task_deallocate(task);
#if __MigKernelSpecificCode
    if (OutP->RetCode != KERN_SUCCESS) {
        MIG_RETURN_ERROR(OutP, OutP->RetCode);
    }

    if (IP_VALID((ipc_port_t)In0P->semaphore.name))
        ipc_port_release_send((ipc_port_t)In0P->semaphore.name);
#endif /* __MigKernelSpecificCode */

The function convert_port_to_semaphore() takes a Mach port and produces a reference on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 underlying semaphore object without consuming cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port. If we assume that a correct implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above code doesn't leak or consume extra references, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can conclude cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following intended semantics for semaphore_destroy():

  1. On success, semaphore_destroy() should consume cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 semaphore reference.
  2. On failure, semaphore_destroy() should still consume cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 semaphore reference.

Thus, semaphore_destroy() doesn't seem to follow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 traditional rules of MIG semantics: a correct implementation always takes ownership of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 semaphore object, regardless of 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 service routine returns success or failure.

This of course begs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 question: what are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 full rules governing MIG semantics? And are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re any instances of code violating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r MIG rules?

A bad swap

Not long into my investigation into extended MIG semantics, I discovered cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 function task_swap_mach_voucher(). This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 MIG definition from osfmk/mach/task.defs:

routine task_swap_mach_voucher(
                task            : task_t;
                new_voucher     : ipc_voucher_t;
        inout   old_voucher     : ipc_voucher_t);

And here's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant code from _Xtask_swap_mach_voucher(), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 autogenerated MIG wrapper:

mig_internal novalue _Xtask_swap_mach_voucher
       (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{
...
   kern_return_t RetCode;
   task_t task;
   ipc_voucher_t new_voucher;
   ipc_voucher_t old_voucher;
...
   task = convert_port_to_task(In0P->Head.msgh_request_port);

   new_voucher = convert_port_to_voucher(In0P->new_voucher.name);

   old_voucher = convert_port_to_voucher(In0P->old_voucher.name);

   RetCode = task_swap_mach_voucher(task, new_voucher, &old_voucher);

   ipc_voucher_release(new_voucher);

   task_deallocate(task);

   if (RetCode != KERN_SUCCESS) {
       MIG_RETURN_ERROR(OutP, RetCode);
   }
...
   if (IP_VALID((ipc_port_t)In0P->old_voucher.name))
       ipc_port_release_send((ipc_port_t)In0P->old_voucher.name);

   if (IP_VALID((ipc_port_t)In0P->new_voucher.name))
       ipc_port_release_send((ipc_port_t)In0P->new_voucher.name);
...
   OutP->old_voucher.name = (mach_port_t)convert_voucher_to_port(old_voucher);

   OutP->Head.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
   OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply));
   OutP->msgh_body.msgh_descriptor_count = 1;
}

Once again, assuming that a correct implementation doesn't leak or consume extra references, we can infer cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following intended semantics for task_swap_mach_voucher():

  1. task_swap_mach_voucher() does not hold a reference on new_voucher; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new_voucher reference is borrowed and should not be consumed.
  2. task_swap_mach_voucher() holds a reference on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 input value of old_voucher that it should consume.
  3. On failure, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output value of old_voucher should not hold any references on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointed-to voucher object.
  4. On success, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output value of old_voucher holds a voucher reference donated from task_swap_mach_voucher() to _Xtask_swap_mach_voucher() that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 latter consumes via convert_voucher_to_port().

With cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se semantics in mind, we can compare against cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 actual implementation. Here's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code from XNU 4903.221.2's osfmk/kern/task.c, presumably a placeholder implementation:

kern_return_t
task_swap_mach_voucher(
       task_t          task,
       ipc_voucher_t   new_voucher,
       ipc_voucher_t   *in_out_old_voucher)
{
   if (TASK_NULL == task)
       return KERN_INVALID_TASK;

   *in_out_old_voucher = new_voucher;
   return KERN_SUCCESS;
}

This implementation does not respect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 intended semantics:

  1. The input value of in_out_old_voucher is a voucher reference owned by task_swap_mach_voucher(). By unconditionally overwriting it without first calling ipc_voucher_release(), task_swap_mach_voucher() leaks a voucher reference.
  2. The value new_voucher is not owned by task_swap_mach_voucher(), and yet it is being returned in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 output value of in_out_old_voucher. This consumes a voucher reference that task_swap_mach_voucher() does not own.

Thus, task_swap_mach_voucher() actually contains two reference counting issues! We can leak a reference on a voucher by calling task_swap_mach_voucher() with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 third argument, and we can drop a reference on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher by passing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second argument. This is a great exploitation primitive, since it offers us nearly complete control over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher object's reference count.

(Furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r investigation revealed that thread_swap_mach_voucher() contained a similar vulnerability, but only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference leak part, and changes in iOS 12 made cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability unexploitable.)

On vouchers

In order to grasp cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 impact of this vulnerability, it's helpful to understand a bit more about Mach vouchers, although cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 full details aren't important for exploitation.

Mach vouchers are represented by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 type ipc_voucher_t in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel, with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following structure definition:

/*
* IPC Voucher
*
* Vouchers are a reference counted immutable (once-created) set of
* indexes to particular resource manager attribute values
* (which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365mselves are reference counted).
*/
struct ipc_voucher {
   iv_index_t      iv_hash;        /* checksum hash */
   iv_index_t      iv_sum;         /* checksum of values */
   os_refcnt_t     iv_refs;        /* reference count */
   iv_index_t      iv_table_size;  /* size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher table */
   iv_index_t      iv_inline_table[IV_ENTRIES_INLINE];
   iv_entry_t      iv_table;       /* table of voucher attr entries */
   ipc_port_t      iv_port;        /* port representing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher */
   queue_chain_t   iv_hash_link;   /* link on hash chain */
};

As cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 comment indicates, an IPC voucher represents a set of arbitrary attributes that can be passed between processes via a send right in a Mach message. The primary client of Mach vouchers appears to be Apple's libdispatch library.

The only fields of ipc_voucher relevant to us are iv_refs and iv_port. The ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r fields are related to managing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 global list of voucher objects and storing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attributes represented by a voucher, neicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r of which will be used in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit.

As of iOS 12, iv_refs is of type os_refcnt_t, which is a 32-bit reference count with allowed values in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range 1-0x0fffffff (that's 7 f's, not 8). Trying to retain or release a voucher with a reference count outside this range will trigger a panic.

iv_port is a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ipc_port object that represents this voucher to userspace. It gets initialized whenever convert_voucher_to_port() is called on an ipc_voucher with iv_port set to NULL.

In order to create a Mach voucher, you can call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host_create_mach_voucher() trap. This function takes a "recipe" describing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher's attributes and returns a voucher port representing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher. However, because vouchers are immutable, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is one quirk: if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 resulting voucher's attributes are exactly cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same as a voucher that already exists, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n host_create_mach_voucher() will simply return a reference to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 existing voucher racá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r than creating a new one.

That's out of line!

There are many different ways to exploit this bug, but in this post I'll discuss my favorite: incrementing an out-of-line Mach port pointer so that it points into pipe buffers.

Now that we understand what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability is, it's time to determine what we can do with it. As you'd expect, an ipc_voucher gets deallocated once its reference count drops to 0. Thus, we can use our vulnerability to cause cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher to be unexpectedly freed.

But freeing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher is only useful if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher is subsequently reused in an interesting way. There are three components to this: storing a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher, reallocating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher with something useful, and reusing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stored voucher pointer to modify kernel state. If we can't get any one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se steps to work, 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 whole bug is pretty much useless.

Let's consider cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first step, storing a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher. There are a few places in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel that directly or indirectly store voucher pointers, including struct ipc_kmsg's ikm_voucher field and struct thread's ith_voucher field. Of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 easiest to use is ith_voucher, since we can directly read and write this field's value from userspace by calling thread_get_mach_voucher() and thread_set_mach_voucher(). Thus, we can make ith_voucher point to a freed voucher by first calling thread_set_mach_voucher() to store a reference to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n using our voucher bug to remove cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 added reference, and finally deallocating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher port in userspace to free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher.

Next consider how to reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher with something useful. ipc_voucher objects live in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir own zalloc zone, ipc.vouchers, so we could easily get our freed voucher reallocated with anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r voucher object. Reallocating with any ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r type of object, however, would require us to force cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel to perform zone garbage collection and move a page containing only freed vouchers over to anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r zone. Unfortunately, vouchers don't seem to store any significant privilege-relevant attributes, so reallocating our freed voucher with anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r voucher probably isn't helpful. That means we'll have to perform zone gc and reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher with anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r type of object.

In order to figure out what type of object we should reallocate with, it's helpful to first examine how we will use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dangling voucher pointer in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread's ith_voucher field. We have a few options, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 easiest is to call thread_get_mach_voucher() to create or return a voucher port for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher. This will invoke ipc_voucher_reference() and convert_voucher_to_port() on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed ipc_voucher object, so we'll need to ensure that both iv_refs and iv_port are valid.

But what makes thread_get_mach_voucher() so useful for exploitation is that it returns cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher's Mach port back to userspace. There are two ways we could leverage this. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed ipc_voucher object's iv_port field is non-NULL, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n that pointer gets directly interpreted as an ipc_port pointer and thread_get_mach_voucher() returns it to us as a Mach send right. On 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 hand, if iv_port is NULL, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n convert_voucher_to_port() will return a freshly allocated voucher port that allows us to continue manipulating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher's reference count from userspace.

This brought me to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 idea of reallocating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher using out-of-line ports. One way to send a large number of Mach port rights in a message is to list cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ports in an out-of-line ports descriptor. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel copies in an out-of-line ports descriptor, it allocates an array to store cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list of ipc_port pointers. By sending many Mach messages containing out-of-line ports descriptors, we can reliably reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed ipc_voucher with an array of out-of-line Mach port pointers.

Since we can control which elements in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array are valid ports and which are MACH_PORT_NULL, we can ensure that we overwrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher's iv_port field with NULL. That way, when we call thread_get_mach_voucher() in userspace, convert_voucher_to_port() will allocate a fresh voucher port that points to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overlapping voucher. Then we can use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference counting bug again on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 returned voucher port to modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher's iv_refs field, which will change cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line port pointer that overlaps iv_refs by any amount we want.

Of course, we haven't yet addressed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 question of ensuring that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iv_refs field is valid to begin with. As previously mentioned, iv_refs must be in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range 1-0x0fffffff if we want to reuse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed ipc_voucher without triggering a kernel panic.

The ipc_voucher structure is 0x50 bytes and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iv_refs field is at offset 0x8; since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iPhone is little-endian, this means that if we reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher with an array of out-of-line ports, iv_refs will always overlap with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower 32 bits of an ipc_port pointer. Let's call cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mach port that overlaps iv_refs cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port. Using eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r MACH_PORT_NULL or MACH_PORT_DEAD as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port would result in iv_refs being eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r 0 or 0xffffffff, both of which are invalid. Thus, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only remaining option is to use a real Mach port as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port, so that iv_refs is overwritten with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower 32 bits of a real ipc_port pointer.

This is dangerous because if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower 32 bits of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port's address are 0 or greater than 0x0fffffff, accessing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 freed voucher will panic. Fortunately, kernel heap allocation on recent iOS devices is pretty well behaved: zalloc pages will be allocated from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 range 0xffffffe0xxxxxxxx starting from low addresses, so as long as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap hasn't become too unruly since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system booted (e.g. because of a heap groom or lots of activity), we can be reasonably sure that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower 32 bits of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port's address will lie within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 required range. Hence overlapping iv_refs with an out-of-line Mach port pointer will almost certainly work fine if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit is run after a fresh boot.

This gives us our working strategy to exploit this bug:

  1. Allocate a page of Mach vouchers.
  2. Store a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target voucher in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread's ith_voucher field and drop cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 added reference using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability.
  3. Deallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher ports, freeing all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vouchers.
  4. Force zone gc and reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page of freed vouchers with an array of out-of-line ports. Overlap cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 target voucher's iv_refs field with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 lower 32 bits of a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port and overlap cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher's iv_port field with NULL.
  5. Call thread_get_mach_voucher() to retrieve a voucher port for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 voucher overlapping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line ports.
  6. Use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability again to modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overlapping voucher's iv_refs field, which changes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line base port pointer so that it points somewhere else instead.
  7. Once we receive cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Mach message containing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line ports, we get a send right to arbitrary memory interpreted as an ipc_port.

Pipe dreams

So what should we get a send right to? Ideally we'd be able to fully control 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 fake ipc_port we receive without having to play risky games by deallocating and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n reallocating cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory backing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port.

Ian actually came up with a great technique for this in his multi_path and empty_list exploits using pipe buffers. Our exploit so far allows us to modify an out-of-line pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port so that it points somewhere else. So, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original base port lies directly in front of a bunch of pipe buffers in kernel memory, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we can leak voucher references to increment cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port pointer in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line ports array so that it points into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffers instead.

At this point, we can receive cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 message containing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 out-of-line ports back in userspace. This message will contain a send right to an ipc_port that overlaps one of our pipe buffers, so we can directly read and write 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 fake ipc_port's memory by reading and writing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overlapping pipe's file descriptors.

tfp0

Once we have a send right to a completely controllable ipc_port object, exploitation is basically deterministic.

We can build a basic kernel memory read primitive using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same old pid_for_task() trick: convert our port into a fake task port such that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake task's bsd_info field (which is a pointer to a proc struct) points to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory we want to read, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n call pid_for_task() to read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 4 bytes overlapping bsd_info->p_pid. Unfortunately, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re's a small catch: we don't know cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address of our pipe buffer in kernel memory, so we don't know where to make our fake task port's ip_kobject field point.

We can get around this by instead placing our fake task struct in a Mach message that we send to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port, after which we can read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffer overlapping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port and get 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 message containing our fake task from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 port's ip_messages.imq_messages field. Once we know 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 ipc_kmsg containing our fake task, we can overwrite 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 fake port to turn it into a task port pointing to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake task, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n call pid_for_task() on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake task port as usual to read 4 bytes of arbitrary kernel memory.

An unfortunate consequence of this approach is that it leaks one ipc_kmsg struct for each 4-byte read. Thus, we'll want to build a better read primitive as quickly as possible and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n free all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked messages.

In order to get 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 pipe buffer we can leverage cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fact that it resides at a known offset from 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 base port. We can call mach_port_request_notification() on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port to add a request that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port be notified once cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port becomes a dead name. This causes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port's ip_requests field to point to a freshly allocated array containing a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 base port, which means we can use our memory read primitive to read out 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 base port and 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 pipe buffer.

At this point we can build a fake kernel task inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pipe buffer, giving us full kernel read/write. Next we allocate kernel memory with mach_vm_allocate(), write a new fake kernel task inside that memory, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake port pointer in our process's ipc_entry table to point to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new kernel task instead. Finally, once we have our new kernel task port, we can clean up all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked memory.

And that's cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 complete exploit! You can find exploit code for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 iPhone XS, iPhone XR, and iPhone 8 here: voucher_swap. A more in-depth, step-by-step technical analysis of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit technique is available in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 source code.

Bug collision

I reported this vulnerability to Apple on December 6, 2018, and by December 19th Apple had already released iOS 12.1.3 beta build 16D5032a which fixed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue. Since this would be an incredibly quick turnaround for Apple, I suspected that this bug was found and reported by some ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r party first.

I subsequently learned that this bug was independently discovered and exploited by Qixun Zhao (@S0rryMybad) of Qihoo 360 Vulcan Team. Amusingly, we were both led to this bug through semaphore_destroy(); thus, I wouldn't be surprised to learn that this bug was broadly known before being fixed. SorryMybad used this vulnerability as part of a remote jailbreak for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Tianfu Cup; you can read about his strategy for obtaining tfp0.

Conclusion

This post looked at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 discovery and exploitation of P0 issue 1731, an IPC voucher reference counting issue rooted in failing to follow MIG semantics for inout objects. When run a few seconds after a fresh boot, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit strategy discussed here is quite reliable: on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 devices I've tested, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit succeeds upwards of 99% of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time. The exploit is also straightforward enough that, when successful, it allows us to clean up all leaked resources and leave cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system in a completely stable state.

In a way, it's surprising that such "easy" vulnerabilities still exist: after all, XNU is open source and heavily scrutinized for valuable bugs like this. However, MIG semantics are very unintuitive and don't align well with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 natural patterns for writing secure kernel code. While I'd love to believe that this is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last major MIG bug, I wouldn't be surprised to see at least a few more crop up.

This bug is also a good reminder that placeholder code can also introduce security vulnerabilities and should be scrutinized as tightly as functional code, no matter how simple it may seem.

And finally, it's worth noting that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 biggest headache for me while exploiting this bug, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 limited range of allowed reference count values, wasn't even an issue on iOS versions prior to 12. On earlier platforms, this bug would have always been incredibly reliable, not just directly after a clean boot. Thus, it's good to see that even though os_refcnt_t didn't stop this bug from being exploited, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mitigation at least impacts exploit reliability, and probably decreases cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 value of bugs like this to attackers.

My next post will show how to use this exploit to analyze Apple's implementation of Pointer Aucá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ntication, culminating in a technique that allows us to forge PACs for pointers signed with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 A keys. This is sufficient to call arbitrary kernel functions or execute arbitrary code in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel via JOP.