Monorail Project: project-zero Issues People Development process History Sign in
New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.
Starred by 3 users
Status: Fixed
Last visit > 30 days ago
Closed: Jan 2017

Sign in to add a comment
iOS/MacOS kernel memory corruption due to userspace pointer being used as a length
Project Member Reported by, Nov 23 2016 Back to list
mach_voucher_extract_attr_recipe_trap is a mach trap which can be called from any context

Here's the code:

  mach_voucher_extract_attr_recipe_trap(struct mach_voucher_extract_attr_recipe_args *args)
    ipc_voucher_t voucher = IV_NULL;
    kern_return_t kr = KERN_SUCCESS;
    mach_msg_type_number_t sz = 0;

    if (copyin(args->recipe_size, (void *)&sz, sizeof(sz)))     <---------- (a)
      return KERN_MEMORY_ERROR;

      return MIG_ARRAY_TOO_LARGE;

    voucher = convert_port_name_to_voucher(args->voucher_name);
    if (voucher == IV_NULL)

    mach_msg_type_number_t __assert_only max_sz = sz;

      /* keep small recipes on the stack for speed */
      uint8_t krecipe[sz];
      if (copyin(args->recipe, (void *)krecipe, sz)) {
        kr = KERN_MEMORY_ERROR;
        goto done;
      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
    } else {
      uint8_t *krecipe = kalloc((vm_size_t)sz);                 <---------- (b)
      if (!krecipe) {
        goto done;

      if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) {         <----------- (c)
        kfree(krecipe, (vm_size_t)sz);
        kr = KERN_MEMORY_ERROR;
        goto done;

      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
      kfree(krecipe, (vm_size_t)sz);

    kr = copyout(&sz, args->recipe_size, sizeof(sz));

    return kr;

Here's the argument structure (controlled from userspace)

  struct mach_voucher_extract_attr_recipe_args {
    PAD_ARG_(mach_port_name_t, voucher_name);
    PAD_ARG_(mach_voucher_attr_key_t, key);
    PAD_ARG_(mach_voucher_attr_raw_recipe_t, recipe);
    PAD_ARG_(user_addr_t, recipe_size);

recipe and recipe_size are userspace pointers.

At point (a) four bytes are read from the userspace pointer recipe_size into sz.

At point (b) if sz was less than MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) and greater than MACH_VOUCHER_TRAP_STACK_LIMIT (256)
sz is used to allocate a kernel heap buffer.

At point (c) copyin is called again to copy userspace memory into that buffer which was just allocated, but rather than passing sz (the 
validate size which was allocated) args->recipe_size is passed as the size. This is the userspace pointer *to* the size, not the size!

This leads to a completely controlled kernel heap overflow.

Tested on MacOS Sierra 10.12.1 (16B2555)
4.3 KB View Download
Project Member Comment 1 by, Nov 23 2016
Labels: Id-653264220 Reported-2016-Nov-23
Project Member Comment 2 by, Jan 25 2017
Labels: CVE-2017-2370 Fixed-2017-Jan-23
MacOS advisory:
iOS advisory:
Project Member Comment 3 by, Jan 25 2017
Status: Fixed
Project Member Comment 4 by, Jan 25 2017
attaching my exploit for iOS 10.2 iPod Touch 6G 14C92
Gets kernel arbitrary r/w
39.7 KB Download
Project Member Comment 5 by, Jan 25 2017
Labels: -Restrict-View-Commit
Comment 6 Deleted
I updated the addresses for my iPhone 6s iOS 10.2 found using Ida however it still comes back as read from kernel memory as 0 

Am I missing something as far as I saw it was only the addresses that seem to be changing per device and version
Project Member Comment 8 by, May 8
Yeah, it's not too user-friendly to add support for other devices, sorry!

To add support for another device you have to update these constants:

  kaslr_shift = vtable - 0xFFFFFFF006FA2C50;

  kernel_base = 0xFFFFFFF007004000 + kaslr_shift;
  get_metaclass = 0xFFFFFFF0074446DC + kaslr_shift;
  osserializer_serialize = 0xFFFFFFF00745B0DC + kaslr_shift;
  ret = 0xFFFFFFF0074446E4 + kaslr_shift;
  kernel_uuid_copy = 0xFFFFFFF0074664F8 + kaslr_shift;

The first one is the address of the AGXCommandQueue vtable; I seem to remember this one is the trickiest to find without extra tooling as there's no symbol for it.

Find the only cross reference to the string "AGXCommandQueue". It should be in an InitFunc which is initializing that class's OSMetaClass object in the bss. The first argument passed to the OSMetaClass constructor is its address; if you look at the xrefs to *that* address you'll be able to find the metaclass's ::alloc method which will call AGXCommandQueue's operator new. You should see the size (0xDB8) being passed as the first argument to the first function call (which is called the allocator). You should then be able to find the store of the vtable pointer to the object and update the first constant.

The kernel base should remain the same.

get_metaclass in this case is the address of OSData::getMetaClass; there's a symbol for this.

osserializer_serialize is the symbol OSSerializer::serialize

ret is just any RET instruction

kernel_uuid_copy is the symbol uuid_copy

There are a few reasons why the exploit could fail; most likely is that the heap groom failed (it's not a very advanced heap groom.) For devices with more RAM try increasing the iteration count in this loop to eg 10000:

  for (int i = 0; i < 2000; i++){

Also try rebooting the phone; leave it for a couple of minutes then try the exploit once.
Brilliant the way you explained the way to get addresses is very similar to what I did so I think it might be the iterations I'll give it ago and check my addresses again just to make sure 

Thank you for replying 
Regard cawk
Brilliant got around to trying it my addresses were indeed correct but setting the iteration count to 15000 did the trick thanks for your help

Time to work my way through other security mechanisms such as amfi:)
Amazing work! Thank you for the writeup and the code :)

Sign in to add a comment