From cde8c2c682cc579e624f6392af81cc1e2a83c601 Mon Sep 17 00:00:00 2001 From: Haruka Date: Sat, 15 Nov 2025 03:54:20 +0800 Subject: [PATCH] Compatibility of iOS 17+ --- Makefile | 4 +- Tweak.x | 197 ++++++++++++++++++++++++++++++++++++++++-- XcodeRootDebug.plist | 6 ++ layout/DEBIAN/control | 4 +- 4 files changed, 198 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index cc2c6c1..3a5647f 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,12 @@ THEOS_DEVICE_PORT = 2222 ARCHS = arm64 arm64e ifeq ($(THEOS_PACKAGE_SCHEME), rootless) -TARGET = iphone:clang:14.5:15.0 # theos includes the iOS 14.5 SDK by default, it's ok +TARGET = iphone:clang:14.5:18.7.2 # theos includes the iOS 14.5 SDK by default, it's ok else TARGET = iphone:clang:14.5:9.0 endif -INSTALL_TARGET_PROCESSES = lockdownd +INSTALL_TARGET_PROCESSES = lockdownd remoted dtdebugproxyd include $(THEOS)/makefiles/common.mk diff --git a/Tweak.x b/Tweak.x index 8ed99a5..d95a034 100644 --- a/Tweak.x +++ b/Tweak.x @@ -4,6 +4,11 @@ #include #include #include +#include +#include +#include +#include +#include extern char **environ; @@ -14,6 +19,7 @@ static NSString * nsNotificationString = @"com.byteage.xcoderootdebug/preference static BOOL enabled; static NSString *debugserverPath; static BOOL isRootUser; +static BOOL isRemotedProcess = NO; static void reloadSettings() { NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:ROOT_PATH_NS(@"/var/mobile/Library/Preferences/com.byteage.xcoderootdebug.plist")]; @@ -112,20 +118,193 @@ bool hooked_SMJobSubmit(CFStringRef domain, CFDictionaryRef job, AuthorizationRe return original_SMJobSubmit(domain, (__bridge CFDictionaryRef)newJobInfo, auth, error); } +// Helper function to check if a path is a debugserver +static BOOL isDebugserverPath(const char *path) { + if (path == NULL) return NO; + NSString *pathStr = [NSString stringWithUTF8String:path]; + return [pathStr hasSuffix:@"/debugserver"]; +} + +// Helper function to redirect debugserver path +static const char* getRedirectedDebugserverPath(const char *originalPath) { + if (!enabled || !debugserverPath || debugserverPath.length == 0) { + return originalPath; + } + + if (access(debugserverPath.UTF8String, F_OK) != 0) { + LOG(@"Custom debugserver not found at: %@", debugserverPath); + return originalPath; + } + + NSString *origPath = [NSString stringWithUTF8String:originalPath]; + if ([origPath isEqualToString:debugserverPath]) { + return originalPath; // Already using custom + } + + if (!systemDebugserverPath) { + systemDebugserverPath = [origPath copy]; + LOG(@"Saved system debugserver path: %@", systemDebugserverPath); + } + + LOG(@"Redirecting from %@ to %@", origPath, debugserverPath); + return debugserverPath.UTF8String; +} + +// posix_spawn hook for iOS 17+ CoreDevice framework compatibility +// This catches debugserver launches from remoted process +int (*original_posix_spawn)(pid_t *restrict pid, const char *restrict path, + const posix_spawn_file_actions_t *restrict file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], + char *const envp[restrict]); + +int hooked_posix_spawn(pid_t *restrict pid, const char *restrict path, + const posix_spawn_file_actions_t *restrict file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], + char *const envp[restrict]) { + if (path == NULL || argv == NULL || argv[0] == NULL) { + return original_posix_spawn(pid, path, file_actions, attrp, argv, envp); + } + + // Check if this is a debugserver launch + if (!isDebugserverPath(path)) { + return original_posix_spawn(pid, path, file_actions, attrp, argv, envp); + } + + LOG(@"posix_spawn debugserver: %s", path); + + const char *newPath = getRedirectedDebugserverPath(path); + + if (newPath == path) { + // No redirection needed + return original_posix_spawn(pid, path, file_actions, attrp, argv, envp); + } + + // Create new argv with modified path + int argc = 0; + while (argv[argc] != NULL) argc++; + + char **newArgv = (char **)malloc((argc + 1) * sizeof(char *)); + newArgv[0] = (char *)newPath; + for (int i = 1; i < argc; i++) { + newArgv[i] = argv[i]; + } + newArgv[argc] = NULL; + + int result = original_posix_spawn(pid, newPath, file_actions, attrp, newArgv, envp); + + if (result == 0 && pid != NULL) { + LOG(@"Successfully spawned custom debugserver (PID: %d)", *pid); + } else if (result != 0) { + LOG(@"Failed to spawn debugserver: %d (%s)", result, strerror(result)); + } + + free(newArgv); + return result; +} + +// execve hook as fallback/alternative +int (*original_execve)(const char *path, char *const argv[], char *const envp[]); + +int hooked_execve(const char *path, char *const argv[], char *const envp[]) { + if (!isDebugserverPath(path)) { + return original_execve(path, argv, envp); + } + + LOG(@"execve debugserver: %s", path); + + const char *newPath = getRedirectedDebugserverPath(path); + + if (newPath == path) { + // No redirection needed + return original_execve(path, argv, envp); + } + + // Create new argv with modified path + int argc = 0; + if (argv) { + while (argv[argc] != NULL) argc++; + } + + char **newArgv = (char **)malloc((argc + 1) * sizeof(char *)); + newArgv[0] = (char *)newPath; + for (int i = 1; i < argc; i++) { + newArgv[i] = argv[i]; + } + newArgv[argc] = NULL; + + int result = original_execve(newPath, newArgv, envp); + + // Note: execve doesn't return on success, only on error + LOG(@"execve failed: %d (%s)", errno, strerror(errno)); + + free(newArgv); + return result; +} + %ctor { - LOG(@"loaded in %s (%d)", getprogname(), getpid()); + const char *processName = getprogname(); + LOG(@"loaded in %s (%d)", processName, getpid()); + + // Detect which process we're running in + if (strcmp(processName, "dtdebugproxyd") == 0) { + LOG(@"Detected dtdebugproxyd process - iOS 17+ debug proxy (THIS IS THE RIGHT ONE!)"); + } else if (strcmp(processName, "remoted") == 0) { + isRemotedProcess = YES; + LOG(@"Detected remoted process - iOS 17+ CoreDevice mode"); + } else if (strcmp(processName, "lockdownd") == 0) { + LOG(@"Detected lockdownd process - legacy mode"); + } + reloadSettings(); CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, notificationCallback, (CFStringRef)nsNotificationString, NULL, CFNotificationSuspensionBehaviorCoalesce); + // Hook SMJobSubmit for lockdownd (iOS < 17) MSImageRef image = MSGetImageByName("/System/Library/PrivateFrameworks/ServiceManagement.framework/ServiceManagement"); - if (!image) { - LOG("ServiceManagement framework not found, it is impossible"); - return; + if (image) { + void *smJobSubmit = MSFindSymbol(image, "_SMJobSubmit"); + if (smJobSubmit) { + MSHookFunction( + smJobSubmit, + (void *)hooked_SMJobSubmit, + (void **)&original_SMJobSubmit + ); + LOG(@"Successfully hooked SMJobSubmit"); + } else { + LOG(@"SMJobSubmit symbol not found"); + } + } else { + LOG(@"ServiceManagement framework not found"); + } + + // Hook posix_spawn for both lockdownd and remoted (universal hook for iOS 17+) + // This will catch debugserver launches from any process + void *posix_spawn_ptr = MSFindSymbol(NULL, "_posix_spawn"); + if (posix_spawn_ptr) { + MSHookFunction( + posix_spawn_ptr, + (void *)hooked_posix_spawn, + (void **)&original_posix_spawn + ); + LOG(@"Successfully hooked posix_spawn"); + } else { + LOG(@"posix_spawn symbol not found"); + } + + // Hook execve as well (some processes may use this instead) + void *execve_ptr = MSFindSymbol(NULL, "_execve"); + if (execve_ptr) { + MSHookFunction( + execve_ptr, + (void *)hooked_execve, + (void **)&original_execve + ); + LOG(@"Successfully hooked execve"); + } else { + LOG(@"execve symbol not found"); } - MSHookFunction( - MSFindSymbol(image, "_SMJobSubmit"), - (void *)hooked_SMJobSubmit, - (void **)&original_SMJobSubmit - ); + + LOG(@"XcodeRootDebug initialization complete (enabled=%d, debugserverPath=%@, isRootUser=%d)", enabled, debugserverPath, isRootUser); } diff --git a/XcodeRootDebug.plist b/XcodeRootDebug.plist index 81a6e51..7885040 100644 --- a/XcodeRootDebug.plist +++ b/XcodeRootDebug.plist @@ -1,7 +1,13 @@ { Filter = { + Bundles = ( + "com.apple.remoted", + "com.apple.lockdownd" + ); Executables = ( lockdownd, + remoted, + dtdebugproxyd ); }; } \ No newline at end of file diff --git a/layout/DEBIAN/control b/layout/DEBIAN/control index 68866eb..e73fe07 100644 --- a/layout/DEBIAN/control +++ b/layout/DEBIAN/control @@ -1,8 +1,8 @@ Package: com.byteage.xcoderootdebug Name: XcodeRootDebug -Version: 0.0.3 +Version: 0.0.4 Architecture: iphoneos-arm -Description: Allow Xcode to start a custom debugserver with root privileges to debug iOS apps. +Description: Allow Xcode to start a custom debugserver with root privileges to debug iOS apps. Now supports iOS 17+ via dtdebugproxyd hook. Maintainer: h4ck1n Author: h4ck1n Section: Tweaks