From cfb44482c19277852daf0ddda4cb490825c4e143 Mon Sep 17 00:00:00 2001 From: opa334 Date: Tue, 24 Dec 2024 18:37:17 +0100 Subject: [PATCH] Rework forkfix on iOS 15 arm64e: Fully reimplement fork, vfork, daemon and forkpty so that no more function hook is neccessary --- BaseBin/forkfix/src/main.c | 40 +----- BaseBin/forkfix/src/reimpl.c | 190 ++++++++++++++++++++++++++++ BaseBin/forkfix/src/reimpl.h | 6 + BaseBin/launchdhook/src/boomerang.c | 2 +- 4 files changed, 199 insertions(+), 39 deletions(-) create mode 100644 BaseBin/forkfix/src/reimpl.c create mode 100644 BaseBin/forkfix/src/reimpl.h diff --git a/BaseBin/forkfix/src/main.c b/BaseBin/forkfix/src/main.c index 114cd1cd1..33d3b25e7 100644 --- a/BaseBin/forkfix/src/main.c +++ b/BaseBin/forkfix/src/main.c @@ -8,6 +8,7 @@ #include #include "syscall.h" #include "litehook.h" +#include "reimpl.h" #include extern void _malloc_fork_prepare(void); @@ -107,48 +108,11 @@ void apply_fork_hook(void) }); } -// iOS 15 arm64e wrappers -// Only apply fork hook when something actually calls it -int fork_hook(void) -{ - apply_fork_hook(); - return fork(); -} -int vfork_hook(void) -{ - apply_fork_hook(); - return vfork(); -} -pid_t forkpty_hook(int *amaster, char *name, struct termios *termp, struct winsize *winp) -{ - apply_fork_hook(); - return forkpty(amaster, name, termp, winp); -} -int daemon_hook(int __nochdir, int __noclose) -{ - apply_fork_hook(); - return daemon(__nochdir, __noclose); -} - __attribute__((constructor)) static void initializer(void) { #ifdef __arm64e__ if (__builtin_available(iOS 16.0, *)) { /* fall through */ } - else { - void *systemhookHandle = dlopen("systemhook.dylib", RTLD_NOLOAD); - if (systemhookHandle) { - // On iOS 15 arm64e, instead of using instruction replacements, rebind everything that calls __fork instead - // Less instruction replacements = Less spinlock panics (DO NOT QUOTE ME ON THIS) - kern_return_t (*litehook_rebind_symbol_globally)(void *source, void *target) = dlsym(systemhookHandle, "litehook_rebind_symbol_globally"); - if (litehook_rebind_symbol_globally) { - litehook_rebind_symbol_globally((void *)fork, (void *)fork_hook); - litehook_rebind_symbol_globally((void *)vfork, (void *)vfork_hook); - litehook_rebind_symbol_globally((void *)forkpty, (void *)forkpty_hook); - litehook_rebind_symbol_globally((void *)daemon, (void *)daemon_hook); - } - } - return; - } + else if (fork_reimpl_init(forkfix___fork)) return; #endif apply_fork_hook(); diff --git a/BaseBin/forkfix/src/reimpl.c b/BaseBin/forkfix/src/reimpl.c new file mode 100644 index 000000000..4cce8fd0c --- /dev/null +++ b/BaseBin/forkfix/src/reimpl.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +kern_return_t bootstrap_parent(mach_port_t bp, mach_port_t *parent_port); +void __fork(void); + +// There are two functions with direct branches to __fork: fork and vfork +// We want to rebind these to reimplementations that work the same, but call our __forkfix_fork instead + +// Additionally, there are also two functions with direct branches to fork: daemon and forkpty +// For these, we want to rebind them to reimplementations that work the same, but call our fork_reimpl instead + +// Unfortunately, there is no other option here than to reimplement the functions, since the point is to do no instruction replacements + +static int (*__fork_ptr)(void) = NULL; + +void (**_libSystem_atfork_prepare)(uint64_t v2Arg) = NULL; +void (**_libSystem_atfork_parent) (uint64_t v2Arg) = NULL; +void (**_libSystem_atfork_child) (uint64_t v2Arg) = NULL; + +int fork_reimpl(void) +{ + (*_libSystem_atfork_prepare)(0); + int pid = __fork_ptr(); + if (pid != 0) { + (*_libSystem_atfork_parent)(0); + } + else { + (*_libSystem_atfork_child)(0); + } + return pid; +} + +int vfork_reimpl(void) +{ + (*_libSystem_atfork_prepare)(1); + int pid = __fork_ptr(); + if (pid != 0) { + (*_libSystem_atfork_parent)(1); + } + else { + (*_libSystem_atfork_child)(1); + } + return pid; +} + +static void move_to_root_bootstrap(void) +{ + mach_port_t parent_port = 0; + mach_port_t previous_port = 0; + + do { + if (previous_port) { + mach_port_deallocate(mach_task_self(), previous_port); + previous_port = parent_port; + } else { + previous_port = bootstrap_port; + } + + if (bootstrap_parent(previous_port, &parent_port) != 0) { + return; + } + } while (parent_port != previous_port); + + task_set_bootstrap_port(mach_task_self(), parent_port); + bootstrap_port = parent_port; +} + +int daemon_reimpl(int nochdir, int noclose) +{ + struct sigaction osa, sa; + int fd; + pid_t newgrp; + int oerrno; + int osa_ok; + + /* A SIGHUP may be thrown when the parent exits below. */ + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + osa_ok = sigaction(SIGHUP, &sa, &osa); + move_to_root_bootstrap(); + switch (fork_reimpl()) { + case -1: + return (-1); + case 0: + break; + default: + _exit(0); + } + + newgrp = setsid(); + oerrno = errno; + if (osa_ok != -1) + sigaction(SIGHUP, &osa, NULL); + + if (newgrp == -1) { + errno = oerrno; + return (-1); + } + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close(fd); + } + return (0); +} + +int forkpty_reimpl(int *aprimary, char *name, struct termios *termp, struct winsize *winp) +{ + int primary, replica, pid; + + if (openpty(&primary, &replica, name, termp, winp) == -1) + return (-1); + switch (pid = fork_reimpl()) { + case -1: + (void) close(primary); + (void) close(replica); + return (-1); + case 0: + /* + * child + */ + (void) close(primary); + /* + * 4300297: login_tty() may fail to set the controlling tty. + * Since we have already forked, the best we can do is to + * dup the replica as if login_tty() succeeded. + */ + if (login_tty(replica) < 0) { + syslog(LOG_ERR, "forkpty: login_tty could't make controlling tty"); + (void) dup2(replica, 0); + (void) dup2(replica, 1); + (void) dup2(replica, 2); + if (replica > 2) + (void) close(replica); + } + return (0); + } + /* + * parent + */ + *aprimary = primary; + (void) close(replica); + return (pid); +} + +bool fork_reimpl_init(void *fork_ptr) +{ + if (!fork_ptr) return false; + + __fork_ptr = fork_ptr; + + void *systemhookHandle = dlopen("systemhook.dylib", RTLD_NOLOAD); + if (!systemhookHandle) return false; + + kern_return_t (*litehook_rebind_symbol_globally)(void *source, void *target) = dlsym(systemhookHandle, "litehook_rebind_symbol_globally"); + void *(*litehook_find_dsc_symbol)(const char *imagePath, const char *symbolName) = dlsym(systemhookHandle, "litehook_find_dsc_symbol"); + if (!litehook_rebind_symbol_globally || !litehook_find_dsc_symbol) return false; + + // The v2 functions take one argument, but we can still store them in the same pointer since the argument will just be discarded if the non v2 implementation is used + // In practice, the v2 implementation should always exist, since we're not dealing with super old versions, so all of this doesn't matter too much + const char *libcpath = "/usr/lib/system/libsystem_c.dylib"; + _libSystem_atfork_prepare = litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_prepare_v2") ?: litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_prepare"); + _libSystem_atfork_parent = litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_parent_v2") ?: litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_parent"); + _libSystem_atfork_child = litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_child_v2") ?: litehook_find_dsc_symbol(libcpath, "__libSystem_atfork_child"); + + litehook_rebind_symbol_globally((void *)__fork, (void *)__fork_ptr); + litehook_rebind_symbol_globally((void *)fork, (void *)fork_reimpl); + litehook_rebind_symbol_globally((void *)vfork, (void *)vfork_reimpl); + litehook_rebind_symbol_globally((void *)daemon, (void *)daemon_reimpl); + litehook_rebind_symbol_globally((void *)forkpty, (void *)forkpty_reimpl); + + return true; +} \ No newline at end of file diff --git a/BaseBin/forkfix/src/reimpl.h b/BaseBin/forkfix/src/reimpl.h new file mode 100644 index 000000000..05436df1f --- /dev/null +++ b/BaseBin/forkfix/src/reimpl.h @@ -0,0 +1,6 @@ +int fork_reimpl(void); +int vfork_reimpl(void); +int daemon_reimpl(int nochdir, int noclose); +int forkpty_reimpl(int *aprimary, char *name, struct termios *termp, struct winsize *winp); + +bool fork_reimpl_init(void *fork_ptr); \ No newline at end of file diff --git a/BaseBin/launchdhook/src/boomerang.c b/BaseBin/launchdhook/src/boomerang.c index a8137bf8a..7a554e071 100644 --- a/BaseBin/launchdhook/src/boomerang.c +++ b/BaseBin/launchdhook/src/boomerang.c @@ -97,6 +97,6 @@ int boomerang_recoverPrimitives(bool firstRetrieval, bool shouldEndBoomerang) waitpid(boomerangPid, &boomerangStatus, 0); } } - + return 0; }