Skip to content

Instantly share code, notes, and snippets.

@coolstar
Created July 7, 2020 22:21
Show Gist options
  • Save coolstar/8afb88ebe4a83b51ae522b75d93eadd9 to your computer and use it in GitHub Desktop.
Save coolstar/8afb88ebe4a83b51ae522b75d93eadd9 to your computer and use it in GitHub Desktop.
patchfinder64 from Odyssey
//
// 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
#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
@rb5933
Copy link

rb5933 commented Dec 3, 2020

#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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment