Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 37 additions & 17 deletions .github/patches/opainject-build.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
+ }

Expand All @@ -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);
Expand All @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions .github/patches/opainject-cleanup.patch
Original file line number Diff line number Diff line change
@@ -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));
91 changes: 91 additions & 0 deletions .github/patches/opainject-serial-cleanup.patch
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 2 additions & 1 deletion .github/workflows/roothide.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions BaseBin/launchdhook/src/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -190,3 +194,4 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp,
roothide_launchd_postinit(firstLoad);
/********** roothide specfic ********/
}

10 changes: 6 additions & 4 deletions BaseBin/watchdoghook/src/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}