From 618d29f3ac3a72dcf4d009f5cffa8c1c5784a721 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Wed, 12 Aug 2026 23:55:26 +0700 Subject: [PATCH 01/19] Disable watchdoghook installation for A/B panic test --- BaseBin/watchdoghook/src/main.m | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 +} From a533eb413d51625502d4cabcd01c3fa8f2e081a1 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 00:12:18 +0700 Subject: [PATCH 02/19] Disable launchdhook initializer for A/B panic test --- BaseBin/launchdhook/src/main.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BaseBin/launchdhook/src/main.m b/BaseBin/launchdhook/src/main.m index 3461e16..57f8f19 100644 --- a/BaseBin/launchdhook/src/main.m +++ b/BaseBin/launchdhook/src/main.m @@ -83,6 +83,10 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, __attribute__((constructor)) static void initializer(void) { + // DEBUG A/B: disable all launchdhook initialization while preserving + // the injected image and opainject handoff path. + return; + crashreporter_start(); /********** roothide specfic ********/ @@ -190,3 +194,4 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, roothide_launchd_postinit(firstLoad); /********** roothide specfic ********/ } + From 4fe3b31a3f7ab0c890a5ddba945b3bc05ab68e65 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 00:25:06 +0700 Subject: [PATCH 03/19] Preserve boomerang handoff and skip launchd hooks for A/B test --- BaseBin/launchdhook/src/main.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseBin/launchdhook/src/main.m b/BaseBin/launchdhook/src/main.m index 57f8f19..0b8fc2e 100644 --- a/BaseBin/launchdhook/src/main.m +++ b/BaseBin/launchdhook/src/main.m @@ -83,10 +83,6 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, __attribute__((constructor)) static void initializer(void) { - // DEBUG A/B: disable all launchdhook initialization while preserving - // the injected image and opainject handoff path. - return; - crashreporter_start(); /********** roothide specfic ********/ @@ -145,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"); From 296035234a1baa17e60176400b8ae018d62ed7ea Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 19:52:21 +0700 Subject: [PATCH 04/19] Create opainject-cleanup.patch --- .github/patches/opainject-cleanup.patch | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/patches/opainject-cleanup.patch 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)); From 5fd81a7ad44cc1d7d3b8e94c83d1cd39aa691b79 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 19:54:27 +0700 Subject: [PATCH 05/19] Update opainject-build.patch --- .github/patches/opainject-build.patch | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index bd1c7f6..955ae7f 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -114,6 +114,10 @@ index 77acb84..f125a72 100644 kr = task_threads(task, &cachedThreads, &cachedThreadCount); - if (kr != KERN_SUCCESS) return kr; + 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); @@ -134,6 +138,13 @@ index 77acb84..f125a72 100644 // 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, { ++ 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)); + if (kr == KERN_OPERATION_TIMED_OUT) { From 3292c8b3965efebf46730b8612073d2b4ff801a9 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 19:59:46 +0700 Subject: [PATCH 06/19] Fix opainject unified diff hunk counts --- .github/patches/opainject-build.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 955ae7f..ffcd482 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -108,7 +108,7 @@ 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,12 +293,26 @@ 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); @@ -136,7 +136,7 @@ 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,6 +324,18 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, { + if (kr != KERN_OPERATION_TIMED_OUT) { + // All non-timeout errors must restore the hijacked target thread. @@ -155,7 +155,7 @@ index 77acb84..f125a72 100644 return kr; } -@@ -325,10 +340,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -325,10 +351,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } From 17f6f685a63ebc6d908bfa5e9b9685d046d026a2 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 20:55:15 +0700 Subject: [PATCH 07/19] Fix opainject failure cleanup before rebuild --- .github/patches/opainject-build.patch | 39 ++++++++++++--------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index ffcd482..b3f1824 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -118,7 +118,7 @@ index 77acb84..f125a72 100644 + // Restore it before returning or launchd may continue with a bad PC/SP. + thread_abort(targetThread); + thread_restore_state_arm64(targetThread, origThreadFullState); -+ free(origThreadFullState); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); + return kr; @@ -136,23 +136,18 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,6 +324,18 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -310,6 +324,13 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, { -+ 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)); -+ if (kr == KERN_OPERATION_TIMED_OUT) { -+ printf("[arbCall] Remote call still active; preserving its stack and thread state\n"); -+ return kr; -+ } ++ // All failures must restore the hijacked target thread before return. ++ 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); - return kr; ++ thread_resume(targetThread); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); + return kr; } @@ -325,10 +351,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, @@ -198,8 +193,8 @@ 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); @@ -224,7 +219,7 @@ index 77acb84..f125a72 100644 - 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; ++ 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); @@ -254,7 +249,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; + } @@ -279,8 +274,8 @@ 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); From ca23ee7c7f0113fbee6cb72e3f2576d28bc6d715 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 20:58:50 +0700 Subject: [PATCH 08/19] Fix opainject failure cleanup before rebuild --- .github/patches/opainject-build.patch | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index b3f1824..2cedbda 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -145,11 +145,10 @@ index 77acb84..f125a72 100644 + origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); -+ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it - printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); - return kr; ++ printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); + return kr; } - + @@ -325,10 +351,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } @@ -218,7 +217,7 @@ 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); ++ 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); @@ -275,7 +274,7 @@ index 77acb84..f125a72 100644 + void* dlopenRet = NULL; + kr = arbCall(task, pthread, (uint64_t*)&dlopenRet, true, dlopenAddr, 2, remoteDylibPath, RTLD_NOW); + vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); -+ if (kr != KERN_SUCCESS) { thread_terminate(pthread); return kr; } ++ if (kr != KERN_SUCCESS) return kr; if (dlopenRet) { printf("[injectDylibViaRop] dlopen succeeded, library handle: %p\n", dlopenRet); @@ -284,7 +283,7 @@ 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); From 7d1746c785be9945f6a4b6c7c39a25a0ad2bc787 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:04:24 +0700 Subject: [PATCH 09/19] Fix opainject patch hunk counts --- .github/patches/opainject-build.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 2cedbda..e5c9b95 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -100,7 +100,7 @@ diff --git a/rop_inject.h b/rop_inject.h index eb9294f..97cbb1d 100644 --- a/rop_inject.h +++ b/rop_inject.h -@@ -1 +1 @@ +@@ -1,1 +1,1 @@ -extern void injectDylibViaRop(task_t task, pid_t pid, const char* dylibPath, vm_address_t allImageInfoAddr); \ No newline at end of file +extern kern_return_t injectDylibViaRop(task_t task, pid_t pid, const char* dylibPath, vm_address_t allImageInfoAddr); @@ -136,7 +136,7 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,6 +324,13 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -310,4 +324,12 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, { + // All failures must restore the hijacked target thread before return. + thread_suspend(targetThread); @@ -352,7 +352,7 @@ index 5339972..58784c6 100644 } if(stateOut) -@@ -212,4 +231,4 @@ void printThreadInfo(thread_act_t thread) +@@ -212,5 +231,5 @@ void printThreadInfo(thread_act_t thread) printf("system_time: %d.%d\n", basicInfo.system_time.seconds, basicInfo.system_time.microseconds); printf("user_time: %d.%d\n", basicInfo.user_time.seconds, basicInfo.user_time.microseconds); From af67247733fb145e2959e1c11716be20c8237b1e Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:08:24 +0700 Subject: [PATCH 10/19] Normalize opainject unified diff formatting --- .github/patches/opainject-build.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index e5c9b95..d1acb42 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -148,7 +148,7 @@ index 77acb84..f125a72 100644 + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); return kr; } - + @@ -325,10 +351,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } From 456428d3f78ea231d0d23e68b43366766e61324b Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:11:29 +0700 Subject: [PATCH 11/19] Fix final opainject diff hunk count --- .github/patches/opainject-build.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index d1acb42..013e382 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -352,7 +352,7 @@ index 5339972..58784c6 100644 } if(stateOut) -@@ -212,5 +231,5 @@ void printThreadInfo(thread_act_t thread) +@@ -212,4 +231,4 @@ void printThreadInfo(thread_act_t thread) printf("system_time: %d.%d\n", basicInfo.system_time.seconds, basicInfo.system_time.microseconds); printf("user_time: %d.%d\n", basicInfo.user_time.seconds, basicInfo.user_time.microseconds); From 561abc42ba9e45bcb669ae3a26fc3ac25023f4be Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:14:14 +0700 Subject: [PATCH 12/19] Fix arbCall wait hunk context --- .github/patches/opainject-build.patch | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 013e382..42ab3d4 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -136,8 +136,10 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,4 +324,12 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -310,6 +324,12 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, { +- free(origThreadFullState); +- printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); + // All failures must restore the hijacked target thread before return. + thread_suspend(targetThread); + thread_abort(targetThread); @@ -352,7 +354,7 @@ index 5339972..58784c6 100644 } if(stateOut) -@@ -212,4 +231,4 @@ void printThreadInfo(thread_act_t thread) +@@ -212,5 +231,5 @@ void printThreadInfo(thread_act_t thread) printf("system_time: %d.%d\n", basicInfo.system_time.seconds, basicInfo.system_time.microseconds); printf("user_time: %d.%d\n", basicInfo.user_time.seconds, basicInfo.user_time.microseconds); From c7f04d2d6ac2aae9de9e1af65d0165b2bf390e84 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:18:24 +0700 Subject: [PATCH 13/19] Correct final diff hunk count after arbCall fix --- .github/patches/opainject-build.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 42ab3d4..56067b8 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -354,7 +354,7 @@ index 5339972..58784c6 100644 } if(stateOut) -@@ -212,5 +231,5 @@ void printThreadInfo(thread_act_t thread) +@@ -212,4 +231,4 @@ void printThreadInfo(thread_act_t thread) printf("system_time: %d.%d\n", basicInfo.system_time.seconds, basicInfo.system_time.microseconds); printf("user_time: %d.%d\n", basicInfo.user_time.seconds, basicInfo.user_time.microseconds); From e2fc687aff6057c288a0dc2047f96d891fd66757 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 21:57:16 +0700 Subject: [PATCH 14/19] Fix opainject remote stack lifetime before thread restore --- .github/patches/opainject-build.patch | 40 ++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 56067b8..c7f7176 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -151,7 +151,7 @@ index 77acb84..f125a72 100644 return kr; } -@@ -325,10 +351,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -325,31 +351,27 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } @@ -159,8 +159,46 @@ index 77acb84..f125a72 100644 - - vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); - ++ if (willReturn) ++ { ++ // suspend target thread ++ thread_suspend(targetThread); ++ thread_abort(targetThread); ++ ++ // restore states of target thread to what they were before the arbitary call ++ bool restoreSuccess = thread_restore_state_arm64(targetThread, origThreadFullState); ++ if(!restoreSuccess) ++ { ++ printf("[arbCall] ERROR: failed to revert to old thread state\n"); ++ return kr; ++ } ++ ++ // resume thread again, process should continue executing as before ++ //printThreadState(targetThread); ++ thread_resume(targetThread); ++ } ++ // release fake stack as it's no longer needed vm_deallocate(task, remoteStack, STACK_SIZE); +- +- if (willReturn) +- { +- // suspend target thread +- thread_suspend(targetThread); +- thread_abort(targetThread); +- +- // restore states of target thread to what they were before the arbitary call +- bool restoreSuccess = thread_restore_state_arm64(targetThread, origThreadFullState); +- if(!restoreSuccess) +- { +- printf("[arbCall] ERROR: failed to revert to old thread state\n"); +- return kr; +- } +- +- // resume thread again, process should continue executing as before +- //printThreadState(targetThread); +- thread_resume(targetThread); +- } @@ -364,33 +375,37 @@ void prepareForMagic(task_t task, vm_address_t allImageInfoAddr) printf("[prepareForMagic] done, ropLoop: 0x%llX\n", ropLoop); From 93d6b95b10bf141e4954809bb2f53e2b86d6fdb1 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 22:02:51 +0700 Subject: [PATCH 15/19] Fix opainject patch hunk context --- .github/patches/opainject-build.patch | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index c7f7176..05e5190 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -199,7 +199,9 @@ index 77acb84..f125a72 100644 - //printThreadState(targetThread); - thread_resume(targetThread); - } - + `treturn kr; + } + @@ -364,33 +375,37 @@ void prepareForMagic(task_t task, vm_address_t allImageInfoAddr) printf("[prepareForMagic] done, ropLoop: 0x%llX\n", ropLoop); } From 719b1d6c373cdb944dff58e7a02b1b8f4d515461 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 22:04:55 +0700 Subject: [PATCH 16/19] Fix literal tab in opainject patch context --- .github/patches/opainject-build.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 05e5190..6a3633f 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -199,7 +199,7 @@ index 77acb84..f125a72 100644 - //printThreadState(targetThread); - thread_resume(targetThread); - } - `treturn kr; + return kr; } @@ -364,33 +375,37 @@ void prepareForMagic(task_t task, vm_address_t allImageInfoAddr) From bf2813919d5b3df52ad73aead89378c07955efc8 Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 22:07:51 +0700 Subject: [PATCH 17/19] Normalize opainject remote stack reorder hunk --- .github/patches/opainject-build.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 6a3633f..a1c2038 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -152,7 +152,7 @@ index 77acb84..f125a72 100644 } @@ -325,31 +351,27 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, - printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); + printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } - resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); @@ -199,9 +199,9 @@ index 77acb84..f125a72 100644 - //printThreadState(targetThread); - thread_resume(targetThread); - } + return kr; } - @@ -364,33 +375,37 @@ void prepareForMagic(task_t task, vm_address_t allImageInfoAddr) printf("[prepareForMagic] done, ropLoop: 0x%llX\n", ropLoop); } From ab8009e95f50481952787f0f41f86db93f6f17ee Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998@gmail.com> Date: Thu, 13 Aug 2026 22:08:41 +0700 Subject: [PATCH 18/19] Fix opainject patch indentation context --- .github/patches/opainject-build.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index a1c2038..8252e26 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -152,7 +152,7 @@ index 77acb84..f125a72 100644 } @@ -325,31 +351,27 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, - printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); + printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } - resume_threads_except_for(cachedThreads, cachedThreadCount, targetThread); From bfc7a0f73f80bc6065bec9d7b61f8efe3210c75e Mon Sep 17 00:00:00 2001 From: 7xj1998-cell <7xj1998-cell@users.noreply.github.com> Date: Fri, 14 Aug 2026 22:22:39 +0700 Subject: [PATCH 19/19] Fix opainject launchd handoff cleanup --- .github/patches/opainject-build.patch | 75 +++++---------- .../patches/opainject-serial-cleanup.patch | 91 +++++++++++++++++++ .github/workflows/roothide.yml | 3 +- 3 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 .github/patches/opainject-serial-cleanup.patch diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index 8252e26..974d7a6 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -100,7 +100,7 @@ diff --git a/rop_inject.h b/rop_inject.h index eb9294f..97cbb1d 100644 --- a/rop_inject.h +++ b/rop_inject.h -@@ -1,1 +1,1 @@ +@@ -1 +1 @@ -extern void injectDylibViaRop(task_t task, pid_t pid, const char* dylibPath, vm_address_t allImageInfoAddr); \ No newline at end of file +extern kern_return_t injectDylibViaRop(task_t task, pid_t pid, const char* dylibPath, vm_address_t allImageInfoAddr); @@ -108,7 +108,7 @@ 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,26 @@ 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); @@ -136,22 +136,23 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,6 +324,12 @@ 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)); -+ // All failures must restore the hijacked target thread before return. ++ // 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)); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); return kr; } -@@ -325,31 +351,27 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -325,10 +340,6 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, printf("[arbCall] pthread successfully did not return with code %d (%s)\n", kr, mach_error_string(kr)); } @@ -159,49 +160,9 @@ index 77acb84..f125a72 100644 - - vm_deallocate(mach_task_self(), (vm_offset_t)cachedThreads, sizeof(thread_act_array_t) * cachedThreadCount); - -+ if (willReturn) -+ { -+ // suspend target thread -+ thread_suspend(targetThread); -+ thread_abort(targetThread); -+ -+ // restore states of target thread to what they were before the arbitary call -+ bool restoreSuccess = thread_restore_state_arm64(targetThread, origThreadFullState); -+ if(!restoreSuccess) -+ { -+ printf("[arbCall] ERROR: failed to revert to old thread state\n"); -+ return kr; -+ } -+ -+ // resume thread again, process should continue executing as before -+ //printThreadState(targetThread); -+ thread_resume(targetThread); -+ } -+ // release fake stack as it's no longer needed vm_deallocate(task, remoteStack, STACK_SIZE); -- -- if (willReturn) -- { -- // suspend target thread -- thread_suspend(targetThread); -- thread_abort(targetThread); -- -- // restore states of target thread to what they were before the arbitary call -- bool restoreSuccess = thread_restore_state_arm64(targetThread, origThreadFullState); -- if(!restoreSuccess) -- { -- printf("[arbCall] ERROR: failed to revert to old thread state\n"); -- return kr; -- } -- -- // resume thread again, process should continue executing as before -- //printThreadState(targetThread); -- thread_resume(targetThread); -- } - return kr; - } @@ -364,33 +375,37 @@ void prepareForMagic(task_t task, vm_address_t allImageInfoAddr) printf("[prepareForMagic] done, ropLoop: 0x%llX\n", ropLoop); } @@ -235,7 +196,10 @@ index 77acb84..f125a72 100644 - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&readExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); + vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) { thread_terminate(pthread); return kr; } ++ 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); @@ -260,7 +224,10 @@ index 77acb84..f125a72 100644 - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&executableExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); + vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) { thread_terminate(pthread); return kr; } ++ 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); @@ -316,7 +283,10 @@ index 77acb84..f125a72 100644 + void* dlopenRet = NULL; + kr = arbCall(task, pthread, (uint64_t*)&dlopenRet, true, dlopenAddr, 2, remoteDylibPath, RTLD_NOW); + vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); -+ if (kr != KERN_SUCCESS) return kr; ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } if (dlopenRet) { printf("[injectDylibViaRop] dlopen succeeded, library handle: %p\n", dlopenRet); @@ -325,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) { thread_terminate(pthread); 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-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