diff options
author | comex | 2015-02-28 13:16:36 -0500 |
---|---|---|
committer | comex | 2015-02-28 13:16:36 -0500 |
commit | d9a7a8a4d4a23fb65e6319e0e8a435046cc39fea (patch) | |
tree | dd219509fc92e695317b82d5dca43296daaf2ab1 /darwin-bootstrap | |
parent | remove deprecated property usage in safety-dance (diff) | |
download | substitute-d9a7a8a4d4a23fb65e6319e0e8a435046cc39fea.tar.gz |
Rename ios-bootstrap to darwin-bootstrap; cleanup posixspawn-hook and unrestrict.
Not tested yet.
Diffstat (limited to 'darwin-bootstrap')
-rw-r--r-- | darwin-bootstrap/bundle-loader.m | 161 | ||||
-rw-r--r-- | darwin-bootstrap/ib-log.h | 27 | ||||
-rw-r--r-- | darwin-bootstrap/inject-into-launchd.c | 93 | ||||
-rw-r--r-- | darwin-bootstrap/posixspawn-hook.c | 399 | ||||
-rw-r--r-- | darwin-bootstrap/safety-dance/AutoGrid.h | 17 | ||||
-rw-r--r-- | darwin-bootstrap/safety-dance/AutoGrid.m | 106 | ||||
-rw-r--r-- | darwin-bootstrap/safety-dance/Info.plist | 88 | ||||
-rw-r--r-- | darwin-bootstrap/safety-dance/main.m | 159 | ||||
-rw-r--r-- | darwin-bootstrap/safety-dance/white.png | bin | 0 -> 1047 bytes | |||
-rw-r--r-- | darwin-bootstrap/unrestrict.c | 245 |
10 files changed, 1295 insertions, 0 deletions
diff --git a/darwin-bootstrap/bundle-loader.m b/darwin-bootstrap/bundle-loader.m new file mode 100644 index 0000000..fec2513 --- /dev/null +++ b/darwin-bootstrap/bundle-loader.m @@ -0,0 +1,161 @@ +#import <Foundation/Foundation.h> +#import <CoreFoundation/CoreFoundation.h> +#include <dlfcn.h> +extern char ***_NSGetArgv(void); + +#define PREFIX "Substitute bundle loader: " + +enum test_filters_ret { + PASSED, + FAILED, + INVALID +}; + +static double id_to_double(id o) { + if ([o isKindOfClass:[NSString class]]) { + NSScanner *scan = [NSScanner scannerWithString:o]; + double d; + if (![scan scanDouble:&d] || !scan.atEnd) + return NAN; + return d; + } else if ([o isKindOfClass:[NSNumber class]]) { + return [o doubleValue]; + } else { + return NAN; + } +} + +static enum test_filters_ret test_filters(NSDictionary *plist_dict) { + + NSDictionary *filter = [plist_dict objectForKey:@"Filter"]; + if (!filter) + return PASSED; + if (![filter isKindOfClass:[NSDictionary class]]) + return INVALID; + + for (NSString *key in [filter allKeys]) { + if (!([key isEqualToString:@"CoreFoundationVersion"] || + [key isEqualToString:@"Classes"] || + [key isEqualToString:@"Bundles"] || + [key isEqualToString:@"Executables"])) { + return INVALID; + } + } + NSArray *cfv = [filter objectForKey:@"CoreFoundationVersion"]; + if (cfv) { + if (![cfv isKindOfClass:[NSArray class]] || + [cfv count] == 0 || + [cfv count] > 2) + return INVALID; + double version = kCFCoreFoundationVersionNumber; + double minimum = id_to_double([cfv objectAtIndex:0]); + if (minimum != minimum) + return INVALID; + if (version < minimum) + return FAILED; + id supremum_o = [cfv objectAtIndex:1]; + if (supremum_o) { + double supremum = id_to_double(supremum_o); + if (supremum != supremum) + return INVALID; + if (version >= supremum) + return FAILED; + } + } + + NSArray *classes = [filter objectForKey:@"Classes"]; + if (classes) { + if (![classes isKindOfClass:[NSArray class]]) + return INVALID; + for (NSString *name in classes) { + if (![name isKindOfClass:[NSString class]]) + return INVALID; + if (NSClassFromString(name)) + goto ok1; + } + return FAILED; + ok1:; + } + + NSArray *bundles = [filter objectForKey:@"Bundles"]; + if (bundles) { + if (![bundles isKindOfClass:[NSArray class]]) + return INVALID; + for (NSString *identifier in bundles) { + if (![identifier isKindOfClass:[NSString class]]) + return INVALID; + if ([NSBundle bundleWithIdentifier:identifier]) + goto ok2; + } + return FAILED; + ok2:; + } + + + NSArray *executables = [filter objectForKey:@"Executables"]; + if (executables) { + const char *argv0 = (*_NSGetArgv())[0]; + NSString *exe = nil; + if (argv0) { + NSString *nsargv0 = [NSString stringWithCString:argv0 + encoding:NSUTF8StringEncoding]; + exe = [[nsargv0 pathComponents] lastObject]; + } + if (!exe) + exe = @""; + if (![executables isKindOfClass:[NSArray class]]) + return INVALID; + for (NSString *name in executables) { + if (![name isKindOfClass:[NSString class]]) + return INVALID; + if ([name isEqualToString:exe]) + goto ok3; + } + return FAILED; + ok3:; + } + return PASSED; +} + +/* this is DYLD_INSERT_LIBRARIES'd, not injected. */ +__attribute__((constructor)) +static void init() { + NSError *err; + NSString *base = @"/Library/Substitute/DynamicLibraries"; + NSArray *list = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:base + error:&err]; + if (!list) + return; + + for (NSString *dylib in list) { + if (![[dylib pathExtension] isEqualToString:@"dylib"]) + continue; + NSString *plist = [[dylib stringByDeletingPathExtension] + stringByAppendingPathExtension:@"plist"]; + NSString *full_plist = [base stringByAppendingPathComponent:plist]; + NSDictionary *plist_dict = [NSDictionary dictionaryWithContentsOfFile: + full_plist]; + if (!plist_dict) { + NSLog(@PREFIX "missing, unreadable, or invalid plist '%@' for dylib '%@'; unlike Substrate, we require plists", full_plist, dylib); + continue; + } + enum test_filters_ret ret = test_filters(plist_dict); + if (ret == FAILED) { + continue; + } else if (ret == INVALID) { + NSLog(@PREFIX "bad data in plist '%@' for dylib '%@'", full_plist, dylib); + continue; + } + NSString *full_dylib = [base stringByAppendingPathComponent:dylib]; + const char *c_dylib = [full_dylib cStringUsingEncoding:NSUTF8StringEncoding]; + if (!c_dylib) { + NSLog(@PREFIX "Not loading weird dylib path %@", full_dylib); + continue; + } + NSLog(@"Substitute loading %@", full_dylib); + if (!dlopen(c_dylib, RTLD_LAZY)) { + NSLog(@PREFIX "Failed to load %@: %s", full_dylib, dlerror()); + } + } +} diff --git a/darwin-bootstrap/ib-log.h b/darwin-bootstrap/ib-log.h new file mode 100644 index 0000000..fee70b5 --- /dev/null +++ b/darwin-bootstrap/ib-log.h @@ -0,0 +1,27 @@ +#pragma once +#include <dispatch/dispatch.h> +#include <stdio.h> +#include <unistd.h> + +static FILE *logfp; +static void open_logfp_if_necessary() { + /* syslog() doesn't seem to work from launchd... */ + static dispatch_once_t pred; + dispatch_once(&pred, ^{ + char filename[128]; + sprintf(filename, "/tmp/substitute-" IB_LOG_NAME "-log.%ld", + (long) getpid()); + logfp = fopen(filename, "w"); + if (!logfp) { + /* Ack... */ + logfp = stderr; + } + }); +} +#define ib_log(fmt, args...) do { \ + open_logfp_if_necessary(); \ + fprintf(logfp, fmt "\n", ##args); \ + fflush(logfp); \ +} while(0) + +#define IB_VERBOSE 0 diff --git a/darwin-bootstrap/inject-into-launchd.c b/darwin-bootstrap/inject-into-launchd.c new file mode 100644 index 0000000..539b1a2 --- /dev/null +++ b/darwin-bootstrap/inject-into-launchd.c @@ -0,0 +1,93 @@ +/* This is an iOS executable, placed in /etc/rc.d, that injects + * posixspawn-hook.dylib into launchd (pid 1). */ + +#define IB_LOG_NAME "iil" +#include "ib-log.h" +#include "substitute.h" +#include "substitute-internal.h" +#include <mach/mach.h> +#include <mach/mach_time.h> +#include <stdbool.h> +#include <stdint.h> +#include <syslog.h> +#include <CoreFoundation/CoreFoundation.h> + +void *IOHIDEventCreateKeyboardEvent(CFAllocatorRef, uint64_t, uint32_t, uint32_t, bool, uint32_t); +void *IOHIDEventSystemCreate(CFAllocatorRef); +void *IOHIDEventSystemCopyEvent(void *, uint32_t, void *, uint32_t); + +CFIndex IOHIDEventGetIntegerValue(void *, uint32_t); +enum { + kIOHIDEventTypeKeyboard = 3, + kIOHIDEventFieldKeyboardDown = 3 << 16 | 2, +}; + +static bool button_pressed(void *event_system, uint32_t usage_page, uint32_t usage) { + /* This magic comes straight from Substrate... I don't really understand + * what it's doing. In particular, where is the equivalent kernel + * implementation on OS X? Does it not exist? But I guess Substrate is + * emulating backboardd. */ + void *dummy = IOHIDEventCreateKeyboardEvent(NULL, mach_absolute_time(), + usage_page, usage, + 0, 0); + if (!dummy) { + ib_log("couldn't create dummy HID event"); + return false; + } + void *event = IOHIDEventSystemCopyEvent(event_system, + kIOHIDEventTypeKeyboard, + dummy, 0); + if (!event) + return false; + CFIndex ival = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown); + return ival; +} + +int main(UNUSED int argc, char **argv) { + pid_t pid = argv[1] ? atoi(argv[1]) : 1; /* for testing */ + + void *event_system = IOHIDEventSystemCreate(NULL); + if (!event_system) { + ib_log("couldn't create HID event system"); + } else { + + /* consumer page -> Volume Increment */ + if (button_pressed(event_system, 0x0c, 0xe9) || + /* telephony page -> Flash */ + button_pressed(event_system, 0x0b, 0x21)) { + ib_log("disabling due to button press"); + return 0; + } + } + mach_port_t port = 0; + kern_return_t kr = mach_port_allocate(mach_task_self(), + MACH_PORT_RIGHT_RECEIVE, + &port); + if (kr) { + ib_log("mach_port_allocate: %x", kr); + return 0; + } + const char *lib = "/Library/Substitute/posixspawn-hook.dylib"; + struct shuttle shuttle = { + .type = SUBSTITUTE_SHUTTLE_MACH_PORT, + .u.mach.right_type = MACH_MSG_TYPE_MAKE_SEND, + .u.mach.port = port + }; + char *error; + int ret = substitute_dlopen_in_pid(pid, lib, 0, &shuttle, 1, &error); + if (ret) { + ib_log("substitute_dlopen_in_pid: %s/%s", + substitute_strerror(ret), error); + return 0; + } + /* wait for it to finish */ + static struct { + mach_msg_header_t hdr; + mach_msg_trailer_t huh; + } msg; + kr = mach_msg_overwrite(NULL, MACH_RCV_MSG, 0, sizeof(msg), port, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, + &msg.hdr, 0); + if (kr) + ib_log("mach_msg_overwrite: %x", kr); +} diff --git a/darwin-bootstrap/posixspawn-hook.c b/darwin-bootstrap/posixspawn-hook.c new file mode 100644 index 0000000..5acb30b --- /dev/null +++ b/darwin-bootstrap/posixspawn-hook.c @@ -0,0 +1,399 @@ +/* This library is loaded into launchd, and from there into xpcproxy, which + * launchd (always?) uses as an intermediary to exec its processes; its main + * purpose is to ensure that bundle-loader.dylib is specified in + * DYLD_INSERT_LIBRARIES when launching such processes. In the interests of + * not making ssh really weird (and because it's what Substrate does), this is + * separate from bundle-loader itself, so any processes that do their own + * spawning won't get the environment override. + * + * It also handles the sandbox override for substituted. */ + +#define IB_LOG_NAME "posixspawn-hook" +#include "ib-log.h" +#include "substitute.h" +#include "substitute-internal.h" +#include <mach/mach.h> +#include <mach-o/dyld.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> +#include <spawn.h> +#include <sys/wait.h> +#include <syslog.h> +#include <malloc/malloc.h> +#include <assert.h> +#include <errno.h> +#include <arpa/inet.h> +#include <libkern/OSByteOrder.h> + +extern char ***_NSGetEnviron(void); + +static __typeof__(posix_spawn) *old_posix_spawn, *old_posix_spawnp, + hook_posix_spawn, hook_posix_spawnp; +static __typeof__(wait4) *old_wait4, hook_wait4; +static __typeof__(waitpid) *old_waitpid, hook_waitpid; + +static bool advance(char **strp, const char *template) { + size_t len = strlen(template); + if (!strncmp(*strp, template, len)) { + *strp += len; + return true; + } + return false; +} + +static bool spawn_unrestrict(pid_t pid, bool should_resume, bool is_exec) { + const char *prog = "/Library/Substitute/unrestrict"; + char pid_s[32]; + sprintf(pid_s, "%ld", (long) pid); + const char *should_resume_s = should_resume ? "1" : "0"; + const char *is_exec_s = is_exec ? "1" : "0"; + const char *argv[] = {prog, pid_s, should_resume_s, is_exec_s, NULL}; + pid_t prog_pid; + if (old_posix_spawn(&prog_pid, prog, NULL, NULL, (char **) argv, NULL)) { + ib_log("posixspawn-hook: couldn't start unrestrict - oh well..."); + return false; + } + int xstat; + /* reap intermediate to avoid zombie - if it doesn't work, not a big deal */ + if (waitpid(prog_pid, &xstat, 0) == -1) + ib_log("posixspawn-hook: couldn't waitpid"); + ib_log("unrestrict xstat=%x", xstat); + return true; +} + +static bool looks_restricted(const char *filename) { + int fd = open(filename, O_RDONLY); + if (fd == -1) { + ib_log("open '%s': %s", filename, strerror(errno)); + return false; + } + uint32_t offset = 0; + union { + uint32_t magic; + struct { + struct fat_header fh; + struct fat_arch fa1; + }; + struct mach_header mh; + } u; + if (read(fd, &u, sizeof(u)) != sizeof(u)) { + ib_log("read header for '%s': %s", filename, strerror(errno)); + return false; + } + if (ntohl(u.magic) == FAT_MAGIC) { + /* Fat binary - to avoid needing to replicate grade_binary in the + * kernel, we assume all architectures have the same restrict-ness. */ + if (u.fh.nfat_arch == 0) + return false; + offset = ntohl(u.fa1.offset); + if (pread(fd, &u, sizeof(u), offset) != sizeof(u)) { + ib_log("read header (inside fat) for '%s': %s", + filename, strerror(errno)); + return false; + } + } + bool swap, is64; + switch (u.magic) { + case MH_MAGIC: + swap = false; + is64 = false; + break; + case MH_MAGIC_64: + swap = false; + is64 = true; + break; + case MH_CIGAM: + swap = true; + is64 = false; + break; + case MH_CIGAM_64: + swap = true; + is64 = true; + break; + default: + ib_log("bad mach-o magic for '%s'", filename); + return false; + } + uint32_t sizeofcmds = u.mh.sizeofcmds; + if (swap) + sizeofcmds = OSSwapInt32(sizeofcmds); + offset += is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header); + char *cmds_buf = malloc(sizeofcmds); + ssize_t actual = pread(fd, cmds_buf, sizeofcmds, offset); + if (actual < 0 || (uint32_t) actual != sizeofcmds) { + ib_log("read load cmds for '%s': %s", filename, strerror(errno)); + free(cmds_buf); + return false; + } + /* overestimation is fine here */ + const char sectname[] = "__restrict"; + bool ret = !!memmem(cmds_buf, sizeofcmds, sectname, sizeof(sectname)); + free(cmds_buf); + return ret; +} + +static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old, + pid_t *restrict pidp, const char *restrict path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], + char *const envp[restrict]) { + char *new = NULL; + char **new_envp = NULL; + char *const *envp_to_use = envp; + char *const *my_envp = envp ? envp : *_NSGetEnviron(); + posix_spawnattr_t my_attr = NULL; + + if (attrp) { + posix_spawnattr_t attr = *attrp; + size_t size = malloc_size(attr); + my_attr = malloc(size); + if (!my_attr) + goto crap; + memcpy(my_attr, attr, size); + } else { + if (posix_spawnattr_init(&my_attr)) + goto crap; + } + + short flags; + if (posix_spawnattr_getflags(&my_attr, &flags)) + goto crap; + if (IB_VERBOSE) { + ib_log("hook_posix_spawn_generic: path=%s%s%s", + path, + (flags & POSIX_SPAWN_SETEXEC) ? " (exec)" : "", + (flags & POSIX_SPAWN_START_SUSPENDED) ? " (suspend)" : ""); + for (char *const *ap = argv; *ap; ap++) + ib_log(" %s", *ap); + } + + /* This mirrors Substrate's logic with safe mode. I don't really + * understand the point of the difference between its 'safe' (which removes + * Substrate from DYLD_INSERT_LIBRARIES) and 'quit' (which just skips + * directly to the original spawn), but I guess I'll just do the same for + * maximum safety... */ + bool safe_mode = false; + + const char *orig_dyld_insert = ""; + static const char bl_dylib[] = + "/Library/Substitute/bundle-loader.dylib"; + static const char psh_dylib[] = + "/Library/Substitute/posixspawn-hook.dylib"; + size_t env_count = 0; + for (char *const *ep = my_envp; *ep; ep++) { + env_count++; + char *env = *ep; + if (advance(&env, "_MSSafeMode=") || advance(&env, "_SubstituteSafeMode=")) { + if (!strcmp(env, "0") || !strcmp(env, "NO")) + continue; + else if (!strcmp(env, "1") || !strcmp(env, "YES")) + safe_mode = true; + else + goto skip; + } else if (advance(&env, "DYLD_INSERT_LIBRARIES=")) { + orig_dyld_insert = env; + } + } + new = malloc(sizeof("DYLD_INSERT_LIBRARIES=") - 1 + + sizeof(psh_dylib) /* not - 1, because : */ + + strlen(orig_dyld_insert) + 1); + char *newp_orig = stpcpy(new, "DYLD_INSERT_LIBRARIES="); + char *newp = newp_orig; + const char *p = orig_dyld_insert; + while (*p) { /* W.N.H. */ + const char *next = strchr(p, ':') ?: (p + strlen(p)); + /* append if it isn't one of ours */ + bool is_substitute = + (next - p == sizeof(bl_dylib) - 1 && + !memcmp(next, bl_dylib, sizeof(bl_dylib) - 1)) || + (next - p == sizeof(psh_dylib) - 1 && + !memcmp(next, psh_dylib, sizeof(psh_dylib) - 1)); + if (!is_substitute) { + if (newp != newp_orig) + *newp++ = ':'; + memcpy(newp, p, next - p); + newp += next - p; + } + if (!*next) + break; + p = next + 1; + } + /* append ours if necessary */ + if (!safe_mode) { + if (newp != newp_orig) + *newp++ = ':'; + const char *dylib_to_add = !strcmp(path, "/usr/libexec/xpcproxy") + ? psh_dylib + : bl_dylib; + newp = stpcpy(newp, dylib_to_add); + } + if (IB_VERBOSE) + ib_log("using %s", new); + /* no libraries? then just get rid of it */ + if (newp == newp_orig) { + free(new); + new = NULL; + } + new_envp = malloc(sizeof(char *) * (env_count + 2)); + envp_to_use = new_envp; + char **outp = new_envp; + for (size_t idx = 0; idx < env_count; idx++) { + char *env = my_envp[idx]; + /* remove *all* D_I_L, including duplicates */ + if (!advance(&env, "DYLD_INSERT_LIBRARIES=")) + *outp++ = env; + } + if (new) + *outp++ = new; + *outp++ = NULL; + + if (safe_mode) + goto skip; + + + /* Deal with the dumb __restrict section. A complication is that this + * could actually be an exec. */ + bool need_unrestrict = looks_restricted(path); + + /* TODO skip this if Substrate is doing it anyway */ + bool was_suspended; + if (need_unrestrict) { + was_suspended = flags & POSIX_SPAWN_START_SUSPENDED; + flags |= POSIX_SPAWN_START_SUSPENDED; + if (posix_spawnattr_setflags(&my_attr, flags)) + goto crap; + if (flags & POSIX_SPAWN_SETEXEC) { + /* make the marker fd; hope you weren't using that */ + if (dup2(2, 255) != 255) { + ib_log("dup2 failure - %s", strerror(errno)); + goto skip; + } + if (fcntl(255, F_SETFD, FD_CLOEXEC)) + goto crap; + if (IB_VERBOSE) + ib_log("spawning unrestrict"); + if (!spawn_unrestrict(getpid(), !was_suspended, true)) + goto skip; + } + } + if (IB_VERBOSE) + ib_log("**"); + int ret = old(pidp, path, file_actions, &my_attr, argv, envp_to_use); + if (IB_VERBOSE) + ib_log("ret=%d pid=%ld", ret, (long) *pidp); + if (ret) + goto cleanup; + /* Since it returned, obviously it was not SETEXEC, so we need to + * unrestrict it ourself. */ + pid_t pid = *pidp; + if (need_unrestrict) + spawn_unrestrict(pid, !was_suspended, false); + goto cleanup; +crap: + ib_log("posixspawn-hook: weird error - OOM? skipping our stuff"); +skip: + ret = old(pidp, path, file_actions, attrp, argv, envp); +cleanup: + free(new_envp); + free(new); + free(my_attr); + return ret; +} + +static void after_wait_generic(pid_t pid, int stat) { + /* TODO safety */ + (void) pid; + (void) stat; +} + +int hook_posix_spawn(pid_t *restrict pid, const char *restrict path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], char *const envp[restrict]) { + return hook_posix_spawn_generic(old_posix_spawn, pid, path, file_actions, + attrp, argv, envp); +} + +int hook_posix_spawnp(pid_t *restrict pid, const char *restrict path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], char *const envp[restrict]) { + return hook_posix_spawn_generic(old_posix_spawnp, pid, path, file_actions, + attrp, argv, envp); +} + +pid_t hook_wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage) { + pid_t ret = old_wait4(pid, stat_loc, options, rusage); + after_wait_generic(ret, *stat_loc); + return ret; +} + +pid_t hook_waitpid(pid_t pid, int *stat_loc, int options) { + pid_t ret = old_waitpid(pid, stat_loc, options); + after_wait_generic(ret, *stat_loc); + return ret; +} + +void substitute_init(struct shuttle *shuttle, size_t nshuttle) { + /* Just tell them we're done */ + if (nshuttle != 1) { + ib_log("nshuttle = %zd?", nshuttle); + return; + } + mach_port_t notify_port = shuttle[0].u.mach.port; + mach_msg_header_t done_hdr; + done_hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, 0); + done_hdr.msgh_size = sizeof(done_hdr); + done_hdr.msgh_remote_port = notify_port; + done_hdr.msgh_local_port = 0; + done_hdr.msgh_voucher_port = 0; + done_hdr.msgh_id = 42; + kern_return_t kr = mach_msg_send(&done_hdr); + if (kr) + ib_log("posixspawn-hook: mach_msg_send failed: kr=%x", kr); + /* MOVE deallocated the port */ +} + +__attribute__((constructor)) +static void init() { + /* Note: I'm using interposing to minimize the chance of conflict with + * Substrate. This shouldn't actually be necessary, because MSHookProcess, + * at least as of the old version I'm looking at the source code of, blocks + * until the thread it remotely creates exits, and that thread does + * pthread_join on the 'real' pthread it creates to do the dlopen (unlike + * the equivalent in Substitute - the difference is to decrease dependence + * on pthread internals). substitute_dlopen_in_pid does not, but that's + * what the notify port is for. Meanwhile, the jailbreak I have installed + * properly runs rc.d sequentially, so the injection tools won't do their + * thing at the same time. But just in case any of that doesn't hold up... + * + * (it also decreases the amount of library code necessary to load from + * disk...) + */ + + struct substitute_image *im = substitute_open_image(_dyld_get_image_name(0)); + if (!im) { + ib_log("posixspawn-hook: substitute_open_image failed"); + goto end; + } + + static const struct substitute_import_hook hooks[] = { + {"_posix_spawn", hook_posix_spawn, &old_posix_spawn}, + {"_posix_spawnp", hook_posix_spawnp, &old_posix_spawnp}, + {"_waitpid", hook_waitpid, &old_waitpid}, + {"_wait4", hook_wait4, &old_wait4}, + }; + + int err = substitute_interpose_imports(im, hooks, sizeof(hooks)/sizeof(*hooks), 0); + if (err) { + ib_log("posixspawn-hook: substitute_interpose_imports failed: %s", + substitute_strerror(err)); + goto end; + } + +end: + if (im) + substitute_close_image(im); + +} diff --git a/darwin-bootstrap/safety-dance/AutoGrid.h b/darwin-bootstrap/safety-dance/AutoGrid.h new file mode 100644 index 0000000..335381a --- /dev/null +++ b/darwin-bootstrap/safety-dance/AutoGrid.h @@ -0,0 +1,17 @@ +// +// AutoGrid.h +// SafetyDance +// +// Created by Nicholas Allegra on 1/26/15. +// Copyright (c) 2015 Nicholas Allegra. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@interface AutoGrid : UIView { + NSArray *views; + UIScrollView *scrollView; +} +- (void)setViews:(NSArray *)views; + +@end diff --git a/darwin-bootstrap/safety-dance/AutoGrid.m b/darwin-bootstrap/safety-dance/AutoGrid.m new file mode 100644 index 0000000..2a8d3ed --- /dev/null +++ b/darwin-bootstrap/safety-dance/AutoGrid.m @@ -0,0 +1,106 @@ +// +// AutoGrid.m +// SafetyDance +// +// Created by Nicholas Allegra on 1/26/15. +// Copyright (c) 2015 Nicholas Allegra. All rights reserved. +// + +#import "AutoGrid.h" + +@implementation AutoGrid +- (void)setViews:(NSArray *)_views { + views = _views; + [scrollView removeFromSuperview]; + scrollView = [[UIScrollView alloc] init]; + [self addSubview:scrollView]; + for (UIView *view in views) + [scrollView addSubview:view]; + [self setNeedsLayout]; +} + +- (void)layoutSubviews { + scrollView.frame = self.bounds; + CGFloat paddingX = 22, paddingY = 10; + NSUInteger nviews = [views count]; + CGSize *sizes = malloc(sizeof(*sizes) * nviews); + + for (NSUInteger i = 0; i < nviews; i++) + sizes[i] = [[views objectAtIndex:i] intrinsicContentSize]; + + CGFloat availableWidth = self.bounds.size.width; + /* try to lay out using an increasing number of columns */ + NSUInteger cols; + CGSize contentSize; + CGFloat *colWidths = NULL; + for (cols = 1; ; cols++) { + free(colWidths); + colWidths = malloc(sizeof(*colWidths) * cols); + for (NSUInteger col = 0; col < cols; col++) + colWidths[col] = 0; + CGFloat tentativeHeight = 0; + CGFloat tentativeWidth = 0; + for (NSUInteger row = 0; row < nviews / cols; row++) { + CGFloat totalWidth = 0; + CGFloat maxHeight = 0; + for (NSUInteger col = 0; col < cols; col++) { + NSUInteger i = row * cols + col; + if (i >= nviews) + goto done1; + CGSize size = sizes[i]; + if (size.width > colWidths[col]) + colWidths[col] = size.width; + if (col != 0) + totalWidth += paddingX; + totalWidth += size.width; + if (size.height > maxHeight) + maxHeight = size.height; + } + if (totalWidth > tentativeWidth) + tentativeWidth = totalWidth; + tentativeHeight += maxHeight + paddingY; + } + done1: + if (cols > 1 && tentativeWidth > availableWidth) { + cols--; + break; + } + contentSize = CGSizeMake(tentativeWidth, tentativeHeight); + NSLog(@"%f", contentSize.height); + if (contentSize.width == 0) + break; + + } + scrollView.contentSize = contentSize; + CGFloat y = 0; + for (NSUInteger row = 0; ; row++) { + CGFloat x = 0; + CGFloat maxHeight = 0; + for (NSUInteger col = 0; col < cols; col++) { + NSUInteger i = row * cols + col; + if (i >= nviews) + goto done2; + CGSize size = sizes[i]; + UIView *view = [views objectAtIndex:i]; + if (col != 0) + x += paddingX; + view.frame = CGRectMake(x, y, size.width, size.height); + x += colWidths[col]; + if (size.height > maxHeight) + maxHeight = size.height; + } + y += maxHeight + paddingY; + } +done2: + free(sizes); + free(colWidths); +} +/* +// Only override drawRect: if you perform custom drawing. +// An empty implementation adversely affects performance during animation. +- (void)drawRect:(CGRect)rect { + // Drawing code +} +*/ + +@end diff --git a/darwin-bootstrap/safety-dance/Info.plist b/darwin-bootstrap/safety-dance/Info.plist new file mode 100644 index 0000000..f2fa520 --- /dev/null +++ b/darwin-bootstrap/safety-dance/Info.plist @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>SafetyDance</string> + <key>CFBundleIdentifier</key> + <string>com.ex.SafetyDance</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>SafetyDance</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>LSRequiresIPhoneOS</key> + <false/> + <key>UILaunchImages</key> + <array> + <dict> + <key>UILaunchImageMinimumOSVersion</key> + <string>7.0</string> + <key>UILaunchImageName</key> + <string>Default</string> + <key>UILaunchImageOrientation</key> + <string>Portrait</string> + <key>UILaunchImageSize</key> + <string>{320, 480}</string> + </dict> + <dict> + <key>UILaunchImageMinimumOSVersion</key> + <string>7.0</string> + <key>UILaunchImageName</key> + <string>Default</string> + <key>UILaunchImageOrientation</key> + <string>Portrait</string> + <key>UILaunchImageSize</key> + <string>{320, 568}</string> + </dict> + <dict> + <key>UILaunchImageMinimumOSVersion</key> + <string>8.0</string> + <key>UILaunchImageName</key> + <string>Default</string> + <key>UILaunchImageOrientation</key> + <string>Portrait</string> + <key>UILaunchImageSize</key> + <string>{375, 667}</string> + </dict> + <dict> + <key>UILaunchImageMinimumOSVersion</key> + <string>8.0</string> + <key>UILaunchImageName</key> + <string>Default</string> + <key>UILaunchImageOrientation</key> + <string>Portrait</string> + <key>UILaunchImageSize</key> + <string>{414, 736}</string> + </dict> + <dict> + <key>UILaunchImageMinimumOSVersion</key> + <string>7.0</string> + <key>UILaunchImageName</key> + <string>Default</string> + <key>UILaunchImageOrientation</key> + <string>Portrait</string> + <key>UILaunchImageSize</key> + <string>{768, 1024}</string> + </dict> + </array> + <key>UIStatusBarStyle</key> + <string>UIStatusBarStyleLightContent</string> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/darwin-bootstrap/safety-dance/main.m b/darwin-bootstrap/safety-dance/main.m new file mode 100644 index 0000000..0d05e00 --- /dev/null +++ b/darwin-bootstrap/safety-dance/main.m @@ -0,0 +1,159 @@ +#import <UIKit/UIKit.h> +#import "AutoGrid.h" + +@interface ViewController : UIViewController { + AutoGrid *autoGrid; +} + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self loadStuff]; + NSMutableArray *names = [NSMutableArray array]; + for (int i = 0; i < 100; i++) + [names addObject:[NSString stringWithFormat:@"Some Dylib %d", i]]; + NSMutableArray *views = [NSMutableArray array]; + for (NSString *name in names) { + UILabel *label = [[UILabel alloc] init]; + label.text = name; + [views addObject:label]; + } + [autoGrid setViews:views]; +} + +#define EXPLANATION \ + @"SpringBoard seems to have crashed. The cause might be a Substitute jailbreak extension, or unrelated. Just to be safe, extensions in SpringBoard have been temporarily disabled. You can continue in this mode, or restart SpringBoard normally.\n\nThe following extensions were running:" + +static void hugging(UIView *view, UILayoutPriority pri) { + [view setContentHuggingPriority:pri forAxis:UILayoutConstraintAxisHorizontal]; + [view setContentHuggingPriority:pri forAxis:UILayoutConstraintAxisVertical]; +} +static void compression(UIView *view, UILayoutPriority pri) { + [view setContentCompressionResistancePriority:pri forAxis:UILayoutConstraintAxisHorizontal]; + [view setContentCompressionResistancePriority:pri forAxis:UILayoutConstraintAxisVertical]; +} + +- (void)loadStuff { + self.view.backgroundColor = [UIColor whiteColor]; + + UILabel *top = [[UILabel alloc] init]; + top.translatesAutoresizingMaskIntoConstraints = NO; + top.textAlignment = NSTextAlignmentCenter; + hugging(top, 251); + top.text = @"libsubstitute"; + top.font = [UIFont systemFontOfSize:23]; + [self.view addSubview:top]; + + UILabel *big = [[UILabel alloc] init]; + big.translatesAutoresizingMaskIntoConstraints = NO; + big.textAlignment = NSTextAlignmentCenter; + hugging(big, 251); + [big setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal]; + [big setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical]; + big.text = @"Safe Mode"; + big.font = [UIFont systemFontOfSize:32]; + [self.view addSubview:big]; + + UILabel *explain = [[UILabel alloc] init]; + explain.translatesAutoresizingMaskIntoConstraints = NO; + explain.textAlignment = NSTextAlignmentCenter; + hugging(explain, 251); + compression(explain, 999); + explain.text = EXPLANATION; + explain.font = [UIFont systemFontOfSize:14]; + explain.minimumScaleFactor = 0.5; /* test */ + explain.numberOfLines = 0; + [self.view addSubview:explain]; + + UIButton *returnButton = [UIButton buttonWithType:UIButtonTypeSystem]; + returnButton.translatesAutoresizingMaskIntoConstraints = NO; + returnButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + returnButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; + returnButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [returnButton setTitle:@"Return to Normal" forState:UIControlStateNormal]; + [self.view addSubview:returnButton]; + + UIButton *continueButton = [UIButton buttonWithType:UIButtonTypeSystem]; + continueButton.translatesAutoresizingMaskIntoConstraints = NO; + hugging(continueButton, 999); + compression(continueButton, 300); + continueButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; + continueButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; + continueButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [continueButton setTitle:@"Continue in Safe Mode" forState:UIControlStateNormal]; + [self.view addSubview:continueButton]; + + autoGrid = [[AutoGrid alloc] init]; + autoGrid.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:autoGrid]; + + NSDictionary *viewsDictionary = @{ + @"top": top, + @"big": big, + @"explain": explain, + @"returnButton": returnButton, + @"continueButton": continueButton, + @"grid": autoGrid, + @"topGuide": self.topLayoutGuide, + @"bottomGuide": self.bottomLayoutGuide, + }; + NSMutableArray *constraints = [[NSMutableArray alloc] init]; + [constraints addObjectsFromArray: + [NSLayoutConstraint constraintsWithVisualFormat: + @"V:[topGuide]-10-[top]-0@100-[big]-0@100-[explain]-18@200-[grid]-18-[continueButton]-8-[returnButton]-20@100-[bottomGuide]" + options:NSLayoutFormatAlignAllCenterX metrics:nil views:viewsDictionary]]; + NSArray *additional = @[ + @"[explain(<=650)]", + @"|-10-[explain]-10-|", + @"|-20-[grid]-20-|", + ]; + for (NSString *fmt in additional) { + [constraints addObjectsFromArray: + [NSLayoutConstraint constraintsWithVisualFormat:fmt options:0 metrics:nil views:viewsDictionary]]; + } + [self.view addConstraints:constraints]; +} + + +- (NSUInteger)supportedInterfaceOrientations +{ + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) + return UIInterfaceOrientationMaskAll; + else if ([UIScreen mainScreen].bounds.size.width >= 414) + return UIInterfaceOrientationMaskAllButUpsideDown; + else + return UIInterfaceOrientationMaskPortrait; +} + +@end + +@interface AppDelegate : UIResponder <UIApplicationDelegate> { +} + +@property (strong, nonatomic) UIWindow *window; + + +@end + +@implementation AppDelegate +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSLog(@"dflwo"); + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + ViewController *viewController = [[ViewController alloc] init]; + self.window.rootViewController = viewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end + +int main(int argc, char *argv[]) { + NSLog(@"main"); + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, @"AppDelegate"); + } +} + diff --git a/darwin-bootstrap/safety-dance/white.png b/darwin-bootstrap/safety-dance/white.png Binary files differnew file mode 100644 index 0000000..eab5a56 --- /dev/null +++ b/darwin-bootstrap/safety-dance/white.png diff --git a/darwin-bootstrap/unrestrict.c b/darwin-bootstrap/unrestrict.c new file mode 100644 index 0000000..c2f13bb --- /dev/null +++ b/darwin-bootstrap/unrestrict.c @@ -0,0 +1,245 @@ +/* This is an iOS executable spawned from posixspawn-hook.dylib, which accesses + * a process and changes the name of any __RESTRICT,__restrict sections in the + * main executable in memory. Doing so prevents dyld from refusing to honor + * DYLD_INSERT_LIBRARIES (which is a pretty dumb protection mechanism in the + * first place, even on OS X). + * + * It exists as a separate executable because (a) such processes may be + * launched with POSIX_SPAWN_SETEXEC, which makes posix_spawn act like exec and + * replace the current process, and (b) if they're not, launchd (into which + * posixspawn-hook is injected) still can't task_for_pid the child process + * itself, because it doesn't have the right entitlements. +*/ + +#define IB_LOG_NAME "unrestrict" +#include "ib-log.h" +#include "darwin/mach-decls.h" +#include "substitute.h" +#include "substitute-internal.h" +#include <stdlib.h> +#include <syslog.h> +#include <signal.h> +#include <errno.h> +#include <stdio.h> +#include <mach/mach.h> +#include <mach-o/loader.h> + +#define PROC_PIDFDVNODEINFO 1 +#define PROC_PIDFDVNODEINFO_SIZE 176 +int proc_pidfdinfo(int, int, int, void *, int); + +static bool unrestrict_macho_header(void *header, size_t size) { + struct mach_header *mh = header; + if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64) { + ib_log("bad mach-o magic"); + return false; + } + bool did_modify = false; + size_t off = mh->magic == MH_MAGIC_64 ? sizeof(struct mach_header_64) + : sizeof(struct mach_header); + for (uint32_t i = 0; i < mh->ncmds; i++) { + if (off > size || size - off < sizeof(struct load_command)) + break; /* whatever */ + struct load_command *lc = header + off; + if (lc->cmdsize > size - off) + break; + #define CASES(code...) \ + if (lc->cmd == LC_SEGMENT) { \ + typedef struct segment_command segment_command_y; \ + typedef struct section section_y; \ + code \ + } else if (lc->cmd == LC_SEGMENT_64) { \ + typedef struct segment_command_64 segment_command_y; \ + typedef struct section_64 section_y; \ + code \ + } + CASES( + segment_command_y *sc = (void *) lc; + if (lc->cmdsize < sizeof(*sc) || + sc->nsects > (lc->cmdsize - sizeof(*sc)) / sizeof(struct section)) { + ib_log("bad segment_command"); + return false; + } + if (!strncmp(sc->segname, "__RESTRICT", 16)) { + section_y *sect = (void *) (sc + 1); + for (uint32_t i = 0; i < sc->nsects; i++, sect++) { + if (!strncmp(sect->sectname, "__restrict", 16)) { + strcpy(sect->sectname, "\xf0\x9f\x92\xa9"); + did_modify = true; + } + } + } + ) + #undef CASES + + if (off + lc->cmdsize < off) { + ib_log("overflowing lc->cmdsize"); + return false; + } + off += lc->cmdsize; + } + return did_modify; +} + +static void unrestrict(task_t task) { + vm_address_t header_addr = 0; + kern_return_t kr; + + /* alrighty then, let's look at the damage. find the first readable + * segment */ +setback:; + mach_vm_address_t segm_addr = 0; + mach_vm_size_t segm_size = 0; + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object_name; + while (1) { + kr = mach_vm_region(task, &segm_addr, &segm_size, VM_REGION_BASIC_INFO_64, + (vm_region_info_t) &info, &info_count, &object_name); + if (kr == KERN_INVALID_ADDRESS) { + /* nothing! maybe it's not there *yet*? this actually is possible */ + usleep(10); + goto setback; + } else if (kr) { + ib_log("mach_vm_region(%lx): %x", (long) segm_addr, kr); + return; + } + if (info.protection) + break; + + segm_addr++; + } + + size_t toread = 0x4000; + if (segm_size < toread) + toread = segm_size; + if ((kr = vm_allocate(mach_task_self(), &header_addr, toread, + VM_FLAGS_ANYWHERE))) { + ib_log("vm_allocate(%zx): %x", toread, kr); + return; + } + mach_vm_size_t actual = toread; + kr = mach_vm_read_overwrite(task, segm_addr, toread, header_addr, + &actual); + if (kr || actual != toread) { + ib_log("mach_vm_read_overwrite: %x", kr); + return; + } + + bool did_modify = unrestrict_macho_header((void *) header_addr, toread); + + if (did_modify) { + if ((kr = vm_protect(mach_task_self(), header_addr, toread, + FALSE, info.protection))) { + ib_log("vm_protect(%lx=>%d): %x", + (long) header_addr, info.protection, kr); + return; + } + vm_prot_t cur, max; + if ((kr = mach_vm_remap(task, &segm_addr, toread, 0, + VM_FLAGS_OVERWRITE, + mach_task_self(), header_addr, FALSE, + &cur, &max, info.inheritance))) { + ib_log("mach_vm_remap(%lx=>%lx size=%zx): %x", + (long) header_addr, (long) segm_addr, toread, kr); + return; + } + } +} + + + +int main(int argc, char **argv) { + if (argc != 4) { + ib_log("wrong number of args"); + return 1; + } + + const char *pids = argv[1]; + char *end; + long pid = strtol(pids, &end, 10); + if (!pids[0] || *end) { + ib_log("pid not an integer"); + return 1; + } + + const char *should_resume = argv[2]; + if (strcmp(should_resume, "0") && strcmp(should_resume, "1")) { + ib_log("should_resume not 0 or 1"); + return 1; + } + + const char *is_exec = argv[3]; + if (strcmp(is_exec, "0") && strcmp(is_exec, "1")) { + ib_log("is_exec not 0 or 1"); + return 1; + } + + /* double fork to avoid zombies */ + int ret = fork(); + if (ret == -1) { + ib_log("fork: %s", strerror(errno)); + return 1; + } else if (ret) { + return 0; + } + + if (IB_VERBOSE) { + ib_log("unrestricting %ld (sr=%s, ie=%s)", pid, + should_resume, is_exec); + } + + int ec = 1; + + mach_port_t task; + kern_return_t kr = task_for_pid(mach_task_self(), (pid_t) pid, &task); + if (kr) { + ib_log("TFP fail: %d", kr); + goto fail; + } + + if (is_exec[0] == '1') { + int retries = 0; + int wait_us = 1; + while (1) { + /* The process might not have transitioned yet. We set up a dummy fd + * 255 in the parent process which was marked CLOEXEC, so test if that + * still exists. AFAICT, Substrate's equivalent to this is not + * actually correct. + * TODO cleanup + */ + char buf[PROC_PIDFDVNODEINFO_SIZE]; + /* A bug in proc_pidfdinfo makes it never return -1. Yuck. */ + errno = 0; + proc_pidfdinfo(pid, 255, PROC_PIDFDVNODEINFO, buf, sizeof(buf)); + if (errno == EBADF) { + break; + } else if (errno) { + ib_log("proc_pidfdinfo: %s", strerror(errno)); + goto fail; + } + + if (retries++ == 20) { + ib_log("still in parent process after 20 retries"); + goto fail; + } + wait_us *= 2; + if (wait_us > 200000) + wait_us = 200000; + while (usleep(wait_us)) + ; + } + } + + unrestrict(task); + +fail: + if (should_resume[0] == '1') { + if ((kill(pid, SIGCONT))) { + ib_log("kill SIGCONT: %s", strerror(errno)); + return 1; + } + } + + return ec; +} |