From d9a7a8a4d4a23fb65e6319e0e8a435046cc39fea Mon Sep 17 00:00:00 2001 From: comex Date: Sat, 28 Feb 2015 13:16:36 -0500 Subject: Rename ios-bootstrap to darwin-bootstrap; cleanup posixspawn-hook and unrestrict. Not tested yet. --- Makefile | 17 +- darwin-bootstrap/bundle-loader.m | 161 +++++++++++++ darwin-bootstrap/ib-log.h | 27 +++ darwin-bootstrap/inject-into-launchd.c | 93 +++++++ darwin-bootstrap/posixspawn-hook.c | 399 +++++++++++++++++++++++++++++++ darwin-bootstrap/safety-dance/AutoGrid.h | 17 ++ darwin-bootstrap/safety-dance/AutoGrid.m | 106 ++++++++ darwin-bootstrap/safety-dance/Info.plist | 88 +++++++ darwin-bootstrap/safety-dance/main.m | 159 ++++++++++++ darwin-bootstrap/safety-dance/white.png | Bin 0 -> 1047 bytes darwin-bootstrap/unrestrict.c | 245 +++++++++++++++++++ ios-bootstrap/bundle-loader.m | 161 ------------- ios-bootstrap/ib-log.h | 27 --- ios-bootstrap/inject-into-launchd.c | 90 ------- ios-bootstrap/posixspawn-hook.c | 352 --------------------------- ios-bootstrap/safety-dance/AutoGrid.h | 17 -- ios-bootstrap/safety-dance/AutoGrid.m | 106 -------- ios-bootstrap/safety-dance/Info.plist | 88 ------- ios-bootstrap/safety-dance/main.m | 159 ------------ ios-bootstrap/safety-dance/white.png | Bin 1047 -> 0 bytes ios-bootstrap/unrestrict.c | 115 --------- lib/darwin/unrestrict.c | 145 ----------- 22 files changed, 1303 insertions(+), 1269 deletions(-) create mode 100644 darwin-bootstrap/bundle-loader.m create mode 100644 darwin-bootstrap/ib-log.h create mode 100644 darwin-bootstrap/inject-into-launchd.c create mode 100644 darwin-bootstrap/posixspawn-hook.c create mode 100644 darwin-bootstrap/safety-dance/AutoGrid.h create mode 100644 darwin-bootstrap/safety-dance/AutoGrid.m create mode 100644 darwin-bootstrap/safety-dance/Info.plist create mode 100644 darwin-bootstrap/safety-dance/main.m create mode 100644 darwin-bootstrap/safety-dance/white.png create mode 100644 darwin-bootstrap/unrestrict.c delete mode 100644 ios-bootstrap/bundle-loader.m delete mode 100644 ios-bootstrap/ib-log.h delete mode 100644 ios-bootstrap/inject-into-launchd.c delete mode 100644 ios-bootstrap/posixspawn-hook.c delete mode 100644 ios-bootstrap/safety-dance/AutoGrid.h delete mode 100644 ios-bootstrap/safety-dance/AutoGrid.m delete mode 100644 ios-bootstrap/safety-dance/Info.plist delete mode 100644 ios-bootstrap/safety-dance/main.m delete mode 100644 ios-bootstrap/safety-dance/white.png delete mode 100644 ios-bootstrap/unrestrict.c delete mode 100644 lib/darwin/unrestrict.c diff --git a/Makefile b/Makefile index c94e4e7..50c4953 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,6 @@ LIB_OBJS := \ out/darwin/read.o \ out/darwin/substrate-compat.o \ out/darwin/execmem.o \ - out/darwin/unrestrict.o \ out/jump-dis.o \ out/transform-dis.o \ out/hook-functions.o \ @@ -171,29 +170,29 @@ $(foreach arch,i386 x86_64 armv7 arm64,$(eval $(transform-dis-cases))) # iOS bootstrap... ifneq (,$(IS_IOS)) SD_OBJS := out/safety-dance/main.o out/safety-dance/AutoGrid.o -out/safety-dance/%.o: ios-bootstrap/safety-dance/%.m ios-bootstrap/safety-dance/*.h Makefile +out/safety-dance/%.o: darwin-bootstrap/safety-dance/%.m darwin-bootstrap/safety-dance/*.h Makefile @mkdir -p $(dir $@) $(CC) -c -o $@ $< -fobjc-arc -Wno-unused-parameter out/safety-dance/SafetyDance.app/SafetyDance: $(SD_OBJS) Makefile @mkdir -p $(dir $@) $(CC) -o $@ $(SD_OBJS) $(IOS_APP_LDFLAGS) ldid -S $@ -out/safety-dance/SafetyDance.app/Info.plist: ios-bootstrap/safety-dance/Info.plist Makefile +out/safety-dance/SafetyDance.app/Info.plist: darwin-bootstrap/safety-dance/Info.plist Makefile @mkdir -p $(dir $@) plutil -convert binary1 -o $@ $< - cp ios-bootstrap/safety-dance/white.png out/safety-dance/SafetyDance.app/Default.png - cp ios-bootstrap/safety-dance/white.png out/safety-dance/SafetyDance.app/Default@2x.png + cp darwin-bootstrap/safety-dance/white.png out/safety-dance/SafetyDance.app/Default.png + cp darwin-bootstrap/safety-dance/white.png out/safety-dance/SafetyDance.app/Default@2x.png safety-dance: out/safety-dance/SafetyDance.app/SafetyDance out/safety-dance/SafetyDance.app/Info.plist all: safety-dance -out/posixspawn-hook.dylib: ios-bootstrap/posixspawn-hook.c out/libsubstitute.dylib +out/posixspawn-hook.dylib: darwin-bootstrap/posixspawn-hook.c out/libsubstitute.dylib $(CC) -dynamiclib -o $@ $< -Lout -lsubstitute -out/bundle-loader.dylib: ios-bootstrap/bundle-loader.m out/libsubstitute.dylib +out/bundle-loader.dylib: darwin-bootstrap/bundle-loader.m out/libsubstitute.dylib $(CC) -dynamiclib -o $@ $< -fobjc-arc -Lout -framework Foundation -framework CoreFoundation -out/unrestrict: ios-bootstrap/unrestrict.c ios-bootstrap/ib-log.h out/libsubstitute.dylib +out/unrestrict: darwin-bootstrap/unrestrict.c darwin-bootstrap/ib-log.h out/libsubstitute.dylib $(CC) -o $@ $< -Lout -lsubstitute ldid -Sent.plist $@ -out/inject-into-launchd: ios-bootstrap/inject-into-launchd.c ios-bootstrap/ib-log.h out/libsubstitute.dylib +out/inject-into-launchd: darwin-bootstrap/inject-into-launchd.c darwin-bootstrap/ib-log.h out/libsubstitute.dylib $(CC) -o $@ $< -Lout -lsubstitute -framework IOKit -framework CoreFoundation ldid -Sent.plist $@ all: out/posixspawn-hook.dylib out/bundle-loader.dylib out/unrestrict out/inject-into-launchd 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 +#import +#include +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 +#include +#include + +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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + +@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 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + SafetyDance + CFBundleIdentifier + com.ex.SafetyDance + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SafetyDance + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchImages + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 480} + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {320, 568} + + + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {375, 667} + + + UILaunchImageMinimumOSVersion + 8.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {414, 736} + + + UILaunchImageMinimumOSVersion + 7.0 + UILaunchImageName + Default + UILaunchImageOrientation + Portrait + UILaunchImageSize + {768, 1024} + + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + 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 +#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 { +} + +@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 new file mode 100644 index 0000000..eab5a56 Binary files /dev/null and b/darwin-bootstrap/safety-dance/white.png differ 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 +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/ios-bootstrap/bundle-loader.m b/ios-bootstrap/bundle-loader.m deleted file mode 100644 index fec2513..0000000 --- a/ios-bootstrap/bundle-loader.m +++ /dev/null @@ -1,161 +0,0 @@ -#import -#import -#include -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/ios-bootstrap/ib-log.h b/ios-bootstrap/ib-log.h deleted file mode 100644 index fee70b5..0000000 --- a/ios-bootstrap/ib-log.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once -#include -#include -#include - -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/ios-bootstrap/inject-into-launchd.c b/ios-bootstrap/inject-into-launchd.c deleted file mode 100644 index 4396f14..0000000 --- a/ios-bootstrap/inject-into-launchd.c +++ /dev/null @@ -1,90 +0,0 @@ -#define IB_LOG_NAME "iil" -#include "ib-log.h" -#include "substitute.h" -#include "substitute-internal.h" -#include -#include -#include -#include -#include -#include - -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/ios-bootstrap/posixspawn-hook.c b/ios-bootstrap/posixspawn-hook.c deleted file mode 100644 index 80339e1..0000000 --- a/ios-bootstrap/posixspawn-hook.c +++ /dev/null @@ -1,352 +0,0 @@ -#define IB_LOG_NAME "posixspawn-hook" -#include "ib-log.h" -#include "substitute.h" -#include "substitute-internal.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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 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; - - /* If Foundation is loaded into notifyd, the system doesn't boot. I spent - * some time trying to figure out why, but managed to brick my device - * instead (no idea how that happened either). I want to solve this before - * a stable release, but this works for now. - * n.b. Substrate isn't affected by this because it uses only - * CoreFoundation, not Foundation. However, CoreFoundation is pretty big - * itself, and also brings in libobjc, so it's not necessarily that - * principled to switch. I suppose principled might be to extract the - * plist code from CF... */ - if (!strcmp(path, "/usr/sbin/notifyd")) { - /* why? */ - safe_mode = true; - } - - - const char *orig_dyld_insert = ""; - static const char my_dylib_1[] = - "/Library/Substitute/bundle-loader.dylib"; - static const char my_dylib_2[] = - "/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(my_dylib_2) /* 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(my_dylib_1) - 1 && - !memcmp(next, my_dylib_1, sizeof(my_dylib_1) - 1)) || - (next - p == sizeof(my_dylib_2) - 1 && - !memcmp(next, my_dylib_2, sizeof(my_dylib_2) - 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") - ? my_dylib_2 - : my_dylib_1; - 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; - - - /* XXX Even async on a separate thread, task_for_pid from launchd hangs in - * kernel - AMFI permitUnrestrictedDebugging waiting on some mach port. - * Normally (from other processes) task_for_pid doesn't even ask amfid - * because we have the right entitlements, but it doesn't usually *hang*. - * Probably should do what Substrate does and only launch a process for the - * few actually restricted executables. I was originally hesitant about - * this because of complications with fat files. Whatever. */ - bool need_unrestrict = getuid() == 0; - - /* Deal with the dumb __restrict section. A complication is that this - * could actually be an exec. */ - /* 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 0 - dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, - 0); - dispatch_async(q, ^{ - char *error; - ib_log("unrestricting %d", pid); - int sret = substitute_ios_unrestrict(pid, !was_suspended, &error); - ib_log("unrestricting done"); - if (sret) { - ib_log("posixspawn-hook: substitute_ios_unrestrict => %d (%s)", - sret, error); - } - free(error); - }); -#else - if (need_unrestrict) - spawn_unrestrict(pid, !was_suspended, false); -#endif - 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/ios-bootstrap/safety-dance/AutoGrid.h b/ios-bootstrap/safety-dance/AutoGrid.h deleted file mode 100644 index 335381a..0000000 --- a/ios-bootstrap/safety-dance/AutoGrid.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// AutoGrid.h -// SafetyDance -// -// Created by Nicholas Allegra on 1/26/15. -// Copyright (c) 2015 Nicholas Allegra. All rights reserved. -// - -#import - -@interface AutoGrid : UIView { - NSArray *views; - UIScrollView *scrollView; -} -- (void)setViews:(NSArray *)views; - -@end diff --git a/ios-bootstrap/safety-dance/AutoGrid.m b/ios-bootstrap/safety-dance/AutoGrid.m deleted file mode 100644 index 2a8d3ed..0000000 --- a/ios-bootstrap/safety-dance/AutoGrid.m +++ /dev/null @@ -1,106 +0,0 @@ -// -// 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/ios-bootstrap/safety-dance/Info.plist b/ios-bootstrap/safety-dance/Info.plist deleted file mode 100644 index f2fa520..0000000 --- a/ios-bootstrap/safety-dance/Info.plist +++ /dev/null @@ -1,88 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - SafetyDance - CFBundleIdentifier - com.ex.SafetyDance - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - SafetyDance - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchImages - - - UILaunchImageMinimumOSVersion - 7.0 - UILaunchImageName - Default - UILaunchImageOrientation - Portrait - UILaunchImageSize - {320, 480} - - - UILaunchImageMinimumOSVersion - 7.0 - UILaunchImageName - Default - UILaunchImageOrientation - Portrait - UILaunchImageSize - {320, 568} - - - UILaunchImageMinimumOSVersion - 8.0 - UILaunchImageName - Default - UILaunchImageOrientation - Portrait - UILaunchImageSize - {375, 667} - - - UILaunchImageMinimumOSVersion - 8.0 - UILaunchImageName - Default - UILaunchImageOrientation - Portrait - UILaunchImageSize - {414, 736} - - - UILaunchImageMinimumOSVersion - 7.0 - UILaunchImageName - Default - UILaunchImageOrientation - Portrait - UILaunchImageSize - {768, 1024} - - - UIStatusBarStyle - UIStatusBarStyleLightContent - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/ios-bootstrap/safety-dance/main.m b/ios-bootstrap/safety-dance/main.m deleted file mode 100644 index 0d05e00..0000000 --- a/ios-bootstrap/safety-dance/main.m +++ /dev/null @@ -1,159 +0,0 @@ -#import -#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 { -} - -@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/ios-bootstrap/safety-dance/white.png b/ios-bootstrap/safety-dance/white.png deleted file mode 100644 index eab5a56..0000000 Binary files a/ios-bootstrap/safety-dance/white.png and /dev/null differ diff --git a/ios-bootstrap/unrestrict.c b/ios-bootstrap/unrestrict.c deleted file mode 100644 index 3c6ff56..0000000 --- a/ios-bootstrap/unrestrict.c +++ /dev/null @@ -1,115 +0,0 @@ -#define IB_LOG_NAME "unrestrict" -#include "ib-log.h" -#include "substitute.h" -#include "substitute-internal.h" -#include -#include -#include -#include -#include - -#define PROC_PIDFDVNODEINFO 1 -#define PROC_PIDFDVNODEINFO_SIZE 176 -int proc_pidfdinfo(int, int, int, void *, int); - -int main(int argc, char **argv) { - if (argc != 4) { - ib_log("unrestrict: 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("unrestrict: pid not an integer"); - return 1; - } - - const char *should_resume = argv[2]; - if (strcmp(should_resume, "0") && strcmp(should_resume, "1")) { - ib_log("unrestrict: 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("unrestrict: is_exec not 0 or 1"); - return 1; - } - - /* double fork to avoid zombies */ - int ret = fork(); - if (ret == -1) { - ib_log("unrestrict: fork: %s", strerror(errno)); - return 1; - } else if (ret) { - return 0; - } - - if (IB_VERBOSE) { - ib_log("unrestrict: 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("unrestrict: 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)) - ; - } - } - - char *err = NULL; - int sret = substitute_ios_unrestrict(task, &err); - if (sret) { - ib_log("unrestrict: substitute_ios_unrestrict => %d (%s)", - sret, err); - ec = 1; - } - - ec = 0; -fail: - if (should_resume[0] == '1') { - if ((kill(pid, SIGCONT))) { - ib_log("unrestrict: kill SIGCONT: %s", strerror(errno)); - return 1; - } - } - - return ec; -} diff --git a/lib/darwin/unrestrict.c b/lib/darwin/unrestrict.c deleted file mode 100644 index a0b6ade..0000000 --- a/lib/darwin/unrestrict.c +++ /dev/null @@ -1,145 +0,0 @@ -#include "substitute.h" -#include "substitute-internal.h" -#include "darwin/mach-decls.h" -#include -#include -#include -#include - -static int unrestrict_macho_header(void *header, size_t size, bool *did_modify_p, - char **error) { - *did_modify_p = false; - struct mach_header *mh = header; - if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64) - return SUBSTITUTE_ERR_MISC; - 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)) { - asprintf(error, "bad segment_command"); - return SUBSTITUTE_ERR_MISC; - } - 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_p = true; - } - } - } - ) - #undef CASES - - if (off + lc->cmdsize < off) { - asprintf(error, "overflowing lc->cmdsize"); - return SUBSTITUTE_ERR_MISC; - } - off += lc->cmdsize; - } - return SUBSTITUTE_OK; -} - -EXPORT -int substitute_ios_unrestrict(task_t task, char **error) { - *error = NULL; - - int ret; - 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) { - asprintf(error, "mach_vm_region(%lx): %x", (long) segm_addr, kr); - ret = SUBSTITUTE_ERR_VM; - goto fail; - } - 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))) { - asprintf(error, "vm_allocate(%zx): %x", toread, kr); - ret = SUBSTITUTE_ERR_MISC; - goto fail; - } - mach_vm_size_t actual = toread; - kr = mach_vm_read_overwrite(task, segm_addr, toread, header_addr, - &actual); - if (kr || actual != toread) { - asprintf(error, "mach_vm_read_overwrite: %x", kr); - ret = SUBSTITUTE_ERR_MISC; - goto fail; - } - - bool did_modify; - if ((ret = unrestrict_macho_header((void *) header_addr, toread, - &did_modify, error))) - goto fail; - - if (did_modify) { - if ((kr = vm_protect(mach_task_self(), header_addr, toread, - FALSE, info.protection))) { - asprintf(error, "vm_protect(%lx=>%d): %x", - (long) header_addr, info.protection, kr); - ret = SUBSTITUTE_ERR_VM; - goto fail; - } - 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))) { - asprintf(error, "mach_vm_remap(%lx=>%lx * %zx): %x", - (long) header_addr, (long) segm_addr, toread, kr); - ret = SUBSTITUTE_ERR_VM; - goto fail; - } - } - - ret = SUBSTITUTE_OK; -fail: - if (header_addr) - vm_deallocate(mach_task_self(), header_addr, toread); - return ret; -} - - -- cgit v1.2.3