|
| 1 | +#import <CoreFoundation/CoreFoundation.h> |
| 2 | +#import <Foundation/Foundation.h> |
| 3 | +#import <Foundation/NSUserDefaults+Private.h> |
| 4 | +#include <unistd.h> |
| 5 | +#include <substrate.h> |
| 6 | + |
| 7 | +extern char **environ; |
| 8 | + |
| 9 | +#define LOG(fmt, ...) NSLog(@"[XcodeRootDebug] " fmt "\n", ##__VA_ARGS__) |
| 10 | + |
| 11 | +static NSString * nsDomainString = @"com.byteage.xcoderootdebug"; |
| 12 | +static NSString * nsNotificationString = @"com.byteage.xcoderootdebug/preferences.changed"; |
| 13 | +static BOOL enabled; |
| 14 | +static NSString *debugserverPath; |
| 15 | +static BOOL isRootUser; |
| 16 | + |
| 17 | +static void reloadSettings() { |
| 18 | + NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:@"/private/var/mobile/Library/Preferences/com.byteage.xcoderootdebug.plist"]; |
| 19 | + NSNumber * enabledValue = (NSNumber *)[settings objectForKey:@"enabled"]; |
| 20 | + enabled = (enabledValue)? [enabledValue boolValue] : YES; |
| 21 | + debugserverPath = [settings objectForKey:@"debugserverPath"]; |
| 22 | + if(!debugserverPath.length) { |
| 23 | + debugserverPath = @"/usr/bin/debugserver"; |
| 24 | + } |
| 25 | + NSNumber * isRootUserValue = (NSNumber *)[settings objectForKey:@"isRootUser"]; |
| 26 | + isRootUser = (isRootUserValue)? [isRootUserValue boolValue] : YES; |
| 27 | +} |
| 28 | + |
| 29 | +static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { |
| 30 | + // kill self |
| 31 | + exit(0); |
| 32 | +} |
| 33 | + |
| 34 | +// If the compiler understands __arm64e__, assume it's paired with an SDK that has |
| 35 | +// ptrauth.h. Otherwise, it'll probably error if we try to include it so don't. |
| 36 | +#if __arm64e__ |
| 37 | +#include <ptrauth.h> |
| 38 | +#endif |
| 39 | + |
| 40 | +#pragma clang diagnostic push |
| 41 | +#pragma clang diagnostic ignored "-Wunused-function" |
| 42 | + |
| 43 | +// Given a pointer to instructions, sign it so you can call it like a normal fptr. |
| 44 | +static void *make_sym_callable(void *ptr) { |
| 45 | +#if __arm64e__ |
| 46 | + ptr = ptrauth_sign_unauthenticated(ptrauth_strip(ptr, ptrauth_key_function_pointer), ptrauth_key_function_pointer, 0); |
| 47 | +#endif |
| 48 | + return ptr; |
| 49 | +} |
| 50 | + |
| 51 | +// Given a function pointer, strip the PAC so you can read the instructions. |
| 52 | +static void *make_sym_readable(void *ptr) { |
| 53 | +#if __arm64e__ |
| 54 | + ptr = ptrauth_strip(ptr, ptrauth_key_function_pointer); |
| 55 | +#endif |
| 56 | + return ptr; |
| 57 | +} |
| 58 | + |
| 59 | +#pragma clang diagnostic pop |
| 60 | + |
| 61 | +typedef CFTypeRef AuthorizationRef; |
| 62 | + |
| 63 | +bool (*original_SMJobSubmit)(CFStringRef domain, CFDictionaryRef job, AuthorizationRef auth, CFErrorRef _Nullable *error); |
| 64 | + |
| 65 | +bool hooked_SMJobSubmit(CFStringRef domain, CFDictionaryRef job, AuthorizationRef auth, CFErrorRef _Nullable *error) { |
| 66 | + LOG(@"Enter hooked_SMJobSubmit %@", job); |
| 67 | + NSMutableDictionary *newJobInfo = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)job]; |
| 68 | + NSMutableArray *programArgs = [newJobInfo[@"ProgramArguments"] mutableCopy]; |
| 69 | + NSString *program = programArgs[0]; |
| 70 | + if (enabled) { |
| 71 | + if([program isEqualToString:@"/Developer/usr/bin/debugserver"]) { |
| 72 | + LOG("Found launch /Developer/usr/bin/debugserver"); |
| 73 | + if(debugserverPath.length > 0 && access(debugserverPath.UTF8String, F_OK | X_OK) == 0){ |
| 74 | + LOG("Change to launch %@", debugserverPath); |
| 75 | + programArgs[0] = debugserverPath; |
| 76 | + newJobInfo[@"ProgramArguments"] = programArgs; |
| 77 | + } else { |
| 78 | + LOG("Debug Server does not exist at %@, or does not have executable permissions", debugserverPath); |
| 79 | + } |
| 80 | + if(isRootUser) { |
| 81 | + LOG("Change to launch with root"); |
| 82 | + newJobInfo[@"UserName"] = @"root"; |
| 83 | + } else { |
| 84 | + newJobInfo[@"UserName"] = @"mobile"; |
| 85 | + } |
| 86 | + LOG(@"Now SMJobSubmit %@", newJobInfo); |
| 87 | + } else if([program isEqualToString:debugserverPath]) { |
| 88 | + LOG("Found launch %@",debugserverPath); |
| 89 | + if(isRootUser) { |
| 90 | + LOG("Change to launch with root"); |
| 91 | + newJobInfo[@"UserName"] = @"root"; |
| 92 | + } else { |
| 93 | + newJobInfo[@"UserName"] = @"mobile"; |
| 94 | + } |
| 95 | + LOG(@"Now SMJobSubmit %@", newJobInfo); |
| 96 | + } |
| 97 | + } else { |
| 98 | + if([program isEqualToString:debugserverPath]) { |
| 99 | + LOG("Found launch %@",debugserverPath); |
| 100 | + LOG("Restore launch /Developer/usr/bin/debugserver with mobile"); |
| 101 | + programArgs[0] = @"/Developer/usr/bin/debugserver"; |
| 102 | + newJobInfo[@"ProgramArguments"] = programArgs; |
| 103 | + newJobInfo[@"UserName"] = @"mobile"; |
| 104 | + LOG(@"Now SMJobSubmit %@", newJobInfo); |
| 105 | + } |
| 106 | + } |
| 107 | + LOG(@"New SMJobSubmit %@", newJobInfo); |
| 108 | + return original_SMJobSubmit(domain, (__bridge CFDictionaryRef)newJobInfo, auth, error); |
| 109 | +} |
| 110 | + |
| 111 | +%ctor { |
| 112 | + LOG(@"loaded in %s (%d)", getprogname(), getpid()); |
| 113 | + reloadSettings(); |
| 114 | + |
| 115 | + CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, notificationCallback, (CFStringRef)nsNotificationString, NULL, CFNotificationSuspensionBehaviorCoalesce); |
| 116 | + |
| 117 | + MSImageRef image = MSGetImageByName("/System/Library/PrivateFrameworks/ServiceManagement.framework/ServiceManagement"); |
| 118 | + if (!image) { |
| 119 | + LOG("ServiceManagement framework not found, it is impossible"); |
| 120 | + return; |
| 121 | + } |
| 122 | + MSHookFunction( |
| 123 | + MSFindSymbol(image, "_SMJobSubmit"), |
| 124 | + (void *)hooked_SMJobSubmit, |
| 125 | + (void **)&original_SMJobSubmit |
| 126 | + ); |
| 127 | +} |
0 commit comments