Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
197 changes: 188 additions & 9 deletions Tweak.x
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
#include <unistd.h>
#include <substrate.h>
#include <rootless.h>
#include <spawn.h>
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>
#include <string.h>

extern char **environ;

Expand All @@ -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")];
Expand Down Expand Up @@ -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);
}
6 changes: 6 additions & 0 deletions XcodeRootDebug.plist
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{
Filter = {
Bundles = (
"com.apple.remoted",
"com.apple.lockdownd"
);
Executables = (
lockdownd,
remoted,
dtdebugproxyd
);
};
}
4 changes: 2 additions & 2 deletions layout/DEBIAN/control
Original file line number Diff line number Diff line change
@@ -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
Expand Down