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
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:
Downloads
You can download the complete modified cdhash.c file here.