Skip to content

Instantly share code, notes, and snippets.

@coolstar
Created July 7, 2020 22:21
Show Gist options
  • Select an option

  • Save coolstar/8afb88ebe4a83b51ae522b75d93eadd9 to your computer and use it in GitHub Desktop.

Select an option

Save coolstar/8afb88ebe4a83b51ae522b75d93eadd9 to your computer and use it in GitHub Desktop.

Revisions

  1. coolstar created this gist Jul 7, 2020.
    490 changes: 490 additions & 0 deletions patchfinder64.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,490 @@
    //
    // patchfinder64.c
    // extra_recipe
    //
    // Created by xerub on 06/06/2017.
    // Copyright © 2017 xerub. All rights reserved.
    //

    #include <assert.h>
    #include <stdint.h>
    #include <string.h>
    #include <errno.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <TargetConditionals.h>

    static bool auth_ptrs = false;
    typedef unsigned long long addr_t;
    static addr_t kerndumpbase = -1;
    static addr_t xnucore_base = 0;
    static addr_t xnucore_size = 0;
    static addr_t prelink_base = 0;
    static addr_t prelink_size = 0;
    static addr_t kernel_entry = 0;
    static void *kernel_mh = 0;
    static addr_t kernel_delta = 0;
    bool monolithic_kernel = false;

    #define IS64(image) (*(uint8_t *)(image) & 1)

    #define MACHO(p) ((*(unsigned int *)(p) & ~1) == 0xfeedface)

    /* generic stuff *************************************************************/

    #define UCHAR_MAX 255

    /* these operate on VA ******************************************************/

    #define INSN_B 0x14000000, 0xFC000000
    #define INSN_ADRP 0x90000000, 0x9F000000

    /* patchfinder ***************************************************************/

    static addr_t
    step64(const uint8_t *buf, addr_t start, size_t length, uint32_t what, uint32_t mask)
    {
    addr_t end = start + length;
    while (start < end) {
    uint32_t x = *(uint32_t *)(buf + start);
    if ((x & mask) == what) {
    return start;
    }
    start += 4;
    }
    return 0;
    }

    static addr_t
    step64_back(const uint8_t *buf, addr_t start, size_t length, uint32_t what, uint32_t mask)
    {
    addr_t end = start - length;
    while (start >= end) {
    uint32_t x = *(uint32_t *)(buf + start);
    if ((x & mask) == what) {
    return start;
    }
    start -= 4;
    }
    return 0;
    }

    static addr_t
    calc64(const uint8_t *buf, addr_t start, addr_t end, int which)
    {
    addr_t i;
    uint64_t value[32];

    memset(value, 0, sizeof(value));

    end &= ~3;
    for (i = start & ~3; i < end; i += 4) {
    uint32_t op = *(uint32_t *)(buf + i);
    unsigned reg = op & 0x1F;
    if ((op & 0x9F000000) == 0x90000000) {
    signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8);
    //printf("%llx: ADRP X%d, 0x%llx\n", i, reg, ((long long)adr << 1) + (i & ~0xFFF));
    value[reg] = ((long long)adr << 1) + (i & ~0xFFF);
    /*} else if ((op & 0xFFE0FFE0) == 0xAA0003E0) {
    unsigned rd = op & 0x1F;
    unsigned rm = (op >> 16) & 0x1F;
    //printf("%llx: MOV X%d, X%d\n", i, rd, rm);
    value[rd] = value[rm];*/
    } else if ((op & 0xFF000000) == 0x91000000) {
    unsigned rn = (op >> 5) & 0x1F;
    unsigned shift = (op >> 22) & 3;
    unsigned imm = (op >> 10) & 0xFFF;
    if (shift == 1) {
    imm <<= 12;
    } else {
    //assert(shift == 0);
    if (shift > 1) continue;
    }
    //printf("%llx: ADD X%d, X%d, 0x%x\n", i, reg, rn, imm);
    value[reg] = value[rn] + imm;
    } else if ((op & 0xF9C00000) == 0xF9400000) {
    unsigned rn = (op >> 5) & 0x1F;
    unsigned imm = ((op >> 10) & 0xFFF) << 3;
    //printf("%llx: LDR X%d, [X%d, 0x%x]\n", i, reg, rn, imm);
    if (!imm) continue; // XXX not counted as true xref
    value[reg] = value[rn] + imm; // XXX address, not actual value
    } else if ((op & 0xF9C00000) == 0xF9000000) {
    unsigned rn = (op >> 5) & 0x1F;
    unsigned imm = ((op >> 10) & 0xFFF) << 3;
    //printf("%llx: STR X%d, [X%d, 0x%x]\n", i, reg, rn, imm);
    if (!imm) continue; // XXX not counted as true xref
    value[rn] = value[rn] + imm; // XXX address, not actual value
    } else if ((op & 0x9F000000) == 0x10000000) {
    signed adr = ((op & 0x60000000) >> 18) | ((op & 0xFFFFE0) << 8);
    //printf("%llx: ADR X%d, 0x%llx\n", i, reg, ((long long)adr >> 11) + i);
    value[reg] = ((long long)adr >> 11) + i;
    } else if ((op & 0xFF000000) == 0x58000000) {
    unsigned adr = (op & 0xFFFFE0) >> 3;
    //printf("%llx: LDR X%d, =0x%llx\n", i, reg, adr + i);
    value[reg] = adr + i; // XXX address, not actual value
    } else if ((op & 0xF9C00000) == 0xb9400000) {
    unsigned rn = (op >> 5) & 0x1F;
    unsigned imm = ((op >> 10) & 0xFFF) << 2;
    if (!imm) continue; // XXX not counted as true xref
    value[reg] = value[rn] + imm; // XXX address, not actual value
    }
    }
    return value[which];
    }

    /* kernel iOS10 **************************************************************/

    #include <fcntl.h>
    #include <stdlib.h>
    #include <unistd.h>

    #ifndef NOT_DARWIN
    #include <mach-o/loader.h>
    #else
    #include "mach-o_loader.h"
    #endif

    #if TARGET_OS_IPHONE
    #include <mach/mach.h>
    size_t kread(uint64_t where, void *p, size_t size);
    #endif

    #ifdef VFS_H_included
    #define INVALID_HANDLE NULL
    static FHANDLE
    OPEN(const char *filename, int oflag)
    {
    // XXX use sub_reopen() to handle FAT
    return img4_reopen(file_open(filename, oflag), NULL, 0);
    }
    #define CLOSE(fd) (fd)->close(fd)
    #define READ(fd, buf, sz) (fd)->read(fd, buf, sz)
    static ssize_t
    PREAD(FHANDLE fd, void *buf, size_t count, off_t offset)
    {
    ssize_t rv;
    //off_t pos = fd->lseek(FHANDLE fd, 0, SEEK_CUR);
    fd->lseek(fd, offset, SEEK_SET);
    rv = fd->read(fd, buf, count);
    //fd->lseek(FHANDLE fd, pos, SEEK_SET);
    return rv;
    }
    #else
    #define FHANDLE int
    #define INVALID_HANDLE -1
    #define OPEN open
    #define CLOSE close
    #define READ read
    #define PREAD pread
    #endif

    #define NUM_DEADZONES 4
    struct tfp0_read_deadzone {
    addr_t start;
    addr_t end;
    };

    static uint8_t *kernel = NULL;
    static size_t kernel_size = 0;

    int
    init_kernel(addr_t kernel_base, const char *filename)
    {
    size_t rv;
    uint8_t buf[0x4000];
    unsigned i;
    const struct mach_header *hdr = (struct mach_header *)buf;
    FHANDLE fd = INVALID_HANDLE;
    const uint8_t *q;
    addr_t min = -1;
    addr_t max = 0;
    int is64 = 0;

    struct tfp0_read_deadzone deadzones[NUM_DEADZONES];
    int deadzone_idx = 0;

    #if TARGET_OS_IPHONE
    if (!kernel_base) {
    return -1;
    }
    #else /* TARGET_OS_IPHONE */
    if (!filename) {
    return -1;
    }
    #endif /* TARGET_OS_IPHONE */

    if (filename == NULL) {
    #if TARGET_OS_IPHONE
    rv = kread(kernel_base, buf, sizeof(buf));
    if (rv != sizeof(buf) || !MACHO(buf)) {
    return -1;
    }
    #else
    return -1;
    #endif
    } else {
    fd = OPEN(filename, O_RDONLY);
    if (fd == INVALID_HANDLE) {
    return -1;
    }
    rv = READ(fd, buf, sizeof(buf));
    if (rv != sizeof(buf) || !MACHO(buf)) {
    CLOSE(fd);
    return -1;
    }
    }

    if (IS64(buf)) {
    if (hdr->cputype == CPU_TYPE_ARM64 && hdr->cpusubtype == CPU_SUBTYPE_ARM64E) {
    auth_ptrs = true;
    }
    is64 = 4;
    }

    q = buf + sizeof(struct mach_header) + is64;
    for (i = 0; i < hdr->ncmds; i++) {
    const struct load_command *cmd = (struct load_command *)q;
    if (cmd->cmd == LC_SEGMENT_64) {
    const struct segment_command_64 *seg = (struct segment_command_64 *)q;
    if (min > seg->vmaddr) {
    if (seg->vmsize > 0) {
    min = seg->vmaddr;
    } else {
    // printf("dudmin: %s\n", seg->segname);
    }
    }
    if (max < seg->vmaddr + seg->vmsize) {
    if (seg->vmsize > 0) {
    max = seg->vmaddr + seg->vmsize;
    } else {
    // printf("dudmax: %s\n", seg->segname);
    }
    }
    if (!strcmp(seg->segname, "__TEXT_EXEC")) {
    xnucore_base = seg->vmaddr;
    xnucore_size = seg->filesize;
    }
    if (!strcmp(seg->segname, "__PLK_TEXT_EXEC")) {
    prelink_base = seg->vmaddr;
    prelink_size = seg->filesize;
    }
    if (!strcmp(seg->segname, "__KLD") || !strcmp(seg->segname, "__BOOTDATA") || !strcmp(seg->segname, "__PRELINK_INFO") || (!strcmp(seg->segname, "__LINKEDIT") && prelink_size == 0)) {
    deadzones[deadzone_idx].start = seg->vmaddr;
    deadzones[deadzone_idx++].end = seg->vmaddr + seg->vmsize;
    // printf("have deadzone #%d 0x%016llx - 0x%016llx\n", deadzone_idx, seg->vmaddr, seg->vmaddr + seg->vmsize);
    }
    }
    if (cmd->cmd == LC_UNIXTHREAD) {
    uint32_t *ptr = (uint32_t *)(cmd + 1);
    uint32_t flavor = ptr[0];
    struct {
    uint64_t x[29]; /* General purpose registers x0-x28 */
    uint64_t fp; /* Frame pointer x29 */
    uint64_t lr; /* Link register x30 */
    uint64_t sp; /* Stack pointer x31 */
    uint64_t pc; /* Program counter */
    uint32_t cpsr; /* Current program status register */
    } *thread = (void *)(ptr + 2);
    if (flavor == 6) {
    kernel_entry = thread->pc;
    }
    }
    q = q + cmd->cmdsize;
    }

    if (prelink_size == 0) {
    monolithic_kernel = true;
    prelink_base = xnucore_base;
    prelink_size = xnucore_size;
    }

    kerndumpbase = min;
    xnucore_base -= kerndumpbase;
    prelink_base -= kerndumpbase;
    kernel_size = max - min;

    if (filename == NULL) {
    #if TARGET_OS_IPHONE
    #define VALIDATE_KREAD(expect_size) do { if (rv != expect_size) { free(kernel); return -1; } } while(0)
    kernel = calloc(kernel_size, 1);
    if (!kernel) {
    return -1;
    }
    if (deadzone_idx != 0) {
    addr_t final_dz_end = deadzones[deadzone_idx - 1].end - kerndumpbase;
    addr_t outer_sz = deadzones[0].start - kerndumpbase;
    rv = kread(kerndumpbase, kernel, outer_sz);
    VALIDATE_KREAD(outer_sz);
    //fprintf(stderr, "breathe deeply of the poison\n");
    for (int i = 1; i < deadzone_idx; ++i) {
    addr_t adjusted_dz_s = deadzones[i].start - kerndumpbase;
    addr_t adjusted_dz_e = deadzones[i - 1].end - kerndumpbase;
    rv = kread(kerndumpbase + adjusted_dz_e, kernel + adjusted_dz_e, adjusted_dz_s - adjusted_dz_e);
    //fprintf(stderr, "breathe deeply of the poison\n");
    VALIDATE_KREAD(adjusted_dz_s - adjusted_dz_e);
    }
    outer_sz = kernel_size - final_dz_end;
    rv = kread(kerndumpbase + final_dz_end, kernel + final_dz_end, outer_sz);
    //fprintf(stderr, "we survived!\n");
    VALIDATE_KREAD(outer_sz);
    } else {
    rv = kread(kerndumpbase, kernel, kernel_size);
    VALIDATE_KREAD(kernel_size);
    }

    kernel_mh = kernel + kernel_base - min;
    #undef VALIDATE_KREAD
    #endif
    } else {
    kernel = calloc(1, kernel_size);
    if (!kernel) {
    CLOSE(fd);
    return -1;
    }

    q = buf + sizeof(struct mach_header) + is64;
    for (i = 0; i < hdr->ncmds; i++) {
    const struct load_command *cmd = (struct load_command *)q;
    if (cmd->cmd == LC_SEGMENT_64) {
    const struct segment_command_64 *seg = (struct segment_command_64 *)q;
    size_t sz = PREAD(fd, kernel + seg->vmaddr - min, seg->filesize, seg->fileoff);
    if (sz != seg->filesize) {
    CLOSE(fd);
    free(kernel);
    return -1;
    }
    if (!kernel_mh) {
    kernel_mh = kernel + seg->vmaddr - min;
    }
    if (!strcmp(seg->segname, "__LINKEDIT")) {
    kernel_delta = seg->vmaddr - min - seg->fileoff;
    }
    }
    q = q + cmd->cmdsize;
    }

    CLOSE(fd);
    }
    return 0;
    }

    void
    term_kernel(void)
    {
    if (kernel != NULL) {
    free(kernel);
    kernel = NULL;
    }
    }

    addr_t find_cs_blob_reset_cache_armv8(void)
    {
    //ldxr w9, [x8]
    //add w9, w9, #0x2
    //stxr w10, w9, [x8]

    addr_t off;
    uint32_t* k;
    k = (uint32_t*)(kernel + xnucore_base);
    for (off = 0; off < xnucore_size - 4; off += 4, k++) {
    if (k[0] == 0x885F7D09 && k[1] == 0x11000929 && k[2] == 0x880A7D09) {
    return off + xnucore_base + kerndumpbase;
    }
    }
    k = (uint32_t*)(kernel + prelink_base);
    for (off = 0; off < prelink_size - 4; off += 4, k++) {
    if (k[0] == 0x885F7D09 && k[1] == 0x11000929 && k[2] == 0x880A7D09) {
    return off + prelink_base + kerndumpbase;
    }
    }
    return 0;
    }

    addr_t find_cs_blob_reset_cache_armv81(void)
    {
    //orr w9, wzr, #0x2
    //stadd w9, [x8]
    //ret
    #define STADDINSTR 0xB829011F

    addr_t off;
    uint32_t* k;
    k = (uint32_t*)(kernel + xnucore_base);
    for (off = 0; off < xnucore_size - 4; off += 4, k++) {
    if (k[0] == 0x321F03E9 && k[1] == STADDINSTR && k[2] == 0xD65F03C0) {
    return off + xnucore_base + kerndumpbase;
    }
    }
    k = (uint32_t*)(kernel + prelink_base);
    for (off = 0; off < prelink_size - 4; off += 4, k++) {
    if (k[0] == 0x321F03E9 && k[1] == STADDINSTR && k[2] == 0xD65F03C0) {
    return off + prelink_base + kerndumpbase;
    }
    }
    return 0;
    }

    addr_t find_cs_blob_generation_count_fallback_adrpfunc(){
    // ldr x8, [x19, #0x78]
    // arbitrary (movz w20, #0x51)
    // arbitrary (cbz x8, <offset>)
    // ldr w8, [x8, #0x2c]

    addr_t off;
    uint32_t* k;
    k = (uint32_t*)(kernel + xnucore_base);
    for (off = 0; off < xnucore_size - 4; off += 4, k++) {
    if (k[0] == 0xF9403E68 && k[3] == 0xB9402D08) {
    return off + xnucore_base + kerndumpbase;
    }
    }
    k = (uint32_t*)(kernel + prelink_base);
    for (off = 0; off < prelink_size - 4; off += 4, k++) {
    if (k[0] == 0xF9403E68 && k[3] == 0xB9402D08) {
    return off + prelink_base + kerndumpbase;
    }
    }
    return 0;
    }

    addr_t find_cs_blob_generation_count_fallback(){
    addr_t adrp_func = find_cs_blob_generation_count_fallback_adrpfunc();
    if (!adrp_func){
    return 0;
    }
    addr_t adrp_ins = step64(kernel, adrp_func - kerndumpbase, 5 * 4, INSN_ADRP);
    addr_t csblob_reset_cache = calc64(kernel, adrp_ins, adrp_ins + 8, 9);
    return csblob_reset_cache + kerndumpbase;
    }

    addr_t find_cs_blob_generation_count()
    {
    addr_t func = find_cs_blob_reset_cache_armv8(); // A7 -> A10 (12.0 -> 13.5)
    if (!func)
    func = find_cs_blob_reset_cache_armv81(); // A11 -> A13 (12.0 -> 13.3)
    if (!func)
    return find_cs_blob_generation_count_fallback(); // 13.4/13.5
    addr_t load_gencount = step64_back(kernel, func - kerndumpbase, 5 * 4, INSN_ADRP);
    addr_t csblob_reset_cache = calc64(kernel, load_gencount, load_gencount + 8, 8);
    return csblob_reset_cache + kerndumpbase;
    }

    #if !TARGET_OS_IPHONE
    int
    main(int argc, char **argv)
    {
    if (argc < 2) {
    printf("Usage: patchfinder64 _decompressed_kernel_image_\n");
    printf("iOS ARM64 kernel patchfinder\n");
    exit(EXIT_FAILURE);
    }
    addr_t kernel_base = 0;
    if (init_kernel(kernel_base, argv[1]) != 0) {
    printf("Failed to prepare kernel\n");
    exit(EXIT_FAILURE);
    }
    printf("cs_blob_generation_count: 0x%llx\n", find_cs_blob_generation_count());
    printf("find_cs_blob_generation_count_fallback: 0x%llx\n", find_cs_blob_generation_count_fallback());
    return 0;
    }
    #endif
    9 changes: 9 additions & 0 deletions patchfinder64.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    #ifndef PATCHFINDER64_H_
    #define PATCHFINDER64_H_

    int init_kernel(uint64_t kernel_base, const char *filename);
    void term_kernel(void);

    uint64_t find_cs_blob_generation_count();

    #endif