From c31b0ba7724277355b6c6d832a865f729f6d91fd Mon Sep 17 00:00:00 2001 From: Nick Chan Date: Mon, 4 Mar 2024 20:31:01 +0800 Subject: [PATCH] old style static binary patch --- checkra1n/kpf/main.c | 91 +++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/checkra1n/kpf/main.c b/checkra1n/kpf/main.c index 1f422284..d8f412d1 100644 --- a/checkra1n/kpf/main.c +++ b/checkra1n/kpf/main.c @@ -2019,31 +2019,61 @@ bool static_binaries_callback(struct xnu_pf_patch* patch, uint32_t* opcode_strea if (found_static_binaries) { panic("KPF: parse_machfile: Found twice!"); } - uint32_t* bne = find_next_insn(opcode_stream+4, 3, 0x54000001, 0xff00001f); + uint32_t* bne = find_next_insn(opcode_stream+4, 3, 0x54000001, 0xff00001f); // b.ne if (!bne) return false; *bne = NOP; - puts("KPF: Found static binaries"); + puts("KPF: Found parse_machfile"); found_static_binaries = true; return true; } -/* XXX: Doesn't work like this, very complicated to have it work correctly */ -#if 0 +// The older style is more complicated bool static_binaries_old_callback(struct xnu_pf_patch* patch, uint32_t* opcode_stream) { - uint32_t tbz_offset = (opcode_stream[1] >> 5) & 0x3fff; - uint32_t *tbz_stream = opcode_stream + 1 + tbz_offset; + DEVLOG("found parse_machfile candidate @ 0x%llx", xnu_ptr_to_va(opcode_stream)); + if (found_static_binaries) { + panic("KPF: parse_machfile: Found twice!"); + } + // Step 1: Find the check for MH_DYLINKER + uint32_t* cmp = find_prev_insn(opcode_stream, 0x10, 0x71001c1f, 0xfffffc1f); // cmp wN, #0x7 + if ((cmp[1] & 0xff00001f) != 0x54000000) return false; /* b.eq */ + uint32_t* dylinker_stream = &cmp[1] + sxt32(cmp[1] >> 5, 19); + + DEVLOG("parse_machfile: Found MH_DYLINKER check @ 0x%llx", xnu_ptr_to_va(dylinker_stream)); + + // Step 2: Make sure this is the depth == 2 check if ( - (tbz_stream[0] & 0xfffffff0) != 0x321e03f0 || - (tbz_stream[1] & 0xfc000000 != 0x14000000) + (dylinker_stream[0] & 0xfffffc1f) != 0x7100081f || /* cmp wN, #0x2 */ + (dylinker_stream[1] & 0xff00001f) != 0x54000001 /* b.ne */ ) return false; - tbz_stream[0] = NOP; - tbz_stream[1] = NOP; + DEVLOG("parse_machfile: Found depth==2 check"); + + // Step 3: We skip over the dylinker-specific code path and find the call to common code + uint32_t* cbz = find_next_insn(&dylinker_stream[3], 3, 0xb4000000, 0xff000000); // cbz + uint32_t* common_stream = cbz + 1; + + // Step 4: Actually patch the check for static binary to point to the common code + if ((opcode_stream[0] & 0xfff80010) == 0x36100000) { // tbz + DEVLOG("tbz old: 0x%x", opcode_stream[0]); + *opcode_stream = (opcode_stream[0] & 0xfff8001f) | (((common_stream - opcode_stream) & 0x3fff) << 5); // uint32 takes care of >> 2 + DEVLOG("tbz new: 0x%x", opcode_stream[0]); + } else if ((opcode_stream[0] & 0xffffffe0) == 0x52800080) { // mov + uint32_t* load_failure = &opcode_stream[3] + sxt32(opcode_stream[3] >> 5, 19); + uint8_t wM = (opcode_stream[2] >> 16) & 0x1f; + // Allow (header->flags & MH_DYLINKER) == 0 + opcode_stream[2] = 0x36100000 | ((common_stream - &opcode_stream[2]) & 0x3fff) << 5 | wM; /* tbz wM, #0x2, common_stream */ + // Disallow (header->flags & MH_PIE) == 0 in dynamic executables + opcode_stream[3] = 0x36a80000 | ((load_failure - &opcode_stream[3]) & 0x3fff) << 5 | wM; /* tbz wM, #0x15, load_failure */ + DEVLOG("static_binaries_old_alt_callback: new checks: 0x%x, 0x%x", opcode_stream[2], opcode_stream[3]); + } else panic("static_binaries_old_callback: unreachable"); + + puts("KPF: Found parse_machfile"); + found_static_binaries = true; + return true; } -#endif void kpf_static_binaries_patch(xnu_pf_patchset_t* patchset) { - // we match the part where it allows x86_64 static binaries + // Match the part where it allows x86_64 static binaries uint64_t matches[] = { 0x37100008, // tbnz w8, #0x2, ... 0x528000e8, // mov w8, #0x7 @@ -2060,23 +2090,38 @@ void kpf_static_binaries_patch(xnu_pf_patchset_t* patchset) { xnu_pf_maskmatch(patchset, "parse_machfile", matches, masks, sizeof(matches)/sizeof(uint64_t), false, (void*)static_binaries_callback); -#if 0 + // Match checks for MH_DYLINKER & MH_PIE + // Case 1: tb(n)z for control flow uint64_t matches_old[] = { - 0x36100008, - 0x37a80008, - 0x52800188, - 0x72a00008 + 0x36100000, // tbz w{0-15}, #0x2, ... + 0x37a80000, // tbnz w{0-15}, #0x15, ... + 0x52800180, // mov w{0-15}, #0xc + 0x72a02000 // movk w{0-15}, #0x100, lsl 16 }; uint64_t masks_old[] = { - 0xfff8001f, - 0xfff8001f, - 0xffffffff, - 0xffe0001f + 0xfff80010, + 0xfff80010, + 0xfffffff0, + 0xfffffff0 }; xnu_pf_maskmatch(patchset, "parse_machfile", matches_old, masks_old, sizeof(matches_old)/sizeof(uint64_t), false, (void*)static_binaries_old_callback); -#endif + // Case 2: bics and b.ne for control flow + uint64_t matches_old_alt[] = { + 0x52800080, // mov wN, #0x4 + 0x72a00400, // mov wN, #0x20, lsl 16 + 0x6a20001f, // bics wzr, wN, wM + 0x54000001 // b.ne + }; + + uint64_t masks_old_alt[] = { + 0xffffffe0, + 0xffffffe0, + 0xffe0fc1f, + 0xff00001f + }; + xnu_pf_maskmatch(patchset, "parse_machfile", matches_old_alt, masks_old_alt, sizeof(matches_old_alt)/sizeof(uint64_t), false, (void*)static_binaries_old_callback); } static uint32_t shellcode_count; @@ -2498,7 +2543,7 @@ static void kpf_cmd(const char *cmd, char *args) puts("Missing patch: apfs_vfsop_mount"); } } - if (!found_static_binaries && rootvp_string_match != NULL) { + if (!found_static_binaries) { panic("Missing patch: parse_machfile"); }