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.

  • Easily preview Mermaid diagrams
  • Live update when editing in your preferred editor
  • Capture screenshots with customizable margins
  • Create PNG from the Terminal
  • Free download on the Mac App Store
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.