diff --git a/checkra1n/kpf/kpf.h b/checkra1n/kpf/kpf.h index d329db0c..38754eb6 100644 --- a/checkra1n/kpf/kpf.h +++ b/checkra1n/kpf/kpf.h @@ -59,15 +59,16 @@ typedef const struct void (*patch)(xnu_pf_patchset_t *patchset); // NULL = end of list } kpf_patch_t; -// Order of invocations: init, shc_size, patches, shc_emit, finish. -// Both init and finish may be NULL independently. +// Order of invocations: init, shc_size, patches, shc_emit, finish, bootprep. +// All of init, finish and bootprep may be NULL independently. // shc_size and shc_emit must either both be NULL or non-NULL. // shc_size returns the maximum number of instructions to be emitted. // shc_emit returns the actual number of instructions that were emitted. typedef const struct { void (*init)(struct mach_header_64 *hdr, xnu_pf_range_t *cstring, checkrain_option_t kpf_flags, checkrain_option_t checkra1n_flags); // Flags are input only - void (*finish)(struct mach_header_64 *hdr, checkrain_option_t *checkra1n_flags); // Flags are output only + void (*finish)(struct mach_header_64 *hdr, checkrain_option_t *checkra1n_flags); // Flags are to be treated as output only + void (*bootprep)(struct mach_header_64 *hdr, checkrain_option_t checkra1n_flags); // Flags are input only uint32_t (*shc_size)(void); uint32_t (*shc_emit)(uint32_t *shellcode_area); kpf_patch_t patches[]; @@ -117,6 +118,7 @@ extern kpf_component_t kpf_launch_constraints; extern kpf_component_t kpf_mach_port; extern kpf_component_t kpf_nvram; extern kpf_component_t kpf_overlay; +extern kpf_component_t kpf_ramdisk; extern kpf_component_t kpf_trustcache; extern kpf_component_t kpf_vfs; extern kpf_component_t kpf_vm_prot; diff --git a/checkra1n/kpf/main.c b/checkra1n/kpf/main.c index c4783abd..a9fc73be 100644 --- a/checkra1n/kpf/main.c +++ b/checkra1n/kpf/main.c @@ -1341,39 +1341,6 @@ void kpf_sandbox_kext_patches(xnu_pf_patchset_t* patchset) { xnu_pf_maskmatch(patchset, "vnode_lookup", matches, masks, sizeof(masks)/sizeof(uint64_t), true, (void*)vnode_lookup_callback); } -bool kpf_md0_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream) -{ - // Find: cmp wN, 0x64 - uint32_t *cmp = find_next_insn(opcode_stream, 10, 0x7101901f, 0xfffffc1f); - if(!cmp) - { - return false; - } - // Change first cmp to short-circuit - *opcode_stream = (*opcode_stream & 0xffc003ff) | (0x64 << 10); - return true; -} - -void kpf_md0_patches(xnu_pf_patchset_t* patchset) { - // This patch turns all md0 checks in kexts into dd0 checks so that they don't think we're restoring. - // For that we search for the sequence below (example from i7 13.3): - // 0xfffffff00617fa98 1fb50171 cmp w8, 0x6d - // 0xfffffff00617fa9c 21010054 b.ne 0xfffffff00617fac0 - // 0xfffffff00617faa0 e8274039 ldrb w8, [sp, 9] - // 0xfffffff00617faa4 1f910171 cmp w8, 0x64 - // 0xfffffff00617faa8 c1000054 b.ne 0xfffffff00617fac0 - - // We can only match the first "cmp" here, because there can be - // a varying number of instructions between the two "cmp"s. - uint64_t matches[] = { - 0x7101b41f, // cmp wN, 0x6d - }; - uint64_t masks[] = { - 0xfffffc1f, - }; - xnu_pf_maskmatch(patchset, "md0_patch", matches, masks, sizeof(matches)/sizeof(uint64_t), true, (void*)kpf_md0_callback); -} - bool vnop_rootvp_auth_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream) { // cmp xN, xM - wrong match if((opcode_stream[2] & 0xffe0ffe0) == 0xeb000300) @@ -1558,6 +1525,7 @@ static void kpf_cmd(const char *cmd, char *args) &kpf_nvram, &kpf_shellcode, &kpf_overlay, + &kpf_ramdisk, &kpf_trustcache, &kpf_vfs, &kpf_vm_prot, @@ -1734,13 +1702,6 @@ static void kpf_cmd(const char *cmd, char *args) // TODO //struct mach_header_64* accessory_header = xnu_pf_get_kext_header(hdr, "com.apple.iokit.IOAccessoryManager"); - xnu_pf_patchset_t* kext_text_exec_patchset = xnu_pf_patchset_create(XNU_PF_ACCESS_32BIT); - kpf_md0_patches(kext_text_exec_patchset); - xnu_pf_emit(kext_text_exec_patchset); - xnu_pf_apply_each_kext(hdr, kext_text_exec_patchset); - xnu_pf_patchset_destroy(kext_text_exec_patchset); - - xnu_pf_range_t* text_exec_range = xnu_pf_section(hdr, "__TEXT_EXEC", "__text"); struct mach_header_64* first_kext = xnu_pf_get_first_kext(hdr); if (first_kext) { @@ -1928,26 +1889,19 @@ static void kpf_cmd(const char *cmd, char *args) } } - struct kerninfo *info = NULL; - if (ramdisk_buf) { - puts("KPF: Found ramdisk, appending kernelinfo"); - - // XXX: Why 0x10000? - ramdisk_buf = realloc(ramdisk_buf, ramdisk_size + 0x10000); - info = (struct kerninfo*)(ramdisk_buf+ramdisk_size); - bzero(info, sizeof(struct kerninfo)); - - *(uint32_t*)(ramdisk_buf) = ramdisk_size; - ramdisk_size += 0x10000; - } - if (info) { - info->size = sizeof(struct kerninfo); - info->base = xnu_slide_value(hdr) + 0xFFFFFFF007004000ULL; - info->slide = xnu_slide_value(hdr); - info->flags = checkra1n_flags; + for(size_t i = 0; i < sizeof(kpf_components)/sizeof(kpf_components[0]); ++i) + { + if(kpf_components[i]->bootprep) + { + kpf_components[i]->bootprep(hdr, checkra1n_flags); + } } - if (checkrain_option_enabled(kpf_flags, checkrain_option_verbose_boot)) + + if(checkrain_option_enabled(kpf_flags, checkrain_option_verbose_boot)) + { gBootArgs->Video.v_display = 0; + } + tick_1 = get_ticks(); printf("KPF: Applied patchset in %llu ms\n", (tick_1 - tick_0) / TICKS_IN_1MS); } diff --git a/checkra1n/kpf/ramdisk.c b/checkra1n/kpf/ramdisk.c new file mode 100644 index 00000000..39695f23 --- /dev/null +++ b/checkra1n/kpf/ramdisk.c @@ -0,0 +1,193 @@ +/* + * pongoOS - https://checkra.in + * + * Copyright (C) 2019-2023 checkra1n team + * + * This file is part of pongoOS. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include "kpf.h" +#include +#include +#include +#include +#include +#include +#include + +static bool have_ramdisk = false; +static char *rootdev_bootarg = NULL; +static uint32_t *rootdev_patchpoint = NULL; + +static bool kpf_rootdev_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream) +{ + uint32_t adrp = opcode_stream[0], + add = opcode_stream[1]; + const char *str = (const char *)(((uint64_t)(opcode_stream) & ~0xfffULL) + adrp_off(adrp) + ((add >> 10) & 0xfff)); + if(strcmp(str, "rootdev") != 0) + { + return false; + } + + // Make sure this is the correct match + uint32_t *bl = find_next_insn(opcode_stream + 2, 6, 0x94000000, 0xfc000000); + if(!bl || (bl[1] & 0xff00001f) != 0x35000000 || (bl[2] & 0xfffffe1f) != 0x3900021f) // cbnz w0, ...; strb wzr, [x{16-31}] + { + return false; + } + + if(rootdev_patchpoint) + { + panic("kpf_rootdev: Found twice"); + } + rootdev_patchpoint = opcode_stream; + + puts("KPF: Found rootdev"); + return true; +} + +static void kpf_rootdev_patch(xnu_pf_patchset_t *xnu_text_exec_patchset) +{ + // A ton of kexts check for "rd=md*" and "rootdev=md*" in order to determine whether we're restoring. + // We previously tried to patch all of those, but that is really tedious to do, and it's basically + // impossible to determine whether you found all instances. + // What we do now is just change the place that actually boots off the ramdisk from "rootdev" to "nootdev", + // and then patch the boot-args string to reflect that. + // + // Because codegen orders function args differently across versions and may or may not inline stuff, + // we just match adrp+add to either x0 or x1, and check the string and the rest in the callback. + // + // /x 0000009000000091:1e00009fde03c0ff + uint64_t matches[] = + { + 0x90000000, // adrp x{0|1}, 0x... + 0x91000000, // add x{0|1}, x{0|1}, 0x... + }; + uint64_t masks[] = + { + 0x9f00001e, + 0xffc003de, + }; + xnu_pf_maskmatch(xnu_text_exec_patchset, "rootdev", matches, masks, sizeof(matches)/sizeof(uint64_t), true, (void*)kpf_rootdev_callback); +} + +static void kpf_ramdisk_patches(xnu_pf_patchset_t *xnu_text_exec_patchset) +{ + if(have_ramdisk) + { + kpf_rootdev_patch(xnu_text_exec_patchset); + } +} + +static void kpf_ramdisk_init(struct mach_header_64 *hdr, xnu_pf_range_t *cstring, checkrain_option_t kpf_flags, checkrain_option_t checkra1n_flags) +{ + char *bootargs = (char*)((uintptr_t)gBootArgs->iOS13.CommandLine - 0x800000000 + kCacheableView); + rootdev_bootarg = strstr(bootargs, "rootdev="); + if(rootdev_bootarg > bootargs && rootdev_bootarg[-1] != ' ' && rootdev_bootarg[-1] != '\t') + { + rootdev_bootarg = NULL; + } +#ifdef DEV_BUILD + have_ramdisk = true; +#else + have_ramdisk = rootdev_bootarg && rootdev_bootarg[8] == 'm' && rootdev_bootarg[9] == 'd'; +#endif +} + +static void kpf_ramdisk_bootprep(struct mach_header_64 *hdr, checkrain_option_t checkra1n_flags) +{ + if(rootdev_bootarg) + { + rootdev_bootarg[0] = 'n'; // rootdev -> nootdev + } + + if(ramdisk_size) + { + puts("KPF: Found ramdisk, appending kerninfo"); + uint64_t slide = xnu_slide_value(hdr); + + ramdisk_buf = realloc(ramdisk_buf, ramdisk_size + sizeof(struct kerninfo)); + if(!ramdisk_buf) + { + panic("Failed to reallocate ramdisk with kerninfo"); + } + + *(struct kerninfo*)(ramdisk_buf + ramdisk_size) = (struct kerninfo) + { + .size = sizeof(struct kerninfo), + .base = slide + 0xfffffff007004000, + .slide = slide, + .flags = checkra1n_flags, + }; + + *(uint32_t*)(ramdisk_buf) = ramdisk_size; + ramdisk_size += sizeof(struct kerninfo); + } +} + +static uint32_t kpf_ramdisk_size(void) +{ + if(!have_ramdisk) + { + return 0; + } + return 2; +} + +static uint32_t kpf_ramdisk_emit(uint32_t *shellcode_area) +{ + if(!have_ramdisk) + { + return 0; + } + + // We emit a new string because it's possible that strings have + // been merged with kexts, and we don't wanna patch those. + const char str[] = "nootdev"; + memcpy(shellcode_area, str, sizeof(str)); + + uint64_t shellcode_addr = xnu_ptr_to_va(shellcode_area); + uint64_t patchpoint_addr = xnu_ptr_to_va(rootdev_patchpoint); + + uint64_t shellcode_page = shellcode_addr & ~0xfffULL; + uint64_t patchpoint_page = patchpoint_addr & ~0xfffULL; + + int64_t pagediff = (shellcode_page - patchpoint_page) >> 12; + + rootdev_patchpoint[0] = (rootdev_patchpoint[0] & 0x9f00001f) | ((pagediff & 0x3) << 29) | (((pagediff >> 2) & 0x7ffff) << 5); + rootdev_patchpoint[1] = (rootdev_patchpoint[1] & 0xffc003ff) | ((shellcode_addr & 0xfff) << 10); + + return 2; +} + +kpf_component_t kpf_ramdisk = +{ + .init = kpf_ramdisk_init, + .bootprep = kpf_ramdisk_bootprep, + .shc_size = kpf_ramdisk_size, + .shc_emit = kpf_ramdisk_emit, + .patches = + { + { NULL, "__TEXT_EXEC", "__text", XNU_PF_ACCESS_32BIT, kpf_ramdisk_patches }, + {}, + }, +}; diff --git a/src/drivers/xnu/xnu.c b/src/drivers/xnu/xnu.c index bd0898ed..eb78c7a0 100644 --- a/src/drivers/xnu/xnu.c +++ b/src/drivers/xnu/xnu.c @@ -1130,7 +1130,8 @@ void xnu_boot(void) gBootArgs->topOfKernelData = gTopOfKernelData; } -void xnu_init(void) { +void xnu_init(void) +{ command_register("xargs", "prints or sets xnu boot-args", pongo_boot_xargs); //command_register("loadx", "loads xnu", pongo_copy_xnu); command_register("bootx", "boots xnu (patched, if such a module is loaded)", pongo_boot_hook); @@ -1139,24 +1140,31 @@ void xnu_init(void) { command_register("xfb", "gives xnu access to the framebuffer (for -v or -s)", flip_video_display); } -void xnu_hook(void) { - if (preboot_hook) preboot_hook(); +void xnu_hook(void) +{ + if(preboot_hook) + { + preboot_hook(); + } } -void xnu_loadrd(void) { - if (ramdisk_size) { - dt_node_t* memory_map = (dt_node_t*)dt_find(gDeviceTree, "memory-map"); - if (!memory_map) panic("invalid devicetree: no memory_map!"); - struct memmap* map = dt_alloc_memmap(memory_map, "RAMDisk"); - if (!map) panic("invalid devicetree: dt_alloc_memmap failed"); +void xnu_loadrd(void) +{ + if(ramdisk_size) + { + dt_node_t *memory_map = dt_node(gDeviceTree, "/chosen/memory-map"); + struct memmap *map = dt_alloc_memmap(memory_map, "RAMDisk"); + if(!map) + { + panic("Failed to allocate RAMDisk memory map"); + } + + uint32_t rd_static_size = (ramdisk_size + 0xfff) & ~0xfffULL; + void *rd_static_buf = alloc_static(rd_static_size); + iprintf("allocated static region for rdsk: %p, sz: 0x%x\n", rd_static_buf, rd_static_size); - void* rd_static_buf = alloc_static(ramdisk_size); - iprintf("allocated static region for rdsk: %p, sz: %x\n", rd_static_buf, ramdisk_size); memcpy(rd_static_buf, ramdisk_buf, ramdisk_size); - - struct memmap md0map; - md0map.addr = ((uint64_t)rd_static_buf) + 0x800000000 - kCacheableView; - md0map.size = ramdisk_size; - memcpy(map, &md0map, 0x10); + map->addr = ((uint64_t)rd_static_buf) + 0x800000000 - kCacheableView; + map->size = rd_static_size; } }