mach_portal: Improve amfid patch to support fat binaries

Posted: December 21st, 2016 | Author: | Filed under: iOS, jailbreak, Programming | Tags: , , , , , , , , | No Comments »

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.

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.



Leave a Reply