Skip to content

Commit

Permalink
New ramdisk/md0/rootdev patch
Browse files Browse the repository at this point in the history
  • Loading branch information
Siguza committed Jun 11, 2023
1 parent 2db9357 commit 8545f97
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 77 deletions.
8 changes: 5 additions & 3 deletions checkra1n/kpf/kpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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;
Expand Down
70 changes: 12 additions & 58 deletions checkra1n/kpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down
193 changes: 193 additions & 0 deletions checkra1n/kpf/ramdisk.c
Original file line number Diff line number Diff line change
@@ -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 <kerninfo.h>
#include <pongo.h>
#include <xnu/xnu.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

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 },
{},
},
};
40 changes: 24 additions & 16 deletions src/drivers/xnu/xnu.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
}

0 comments on commit 8545f97

Please sign in to comment.