diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index bd1c7f6..974d7a6 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -108,13 +108,17 @@ diff --git a/rop_inject.m b/rop_inject.m index 77acb84..f125a72 100644 --- a/rop_inject.m +++ b/rop_inject.m -@@ -293,12 +293,22 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -293,13 +293,22 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, thread_act_array_t cachedThreads; mach_msg_type_number_t cachedThreadCount; kr = task_threads(task, &cachedThreads, &cachedThreadCount); - if (kr != KERN_SUCCESS) return kr; + if (kr != KERN_SUCCESS) { -+ free(origThreadFullState); ++ // The target thread was already rewritten for the remote call. ++ // Restore it before returning or launchd may continue with a bad PC/SP. ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); + return kr; @@ -132,15 +136,19 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,6 +320,11 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -310,7 +320,15 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + if(kr != KERN_SUCCESS) { - free(origThreadFullState); - printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); -+ if (kr == KERN_OPERATION_TIMED_OUT) { -+ printf("[arbCall] Remote call still active; preserving its stack and thread state\n"); -+ return kr; -+ } +- free(origThreadFullState); ++ // The target was resumed before waiting. Fail closed: abort the ++ // remote call, restore the original context, then release its stack. ++ thread_suspend(targetThread); ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); ++ thread_resume(targetThread); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); return kr; } @@ -187,8 +195,11 @@ index 77acb84..f125a72 100644 - arbCall(task, pthread, (uint64_t*)&readExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&readExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteExtString, remoteExtStringSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } printf("[sandboxFixup] sandbox_extension_consume returned %lld for read extension\n", (int64_t)readExtensionRet); - retval |= (readExtensionRet <= 0); @@ -212,8 +223,11 @@ index 77acb84..f125a72 100644 - arbCall(task, pthread, (uint64_t*)&executableExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&executableExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteExtString, remoteExtStringSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } printf("[sandboxFixup] sandbox_extension_consume returned %lld for executable extension\n", (int64_t)executableExtensionRet); - retval |= (executableExtensionRet <= 0); @@ -243,7 +257,7 @@ index 77acb84..f125a72 100644 + kr = sandboxFixup(task, pthread, pid, dylibPath, allImageInfoAddr); + if (kr != KERN_SUCCESS) { + printf("[injectDylibViaRop] ERROR: sandbox fixup failed: %s\n", mach_error_string(kr)); -+ if (kr != KERN_OPERATION_TIMED_OUT) thread_terminate(pthread); ++ thread_terminate(pthread); + return kr; + } @@ -268,8 +282,11 @@ index 77acb84..f125a72 100644 - vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); + void* dlopenRet = NULL; + kr = arbCall(task, pthread, (uint64_t*)&dlopenRet, true, dlopenAddr, 2, remoteDylibPath, RTLD_NOW); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } if (dlopenRet) { printf("[injectDylibViaRop] dlopen succeeded, library handle: %p\n", dlopenRet); @@ -278,7 +295,10 @@ index 77acb84..f125a72 100644 uint64_t remoteErrorString = 0; - arbCall(task, pthread, (uint64_t*)&remoteErrorString, true, dlerrorAddr, 0); + kr = arbCall(task, pthread, (uint64_t*)&remoteErrorString, true, dlerrorAddr, 0); -+ if (kr != KERN_SUCCESS) return kr; ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } char *errorString = task_copy_string(task, remoteErrorString); printf("[injectDylibViaRop] dlopen failed, error:\n%s\n", errorString); free(errorString); diff --git a/.github/patches/opainject-cleanup.patch b/.github/patches/opainject-cleanup.patch new file mode 100644 index 0000000..fe2462c --- /dev/null +++ b/.github/patches/opainject-cleanup.patch @@ -0,0 +1,25 @@ +diff --git a/rop_inject.m b/rop_inject.m +--- a/rop_inject.m ++++ b/rop_inject.m +@@ + kr = task_threads(task, &cachedThreads, &cachedThreadCount); + if (kr != KERN_SUCCESS) { ++ // The target thread was already rewritten for the remote call. ++ // Restore it before returning or launchd may continue with a bad PC/SP. ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); + free(origThreadFullState); + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); +@@ + if(kr != KERN_SUCCESS) + { ++ if (kr != KERN_OPERATION_TIMED_OUT) { ++ // All non-timeout errors must restore the hijacked target thread. ++ thread_suspend(targetThread); ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ thread_resume(targetThread); ++ } + free(origThreadFullState); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); diff --git a/.github/patches/opainject-serial-cleanup.patch b/.github/patches/opainject-serial-cleanup.patch new file mode 100644 index 0000000..eea9d02 --- /dev/null +++ b/.github/patches/opainject-serial-cleanup.patch @@ -0,0 +1,91 @@ +diff --git a/rop_inject.m b/rop_inject.m +index 956e7b9..b7c7a03 100644 +--- a/rop_inject.m ++++ b/rop_inject.m +@@ -308,11 +308,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + + // perform arbitary call + thread_resume(targetThread); +- // Constructors and runtime availability checks can need work from another +- // target-process thread. Keeping all launchd threads suspended here can +- // deadlock dlopen until the system watchdog panics. +- resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); +- vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); + printf("[arbCall] Started thread, waiting for it to finish...\n"); + + // wait for arbitary call to finish (or not) +@@ -328,6 +323,8 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + thread_abort(targetThread); + thread_restore_state_arm64(targetThread, origThreadFullState); + origThreadFullState = NULL; // thread_restore_state_arm64 consumes it ++ resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); ++ vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); +@@ -344,11 +341,10 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + { + kr = wait_for_thread(targetThread, 0, &outState); + printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); ++ free(origThreadFullState); ++ origThreadFullState = NULL; + } + +- // release fake stack as it's no longer needed +- vm_deallocate(task, remoteStack, STACK_SIZE); +- + if (willReturn) + { + // suspend target thread +@@ -360,6 +356,8 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + if(!restoreSuccess) + { + printf("[arbCall] ERROR: failed to revert to old thread state\n"); ++ resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); ++ vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); + return kr; + } + +@@ -368,6 +366,11 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + thread_resume(targetThread); + } + ++ // Balance all suspend counts before releasing the thread array. ++ resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); ++ vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); ++ // Release the temporary stack only after the target has been restored. ++ vm_deallocate(task, remoteStack, STACK_SIZE); + return kr; + } + +@@ -511,6 +514,15 @@ kern_return_t injectDylibViaRop(task_t task, pid_t pid, const char* dylibPath, v + return KERN_FAILURE; + } + +- thread_terminate(pthread); ++ // Exit through pthread_exit so libsystem can run pthread teardown. ++ // Direct Mach thread termination can leave launchd's pthread bookkeeping ++ // inconsistent and cause initproc to exit. ++ vm_address_t libSystemPthreadAddr = getRemoteImageAddress(task, allImageInfoAddr, "/usr/lib/system/libsystem_pthread.dylib"); ++ uint64_t pthreadExitAddr = remoteDlSym(task, libSystemPthreadAddr, "_pthread_exit"); ++ if (pthreadExitAddr) { ++ (void)arbCall(task, pthread, NULL, false, pthreadExitAddr, 1, (uint64_t)NULL); ++ } else { ++ thread_terminate(pthread); ++ } + return KERN_SUCCESS; + } +diff --git a/thread_utils.m b/thread_utils.m +index 58784c6..a004204 100644 +--- a/thread_utils.m ++++ b/thread_utils.m +@@ -108,7 +108,8 @@ kern_return_t wait_for_thread(thread_act_t thread, uint64_t pcToWait, struct arm + kern_return_t kr = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&stateToObserve.ts_64, &stateToObserveCount); + if(kr != KERN_SUCCESS) + { +- if (pcToWait == 0) return kr; ++ // A non-returning call is expected to terminate its remote thread. ++ if (pcToWait == 0) return KERN_SUCCESS; + + errCount++; + if(errCount >= 5) diff --git a/.github/workflows/roothide.yml b/.github/workflows/roothide.yml index 648ac01..a8cfbea 100644 --- a/.github/workflows/roothide.yml +++ b/.github/workflows/roothide.yml @@ -44,7 +44,8 @@ jobs: run: | set -euo pipefail git -C BaseBin/XPF apply "$GITHUB_WORKSPACE/.github/patches/XPF-roothide.patch" - git -C BaseBin/opainject apply "$GITHUB_WORKSPACE/.github/patches/opainject-build.patch" + git -C BaseBin/opainject apply --recount "$GITHUB_WORKSPACE/.github/patches/opainject-build.patch" + git -C BaseBin/opainject apply "$GITHUB_WORKSPACE/.github/patches/opainject-serial-cleanup.patch" # Xcode 15.4 is the last toolchain used by the original Dopamine 3 # project layout. Newer Xcodes treat some command-line targets as diff --git a/BaseBin/launchdhook/src/main.m b/BaseBin/launchdhook/src/main.m index 3461e16..0b8fc2e 100644 --- a/BaseBin/launchdhook/src/main.m +++ b/BaseBin/launchdhook/src/main.m @@ -141,6 +141,10 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, return; } + // DEBUG A/B: preserve primitive recovery and boomerang handoff, but + // skip all launchd hook registration and post-initialization logic. + return; + if (jbupdatePrevVersion && jbupdateNewVersion) { jbupdate_finalize_stage2(jbupdatePrevVersion, jbupdateNewVersion); unsetenv("JBUPDATE_PREV_VERSION"); @@ -190,3 +194,4 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, roothide_launchd_postinit(firstLoad); /********** roothide specfic ********/ } + diff --git a/BaseBin/watchdoghook/src/main.m b/BaseBin/watchdoghook/src/main.m index bb54b7d..0c97a1b 100644 --- a/BaseBin/watchdoghook/src/main.m +++ b/BaseBin/watchdoghook/src/main.m @@ -42,11 +42,13 @@ kern_return_t IOConnectCallStructMethod_hook(mach_port_t connection, uint32_t se __attribute__((constructor)) static void initializer(void) { ///////////////////////////// -if(access("/var/log/.disable_watchdoghook", F_OK) == 0) { - return; -} +if (1) { + // DEBUG A/B: disable RootHide watchdoghook installation. + // Leave the stock iOS watchdog behavior untouched. + return; + } /////////////////////////////// MSHookFunction(IOServiceOpen, (void *)&IOServiceOpen_hook, (void **)&IOServiceOpen_orig); MSHookFunction(IOConnectCallStructMethod, (void *)&IOConnectCallStructMethod_hook, (void **)&IOConnectCallStructMethod_orig); -} \ No newline at end of file +}