Ian Beer did an incredible work with his iOS 10.1.1 exploit. The mach_portal proof of concept gives you a root shell on iOS 10.1.1. You can read more about it here:

https://bugs.chromium.org/p/project-zero/issues/detail?id=965

While playing with it, I discovered that the amfid patch was only supporting thin arm64 binaries. I did not find a fix online so here is my solution.

Want to support this blog? Please check out

MarkChart

amfid patch

In this PoC amfid is patched to allow any signatures and entitlements. The amfid patch searches for the LC_CODE_SIGNATURE blob, calculate the expected SHA1 checksum and write it.

However as mentioned at the top of the file cdhash.c:

this code has very minimal mach-o parsing - it works for thin arm64 binaries though

And effectively if you run a fat binary (arm64 and armv7), you will get a kernel panic after:

got exception message from amfid!

got thread state

got filename for amfid request: /private/var/containers/Bundle/Application/7066B0A5-BFDC-4C60-B08B-3C64FF98FFDB/mach_portal.app/iosbinpack64/usr/bin/printHello

[-] too many load commands

To support fat binaries, we just need to improve the function that searches for the LC_CODE_SIGNATURE blob. Here is the original find_cs_blob() function:

void* find_cs_blob(uint8_t* buf, size_t size) {
  struct mach_header_64* hdr = (struct mach_header_64*)buf;
   
  uint32_t ncmds = hdr->ncmds;
   
  assert(ncmds < 1000, "too many load commands");
   
  uint8_t* commands = (uint8_t*)(hdr+1);
  for (uint32_t command_i = 0; command_i < ncmds; command_i++) {
    //assert(commands + sizeof(struct load_command) < end, "invalid load command");
     
    struct load_command* lc = (struct load_command*)commands;
    //assert(commands + lc->cmdsize <= end, "invalid load command");
     
    if (lc->cmd == LC_CODE_SIGNATURE) {
      struct linkedit_data_command* cs_cmd = (struct linkedit_data_command*)lc;
      printf("found LC_CODE_SIGNATURE blob at offset +0x%x\n", cs_cmd->dataoff);
      return ((uint8_t*)buf) + cs_cmd->dataoff;
    }
     
    commands += lc->cmdsize;
  }
  return NULL;
}

You can see the assert ’too many load commands’ that is printed just before the kernel panic. As you can see, this function expects a mach_header_64 and don’t support a fat header.

find_cs_blob() with fat support

To solve this issue, we need to:

  • detect a fat binary
  • find the correct fat_arch for the current cpu type and cpu subtype
  • find the file offset of the fat_arch
  • use the file offset to get the mach header and correct LC_CODE_SIGNATURE blob

Below is the updated find_cs_blob() function:

void* find_cs_blob(uint8_t* buf, size_t size) {
     
  uint32_t fileOffset = 0;
 
  uint32_t magic = *(uint32_t*)buf;
  if(ntohl(magic) == FAT_MAGIC)
  {
    printf("found a fat header\n");
     
    // Get the cputype and cpusubtype of the mach_portal binary
    struct mach_header_64 *mainMachHeader = (struct mach_header_64 *)_dyld_get_image_header(0);
    cpu_type_t mainCpuType = mainMachHeader->cputype & ~CPU_ARCH_MASK;
    cpu_type_t mainCpuSubType = mainMachHeader->cpusubtype & ~CPU_SUBTYPE_MASK;
     
    struct fat_header *fatHeader = (struct fat_header *)buf;
    struct fat_arch *fatArch = (struct fat_arch *)(buf + sizeof(struct fat_header));
    for(int i = 0 ; i < ntohl(fatHeader->nfat_arch) ; i++, fatArch++)
    {
      cpu_type_t cpuType = ntohl(fatArch->cputype) & ~CPU_ARCH_MASK;
      cpu_subtype_t cpuSubType = ntohl(fatArch->cpusubtype) & ~CPU_SUBTYPE_MASK;
      if(cpuType == mainCpuType && cpuSubType == mainCpuSubType)
      {
        fileOffset = ntohl(fatArch->offset);
        printf("arm64 arch offset is %u\n", fileOffset);
        fatHeader++;
        break;
      }
    }
     
    if (fileOffset == 0)
    {
      printf("arch not found in fat header\n");
    }
  }
     
  struct mach_header_64* hdr = (struct mach_header_64*)(buf + fileOffset);
   
  uint32_t ncmds = hdr->ncmds;
   
  assert(ncmds < 1000, "too many load commands");
   
  uint8_t* commands = (uint8_t*)(hdr+1);
  for (uint32_t command_i = 0; command_i < ncmds; command_i++) {
    //assert(commands + sizeof(struct load_command) < end, "invalid load command");
     
    struct load_command* lc = (struct load_command*)commands;
    //assert(commands + lc->cmdsize <= end, "invalid load command");
     
    if (lc->cmd == LC_CODE_SIGNATURE) {
      struct linkedit_data_command* cs_cmd = (struct linkedit_data_command*)lc;
      printf("found LC_CODE_SIGNATURE blob at offset +0x%x\n", cs_cmd->dataoff);
      return (((uint8_t*)buf + fileOffset)) + cs_cmd->dataoff;
    }
     
    commands += lc->cmdsize;
  }
  return NULL;
}

Note that you will need to include:

#include <mach-o/dyld.h>
#include <mach-o/fat.h>

Running a fat binary

Running a fat binary doesn’t trigger a kernel panic anymore:

Running a fat binary

Downloads

You can download the complete modified cdhash.c file here.