diff --git a/RootHelper/Makefile b/RootHelper/Makefile index 2aeb2fc..b9c936f 100644 --- a/RootHelper/Makefile +++ b/RootHelper/Makefile @@ -14,6 +14,6 @@ trollstorehelper_CODESIGN_FLAGS = --entitlements entitlements.plist trollstorehelper_INSTALL_PATH = /usr/local/bin trollstorehelper_LIBRARIES = archive trollstorehelper_FRAMEWORKS = CoreTelephony -trollstorehelper_PRIVATE_FRAMEWORKS = SpringBoardServices BackBoardServices MobileContainerManager +trollstorehelper_PRIVATE_FRAMEWORKS = SpringBoardServices BackBoardServices MobileContainerManager FrontBoardServices include $(THEOS_MAKE_PATH)/tool.mk diff --git a/RootHelper/devmode.h b/RootHelper/devmode.h new file mode 100644 index 0000000..9343b61 --- /dev/null +++ b/RootHelper/devmode.h @@ -0,0 +1,4 @@ +#import + +BOOL checkDeveloperMode(void); +BOOL armDeveloperMode(BOOL* alreadyEnabled); \ No newline at end of file diff --git a/RootHelper/devmode.m b/RootHelper/devmode.m new file mode 100644 index 0000000..241ad0e --- /dev/null +++ b/RootHelper/devmode.m @@ -0,0 +1,142 @@ +@import Foundation; + +// Types +typedef NSObject* xpc_object_t; +typedef xpc_object_t xpc_connection_t; +typedef void (^xpc_handler_t)(xpc_object_t object); + +// Serialization +extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xpcattrs); +extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef attrs); +extern xpc_object_t _CFXPCCreateXPCMessageWithCFObject(CFTypeRef obj); +extern CFTypeRef _CFXPCCreateCFObjectFromXPCMessage(xpc_object_t obj); + +// Communication +extern xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, uint64_t flags); +extern void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); +extern void xpc_connection_resume(xpc_connection_t connection); +extern void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq, xpc_handler_t handler); +extern xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, xpc_object_t message); +extern xpc_object_t xpc_dictionary_get_value(xpc_object_t xdict, const char *key); + +typedef enum { + kAMFIActionArm = 0, // Trigger a prompt asking the user to enable developer mode on the next reboot + // (regardless of current state) + kAMFIActionDisable = 1, // Disable developer mode if it's currently enabled. Takes effect immediately. + kAMFIActionStatus = 2, // Returns a dict: {success: bool, status: bool, armed: bool} +} AMFIXPCAction; + +xpc_connection_t startConnection(void) { + xpc_connection_t connection = xpc_connection_create_mach_service("com.apple.amfi.xpc", NULL, 0); + if (!connection) { + NSLog(@"[startXPCConnection] Failed to create XPC connection to amfid"); + return nil; + } + xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { + }); + xpc_connection_resume(connection); + return connection; +} + +NSDictionary* sendXPCRequest(xpc_connection_t connection, AMFIXPCAction action) { + xpc_object_t message = _CFXPCCreateXPCMessageWithCFObject((__bridge CFDictionaryRef) @{@"action": @(action)}); + xpc_object_t replyMsg = xpc_connection_send_message_with_reply_sync(connection, message); + if (!replyMsg) { + NSLog(@"[sendXPCRequest] got no reply from amfid"); + return nil; + } + + xpc_object_t replyObj = xpc_dictionary_get_value(replyMsg, "cfreply"); + if (!replyObj) { + NSLog(@"[sendXPCRequest] got reply but no cfreply"); + return nil; + } + + NSDictionary* asCF = (__bridge NSDictionary*)_CFXPCCreateCFObjectFromXPCMessage(replyObj); + return asCF; +} + +BOOL getDeveloperModeState(xpc_connection_t connection) { + NSDictionary* reply = sendXPCRequest(connection, kAMFIActionStatus); + if (!reply) { + NSLog(@"[getDeveloperModeState] failed to get reply"); + return NO; + } + + NSLog(@"[getDeveloperModeState] got reply %@", reply); + + NSObject* success = reply[@"success"]; + if (!success || ![success isKindOfClass:[NSNumber class]] || ![(NSNumber*)success boolValue]) { + NSLog(@"[getDeveloperModeState] request failed with error %@", reply[@"error"]); + return NO; + } + + NSObject* status = reply[@"status"]; + if (!status || ![status isKindOfClass:[NSNumber class]]) { + NSLog(@"[getDeveloperModeState] request succeeded but no status"); + return NO; + } + + return [(NSNumber*)status boolValue]; +} + +BOOL setDeveloperModeState(xpc_connection_t connection, BOOL enable) { + NSDictionary* reply = sendXPCRequest(connection, enable ? kAMFIActionArm : kAMFIActionDisable); + if (!reply) { + NSLog(@"[setDeveloperModeState] failed to get reply"); + return NO; + } + + NSObject* success = reply[@"success"]; + if (!success || ![success isKindOfClass:[NSNumber class]] || ![(NSNumber*)success boolValue]) { + NSLog(@"[setDeveloperModeState] request failed with error %@", reply[@"error"]); + return NO; + } + + return YES; +} + +BOOL checkDeveloperMode(void) { + // Developer mode does not exist before iOS 16 + if (@available(iOS 16, *)) { + xpc_connection_t connection = startConnection(); + if (!connection) { + NSLog(@"[checkDeveloperMode] failed to start connection"); + // Assume it's disabled + return NO; + } + + return getDeveloperModeState(connection); + } else { + return YES; + } +} + +BOOL armDeveloperMode(BOOL* alreadyEnabled) { + // Developer mode does not exist before iOS 16 + if (@available(iOS 16, *)) { + xpc_connection_t connection = startConnection(); + if (!connection) { + NSLog(@"[armDeveloperMode] failed to start connection"); + return NO; + } + + BOOL enabled = getDeveloperModeState(connection); + if (alreadyEnabled) { + *alreadyEnabled = enabled; + } + + if (enabled) { + // NSLog(@"[armDeveloperMode] already enabled"); + return YES; + } + + BOOL success = setDeveloperModeState(connection, YES); + if (!success) { + NSLog(@"[armDeveloperMode] failed to arm"); + return NO; + } + } + + return YES; +} diff --git a/RootHelper/entitlements.plist b/RootHelper/entitlements.plist index 70c45bc..2a0d842 100644 --- a/RootHelper/entitlements.plist +++ b/RootHelper/entitlements.plist @@ -44,5 +44,9 @@ Uninstall UpdatePlaceholderMetadata + com.apple.private.amfi.developer-mode-control + + com.apple.frontboard.shutdown + diff --git a/RootHelper/main.m b/RootHelper/main.m index 4fccd28..7e339af 100644 --- a/RootHelper/main.m +++ b/RootHelper/main.m @@ -10,6 +10,7 @@ #import #import #import +#import "devmode.h" #ifndef EMBEDDED_ROOT_HELPER #import "codesign.h" #import "coretrust_bug.h" @@ -20,6 +21,7 @@ #endif #import +#import #import #ifdef EMBEDDED_ROOT_HELPER @@ -564,6 +566,10 @@ int signApp(NSString* appPath) } } + // On iOS 16+, binaries with certain entitlements requires developer mode to be enabled, so we'll check + // while we're fixing entitlements + BOOL requiresDevMode = NO; + NSURL* fileURL; NSDirectoryEnumerator *enumerator; @@ -608,6 +614,25 @@ int signApp(NSString* appPath) if (!entitlementsToUse) entitlementsToUse = [NSMutableDictionary new]; + // Developer mode does not exist before iOS 16 + if (@available(iOS 16, *)){ + if (!requiresDevMode) { + for (NSString* restrictedEntitlementKey in @[ + @"get-task-allow", + @"task_for_pid-allow", + @"com.apple.system-task-ports", + @"com.apple.system-task-ports.control", + @"com.apple.system-task-ports.token.control", + @"com.apple.private.cs.debugger" + ]) { + NSObject *restrictedEntitlement = entitlementsToUse[restrictedEntitlementKey]; + if (restrictedEntitlement && [restrictedEntitlement isKindOfClass:[NSNumber class]] && [(NSNumber *)restrictedEntitlement boolValue]) { + requiresDevMode = YES; + } + } + } + } + NSObject *containerRequiredO = entitlementsToUse[@"com.apple.private.security.container-required"]; BOOL containerRequired = YES; if (containerRequiredO && [containerRequiredO isKindOfClass:[NSNumber class]]) { @@ -686,6 +711,11 @@ int signApp(NSString* appPath) } } + if (requiresDevMode) { + // Postpone trying to enable dev mode until after the app is (successfully) installed + return 182; + } + return 0; } #endif @@ -770,10 +800,19 @@ int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, applyPatchesToInfoDictionary(appBundleToInstallPath); } + BOOL requiresDevMode = NO; + if(sign) { int signRet = signApp(appBundleToInstallPath); - if(signRet != 0) return signRet; + // 182: app requires developer mode; non-fatal + if(signRet != 0) { + if (signRet == 182) { + requiresDevMode = YES; + } else { + return signRet; + } + }; } MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil]; @@ -919,6 +958,23 @@ int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, [[NSFileManager defaultManager] removeItemAtURL:appContainer.url error:nil]; return 181; } + + // Handle developer mode after installing and registering the app, to ensure that we + // don't arm developer mode but then fail to install the app + if (requiresDevMode) { + BOOL alreadyEnabled = NO; + if (armDeveloperMode(&alreadyEnabled)) { + if (!alreadyEnabled) { + NSLog(@"[installApp] app requires developer mode and we have successfully armed it"); + // non-fatal + return 182; + } + } else { + NSLog(@"[installApp] failed to arm developer mode"); + // fatal + return 183; + } + } return 0; } @@ -1470,6 +1526,22 @@ int MAIN_NAME(int argc, char *argv[], char *envp[]) setTSURLSchemeState(newState, nil); } } + else if([cmd isEqualToString:@"check-dev-mode"]) + { + // switch the result, so 0 is enabled, and 1 is disabled/error + ret = !checkDeveloperMode(); + } + else if([cmd isEqualToString:@"arm-dev-mode"]) + { + // assumes that checkDeveloperMode() has already been called + ret = !armDeveloperMode(NULL); + } + else if([cmd isEqualToString:@"reboot"]) + { + [[FBSSystemService sharedService] reboot]; + // Give the system some time to reboot + sleep(1); + } NSLog(@"trollstorehelper returning %d", ret); return ret; diff --git a/TrollHelper/Makefile b/TrollHelper/Makefile index 0a01c68..812e4e3 100644 --- a/TrollHelper/Makefile +++ b/TrollHelper/Makefile @@ -34,7 +34,7 @@ ifeq ($(EMBEDDED_ROOT_HELPER),1) TrollStorePersistenceHelper_CFLAGS += -DEMBEDDED_ROOT_HELPER=1 TrollStorePersistenceHelper_FILES += $(wildcard ../RootHelper/*.m) TrollStorePersistenceHelper_LIBRARIES += archive -TrollStorePersistenceHelper_PRIVATE_FRAMEWORKS += SpringBoardServices BackBoardServices +TrollStorePersistenceHelper_PRIVATE_FRAMEWORKS += SpringBoardServices BackBoardServices FrontBoardServices endif include $(THEOS_MAKE_PATH)/application.mk \ No newline at end of file diff --git a/TrollStore/TSApplicationsManager.m b/TrollStore/TSApplicationsManager.m index 3fa5123..d6a0195 100644 --- a/TrollStore/TSApplicationsManager.m +++ b/TrollStore/TSApplicationsManager.m @@ -80,6 +80,12 @@ extern NSUserDefaults* trollStoreUserDefaults(); case 181: errorDescription = @"Failed to add app to icon cache."; break; + case 182: + errorDescription = @"The app was installed successfully, but requires developer mode to be enabled to run. After rebooting, select \"Turn On\" to enable developer mode."; + break; + case 183: + errorDescription = @"Failed to enable developer mode."; + break; } NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; diff --git a/TrollStore/TSInstallationController.m b/TrollStore/TSInstallationController.m index 0b3e674..4caf61f 100644 --- a/TrollStore/TSInstallationController.m +++ b/TrollStore/TSInstallationController.m @@ -32,42 +32,58 @@ extern NSUserDefaults* trollStoreUserDefaults(void); { [TSPresentationDelegate stopActivityWithCompletion:^ { - if(ret != 0) - { + if (ret == 0) { + // success + if(completionBlock) completionBlock(YES, nil); + } else if (ret == 171) { + // recoverable error UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Install Error %d", ret] message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { - if(ret == 171) - { - if(completionBlock) completionBlock(NO, error); - } + if(completionBlock) completionBlock(NO, error); }]; [errorAlert addAction:closeAction]; - if(ret == 171) + UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { - UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) - { - [self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock]; - }]; - [errorAlert addAction:forceInstallAction]; - } - else - { - UIAlertAction* copyLogAction = [UIAlertAction actionWithTitle:@"Copy Debug Log" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) - { - UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; - pasteboard.string = log; - }]; - [errorAlert addAction:copyLogAction]; - } + [self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock]; + }]; + [errorAlert addAction:forceInstallAction]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; - } + } else if (ret == 182) { + // non-fatal informative message + UIAlertController* rebootNotification = [UIAlertController alertControllerWithTitle:@"Reboot Required" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) + { + if(completionBlock) completionBlock(YES, nil); + }]; + [rebootNotification addAction:closeAction]; - if(ret != 171) - { - if(completionBlock) completionBlock((BOOL)error, error); + UIAlertAction* rebootAction = [UIAlertAction actionWithTitle:@"Reboot Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) + { + if(completionBlock) completionBlock(YES, nil); + spawnRoot(rootHelperPath(), @[@"reboot"], nil, nil); + }]; + [rebootNotification addAction:rebootAction]; + + [TSPresentationDelegate presentViewController:rebootNotification animated:YES completion:nil]; + } else { + // unrecoverable error + UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Install Error %d", ret] message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; + [errorAlert addAction:closeAction]; + + UIAlertAction* copyLogAction = [UIAlertAction actionWithTitle:@"Copy Debug Log" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) + { + UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.string = log; + }]; + [errorAlert addAction:copyLogAction]; + + [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; + + if(completionBlock) completionBlock(NO, error); } }]; }); diff --git a/TrollStore/TSSettingsListController.h b/TrollStore/TSSettingsListController.h index fc573ee..9cc397e 100644 --- a/TrollStore/TSSettingsListController.h +++ b/TrollStore/TSSettingsListController.h @@ -5,5 +5,6 @@ PSSpecifier* _installPersistenceHelperSpecifier; NSString* _newerVersion; NSString* _newerLdidVersion; + BOOL _devModeEnabled; } @end \ No newline at end of file diff --git a/TrollStore/TSSettingsListController.m b/TrollStore/TSSettingsListController.m index 6a97299..3b3efca 100644 --- a/TrollStore/TSSettingsListController.m +++ b/TrollStore/TSSettingsListController.m @@ -55,6 +55,16 @@ extern NSUserDefaults* trollStoreUserDefaults(void); } }); //} + + if (@available(iOS 16, *)) + { + _devModeEnabled = spawnRoot(rootHelperPath(), @[@"check-dev-mode"], nil, nil) == 0; + } + else + { + _devModeEnabled = YES; + } + [self reloadSpecifiers]; } - (NSMutableArray*)specifiers @@ -82,6 +92,26 @@ extern NSUserDefaults* trollStoreUserDefaults(void); [_specifiers addObject:updateTrollStoreSpecifier]; } + if(!_devModeEnabled) + { + PSSpecifier* enableDevModeGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; + enableDevModeGroupSpecifier.name = @"Developer Mode"; + [enableDevModeGroupSpecifier setProperty:@"Some apps require developer mode enabled to launch. This requires a reboot to take effect." forKey:@"footerText"]; + [_specifiers addObject:enableDevModeGroupSpecifier]; + + PSSpecifier* enableDevModeSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Enable Developer Mode" + target:self + set:nil + get:nil + detail:nil + cell:PSButtonCell + edit:nil]; + enableDevModeSpecifier.identifier = @"enableDevMode"; + [enableDevModeSpecifier setProperty:@YES forKey:@"enabled"]; + enableDevModeSpecifier.buttonAction = @selector(enableDevModePressed); + [_specifiers addObject:enableDevModeSpecifier]; + } + PSSpecifier* utilitiesGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; utilitiesGroupSpecifier.name = @"Utilities"; [utilitiesGroupSpecifier setProperty:@"If an app does not immediately appear after installation, respring here and it should appear afterwards." forKey:@"footerText"]; @@ -369,6 +399,37 @@ extern NSUserDefaults* trollStoreUserDefaults(void); [TSInstallationController installLdid]; } +- (void)enableDevModePressed +{ + int ret = spawnRoot(rootHelperPath(), @[@"arm-dev-mode"], nil, nil); + + if (ret == 0) { + UIAlertController* rebootNotification = [UIAlertController alertControllerWithTitle:@"Reboot Required" + message:@"After rebooting, select \"Turn On\" to enable developer mode." + preferredStyle:UIAlertControllerStyleAlert + ]; + UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) + { + [self reloadSpecifiers]; + }]; + [rebootNotification addAction:closeAction]; + + UIAlertAction* rebootAction = [UIAlertAction actionWithTitle:@"Reboot Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) + { + spawnRoot(rootHelperPath(), @[@"reboot"], nil, nil); + }]; + [rebootNotification addAction:rebootAction]; + + [TSPresentationDelegate presentViewController:rebootNotification animated:YES completion:nil]; + } else { + UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Error %d", ret] message:@"Failed to enable developer mode." preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; + [errorAlert addAction:closeAction]; + + [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; + } +} + - (void)installPersistenceHelperPressed { NSMutableArray* appCandidates = [NSMutableArray new];