Add code to check and arm developer mode

This commit is contained in:
Dhinak G 2023-11-30 19:51:51 -05:00
parent 6094bc024f
commit 2ac6bc280f
No known key found for this signature in database
5 changed files with 244 additions and 27 deletions

4
RootHelper/devmode.h Normal file
View File

@ -0,0 +1,4 @@
#import <Foundation/Foundation.h>
BOOL checkDeveloperMode(void);
BOOL armDeveloperMode(BOOL* alreadyEnabled);

139
RootHelper/devmode.m Normal file
View File

@ -0,0 +1,139 @@
@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,
kAMFIActionDisable = 1,
kAMFIActionStatus = 2,
} 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;
}
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;
}

View File

@ -10,6 +10,7 @@
#import <sys/utsname.h> #import <sys/utsname.h>
#import <mach-o/loader.h> #import <mach-o/loader.h>
#import <mach-o/fat.h> #import <mach-o/fat.h>
#import "devmode.h"
#ifndef EMBEDDED_ROOT_HELPER #ifndef EMBEDDED_ROOT_HELPER
#import "codesign.h" #import "codesign.h"
#import "coretrust_bug.h" #import "coretrust_bug.h"
@ -564,6 +565,10 @@ int signApp(NSString* appPath)
} }
} }
// On iOS 16+, any binary with get-task-allow requires developer mode to be enabled, so we will check
// while we're at it
BOOL requiresDevMode = NO;
NSURL* fileURL; NSURL* fileURL;
NSDirectoryEnumerator *enumerator; NSDirectoryEnumerator *enumerator;
@ -608,6 +613,14 @@ int signApp(NSString* appPath)
if (!entitlementsToUse) entitlementsToUse = [NSMutableDictionary new]; if (!entitlementsToUse) entitlementsToUse = [NSMutableDictionary new];
// Developer mode does not exist before iOS 16
if (@available(iOS 16, *)){
NSObject *getTaskAllowO = entitlementsToUse[@"get-task-allow"];
if (getTaskAllowO && [getTaskAllowO isKindOfClass:[NSNumber class]]) {
requiresDevMode |= [(NSNumber *)getTaskAllowO boolValue];
}
}
NSObject *containerRequiredO = entitlementsToUse[@"com.apple.private.security.container-required"]; NSObject *containerRequiredO = entitlementsToUse[@"com.apple.private.security.container-required"];
BOOL containerRequired = YES; BOOL containerRequired = YES;
if (containerRequiredO && [containerRequiredO isKindOfClass:[NSNumber class]]) { if (containerRequiredO && [containerRequiredO isKindOfClass:[NSNumber class]]) {
@ -681,6 +694,11 @@ int signApp(NSString* appPath)
} }
} }
if (requiresDevMode) {
// Postpone trying to enable dev mode until after the app is (successfully) installed
return 180;
}
return 0; return 0;
} }
#endif #endif
@ -764,10 +782,19 @@ int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate,
applyPatchesToInfoDictionary(appBundleToInstallPath); applyPatchesToInfoDictionary(appBundleToInstallPath);
} }
BOOL requiresDevMode = NO;
if(sign) if(sign)
{ {
int signRet = signApp(appBundleToInstallPath); int signRet = signApp(appBundleToInstallPath);
if(signRet != 0) return signRet; // 180: app requires developer mode; non-fatal
if(signRet != 0) {
if (signRet == 180) {
requiresDevMode = YES;
} else {
return signRet;
}
};
} }
MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil]; MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil];
@ -910,6 +937,23 @@ int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate,
NSURL* updatedAppURL = findAppURLInBundleURL(appContainer.url); NSURL* updatedAppURL = findAppURLInBundleURL(appContainer.url);
fixPermissionsOfAppBundle(updatedAppURL.path); fixPermissionsOfAppBundle(updatedAppURL.path);
registerPath(updatedAppURL.path, 0, YES); registerPath(updatedAppURL.path, 0, YES);
// 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 180;
}
} else {
NSLog(@"[installApp] failed to arm developer mode");
// fatal
return 181;
}
}
return 0; return 0;
} }
@ -1460,6 +1504,15 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
setTSURLSchemeState(newState, nil); setTSURLSchemeState(newState, nil);
} }
} }
else if([cmd isEqualToString:@"check-dev-mode"])
{
ret = checkDeveloperMode();
}
else if([cmd isEqualToString:@"arm-dev-mode"])
{
// assumes that checkDeveloperMode() has already been called
ret = armDeveloperMode(NULL);
}
NSLog(@"trollstorehelper returning %d", ret); NSLog(@"trollstorehelper returning %d", ret);
return ret; return ret;

View File

@ -74,6 +74,12 @@ extern NSUserDefaults* trollStoreUserDefaults();
case 179: case 179:
errorDescription = @"The app you tried to install has the same identifier as a system app already installed on the device. The installation has been prevented to protect you from possible bootloops or other issues."; errorDescription = @"The app you tried to install has the same identifier as a system app already installed on the device. The installation has been prevented to protect you from possible bootloops or other issues.";
break; break;
case 180:
errorDescription = @"The app was installed successfully, but requires developer mode to be enabled to run.";
break;
case 181:
errorDescription = @"Failed to enable developer mode.";
break;
} }
NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}];

View File

@ -32,42 +32,57 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
{ {
[TSPresentationDelegate stopActivityWithCompletion:^ [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]; 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) 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]; [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];
{ }];
[self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock]; [errorAlert addAction:forceInstallAction];
}];
[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];
}
[TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
} } else if (ret == 180) {
// non-fatal informative message
UIAlertController* rebootNotification = [UIAlertController alertControllerWithTitle:@"Reboot Required" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{
if(completionBlock) completionBlock(YES, nil);
}];
[rebootNotification addAction:closeAction];
if(ret != 171) UIAlertAction* rebootAction = [UIAlertAction actionWithTitle:@"Reboot Now" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{ {
if(completionBlock) completionBlock((BOOL)error, error); if(completionBlock) completionBlock(YES, 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);
} }
}]; }];
}); });