From ee06075989b055138736a1502b4eaa99859aaa1d Mon Sep 17 00:00:00 2001 From: khanhduytran0 Date: Thu, 18 Apr 2024 07:46:40 +0700 Subject: [PATCH] Implement cert obtaining safely --- LCAppDelegate.m | 8 +++- LCJITLessSetupViewController.h | 5 ++ LCJITLessSetupViewController.m | 42 +++++++++++++++++ LCRootViewController.m | 26 +++++++++- LCUtils.h | 20 ++++++++ LCUtils.m | 86 ++++++++++++++++++++++++++++++++++ Makefile | 7 ++- entitlements.xml | 22 +++++---- entitlements_setup.xml | 18 +++++++ main.m | 14 ++---- 10 files changed, 225 insertions(+), 23 deletions(-) create mode 100644 LCJITLessSetupViewController.h create mode 100644 LCJITLessSetupViewController.m create mode 100644 LCUtils.h create mode 100644 LCUtils.m create mode 100644 entitlements_setup.xml diff --git a/LCAppDelegate.m b/LCAppDelegate.m index ea970cf..80efa94 100644 --- a/LCAppDelegate.m +++ b/LCAppDelegate.m @@ -1,10 +1,16 @@ #import "LCAppDelegate.h" +#import "LCJITLessSetupViewController.h" #import "LCRootViewController.h" @implementation LCAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - LCRootViewController *viewController = [[LCRootViewController alloc] init]; + UIViewController *viewController; + if ([NSBundle.mainBundle.executablePath.lastPathComponent isEqualToString:@"JITLessSetup"]) { + viewController = [[LCJITLessSetupViewController alloc] init]; + } else { + viewController = [[LCRootViewController alloc] init]; + } _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; _rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController]; _window.rootViewController = _rootViewController; diff --git a/LCJITLessSetupViewController.h b/LCJITLessSetupViewController.h new file mode 100644 index 0000000..392b637 --- /dev/null +++ b/LCJITLessSetupViewController.h @@ -0,0 +1,5 @@ +#import + +@interface LCJITLessSetupViewController : UIViewController + +@end diff --git a/LCJITLessSetupViewController.m b/LCJITLessSetupViewController.m new file mode 100644 index 0000000..2cec57c --- /dev/null +++ b/LCJITLessSetupViewController.m @@ -0,0 +1,42 @@ +#import "LCJITLessSetupViewController.h" +#import "LCUtils.h" +#import "UIKitPrivate.h" + +@implementation LCJITLessSetupViewController + +- (void)showDialogTitle:(NSString *)title message:(NSString *)message handler:(void(^)(UIAlertAction *))handler { + UIAlertController* alert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:handler]; + [alert addAction:okAction]; + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)loadView { + [super loadView]; + self.view.backgroundColor = UIColor.systemBackgroundColor; + self.title = @"LiveContainer JIT-less setup"; + + if (!LCUtils.storeCertPassword) { + [self showDialogTitle:@"Error" message:@"Failed to find certificate password" handler:nil]; + return; + } + + [LCUtils updateCertPassword]; + [LCUtils changeMainExecutableTo:@"LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou"]; + + NSError *error; + NSURL *url = [LCUtils archiveIPAWithError:&error]; + if (!url) { + [self showDialogTitle:@"Error" message:error.localizedDescription handler:nil]; + return; + } + + [self showDialogTitle:@"Instruction" message:@"Done. Press OK to finish setting up." + handler:^(UIAlertAction * action) { + [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:@"sidestore://install?url=%@", url]] options:@{} completionHandler:nil]; + }]; +} + +@end diff --git a/LCRootViewController.m b/LCRootViewController.m index c99b5cf..71ba029 100644 --- a/LCRootViewController.m +++ b/LCRootViewController.m @@ -1,5 +1,6 @@ #import #import "LCRootViewController.h" +#import "LCUtils.h" #import "MBRoundProgressView.h" #import "UIKitPrivate.h" #import "unarchive.h" @@ -139,7 +140,10 @@ - (void)loadView { [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(launchButtonTapped)], [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped)] ]; - self.navigationItem.leftBarButtonItems[0].enabled = NO; + + if (!LCUtils.certPassword) { + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Setup JIT-less" style:UIBarButtonItemStylePlain target:self action:@selector(setupJITLessTapped)]; + } } - (void)viewWillAppear:(BOOL)animated { @@ -189,6 +193,26 @@ - (void)showInputDialogTitle:(NSString *)title message:(NSString *)message place [self presentViewController:alert animated:YES completion:nil]; } +- (void)setupJITLessTapped { + if (!LCUtils.isAppGroupSideStore) { + [self showDialogTitle:@"Error" message:@"Unsupported installation method. Please use SideStore to setup this feature."]; + return; + } + + [LCUtils changeMainExecutableTo:@"JITLessSetup"]; + NSError *error; + NSURL *url = [LCUtils archiveIPAWithError:&error]; + if (!url) { + [self showDialogTitle:@"Error" message:error.localizedDescription]; + return; + } + + [self showDialogTitle:@"Instruction" message:@"Setting up JIT-less allows you to use LiveContainer without having to enable JIT. LiveContainer needs to safely obtain the certificate password from SideStore. Press OK to continue." + handler:^(UIAlertAction * action) { + [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:@"sidestore://install?url=%@", url]] options:@{} completionHandler:nil]; + }]; +} + - (void)addButtonTapped { UIDocumentPickerViewController* documentPickerVC = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[[UTType typeWithFilenameExtension:@"ipa" conformingToType:UTTypeData]]]; documentPickerVC.allowsMultipleSelection = YES; diff --git a/LCUtils.h b/LCUtils.h new file mode 100644 index 0000000..74a0a11 --- /dev/null +++ b/LCUtils.h @@ -0,0 +1,20 @@ +#import + +@interface PKZipArchiver : NSObject + +- (NSData *)zippedDataForURL:(NSURL *)url; + +@end + +@interface LCUtils : NSObject + ++ (NSString *)certPassword; ++ (void)updateCertPassword; ++ (NSData *)storeCertPassword; + ++ (BOOL)isAppGroupSideStore; ++ (NSError *)changeMainExecutableTo:(NSString *)exec; + ++ (NSURL *)archiveIPAWithError:(NSError **)error; + +@end diff --git a/LCUtils.m b/LCUtils.m new file mode 100644 index 0000000..e061df9 --- /dev/null +++ b/LCUtils.m @@ -0,0 +1,86 @@ +#import "LCUtils.h" +#include + +@implementation LCUtils + +#pragma mark Certificate password + ++ (NSString *)storeCertPassword { + NSDictionary *dict = @{ + (id)kSecClass: (id)kSecClassGenericPassword, + (id)kSecAttrService: @"com.SideStore.SideStore", + (id)kSecAttrAccount: @"signingCertificatePassword", + (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny, + (id)kSecMatchLimit: (id)kSecMatchLimitOne, + (id)kSecReturnData: (id)kCFBooleanTrue + }; + CFTypeRef result = nil; + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); + if (status == errSecSuccess) { + return [[NSString alloc] initWithData:(__bridge NSData *)result encoding:NSUTF8StringEncoding]; + } else { + return nil; + } +} + ++ (void)updateCertPassword { + [NSUserDefaults.standardUserDefaults setObject:self.storeCertPassword forKey:@"LCCertificateID"]; +} + ++ (NSString *)certPassword { + return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateID"]; +} + +#pragma mark Setup + ++ (NSString *)appGroupID { + return [NSBundle.mainBundle.infoDictionary[@"ALTAppGroups"] firstObject]; +} + ++ (BOOL)isAppGroupSideStore { + return [self.appGroupID containsString:@"com.SideStore.SideStore"]; +} + ++ (NSError *)changeMainExecutableTo:(NSString *)exec { + NSError *error; + NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; + NSURL *infoPath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer/App.app/Info.plist"]; + NSMutableDictionary *infoDict = [NSMutableDictionary dictionaryWithContentsOfURL:infoPath]; + if (!infoDict) return nil; + + infoDict[@"CFBundleExecutable"] = exec; + [infoDict writeToURL:infoPath error:&error]; + return error; +} + ++ (NSURL *)archiveIPAWithError:(NSError **)error { + NSFileManager *manager = NSFileManager.defaultManager; + NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; + NSURL *bundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer"]; + + NSURL *tmpPath = [appGroupPath URLByAppendingPathComponent:@"tmp"]; + [manager removeItemAtURL:tmpPath error:nil]; + + NSURL *tmpPayloadPath = [tmpPath URLByAppendingPathComponent:@"Payload"]; + NSURL *tmpIPAPath = [appGroupPath URLByAppendingPathComponent:@"tmp.ipa"]; + + [manager createDirectoryAtURL:tmpPath withIntermediateDirectories:YES attributes:nil error:error]; + if (*error) return nil; + + [manager copyItemAtURL:bundlePath toURL:tmpPayloadPath error:error]; + if (*error) return nil; + + dlopen("/System/Library/PrivateFrameworks/PassKitCore.framework/PassKitCore", RTLD_GLOBAL); + NSData *zipData = [[NSClassFromString(@"PKZipArchiver") new] zippedDataForURL:tmpPayloadPath.URLByDeletingLastPathComponent]; + if (!zipData) return nil; + + [manager removeItemAtURL:tmpPath error:error]; + if (*error) return nil; + + [zipData writeToURL:tmpIPAPath options:0 error:error]; + if (*error) return nil; + + return tmpIPAPath; +} + +@end diff --git a/Makefile b/Makefile index 3d8b67f..36f2897 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ CONFIG_BRANCH = $(shell git branch --show-current) CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1-7) # Build the UI library -LiveContainerUI_FILES = LCAppDelegate.m LCRootViewController.m MBRoundProgressView.m unarchive.m AppInfo.m +LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCRootViewController.m LCUtils.m MBRoundProgressView.m unarchive.m AppInfo.m LiveContainerUI_CFLAGS = \ -fobjc-arc \ -DCONFIG_TYPE=\"$(CONFIG_TYPE)\" \ @@ -31,14 +31,17 @@ include $(THEOS_MAKE_PATH)/library.mk # Build the app APPLICATION_NAME = LiveContainer + $(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m FixCydiaSubstrate.c fishhook/fishhook.c $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e_LiveContainerMain -rpath @loader_path/Frameworks $(APPLICATION_NAME)_FRAMEWORKS = UIKit -#$(APPLICATION_NAME)_INSTALL_PATH = /Applications/LiveContainer.app + include $(THEOS_MAKE_PATH)/application.mk # Make the executable name longer so we have space to overwrite it with the guest app's name before-package:: + @cp .theos/_/Applications/LiveContainer.app/LiveContainer .theos/_/Applications/LiveContainer.app/JITLessSetup + @ldid -Sentitlements_setup.xml .theos/_/Applications/LiveContainer.app/JITLessSetup @mv .theos/_/Applications/LiveContainer.app/LiveContainer .theos/_/Applications/LiveContainer.app/LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou diff --git a/entitlements.xml b/entitlements.xml index 75761ce..5b553f0 100644 --- a/entitlements.xml +++ b/entitlements.xml @@ -1,14 +1,18 @@ + - application-identifier - com.kdt.livecontainer - get-task-allow - - - com.apple.developer.kernel.extended-virtual-addressing - - com.apple.developer.kernel.increased-memory-limit - + application-identifier + com.kdt.livecontainer + com.apple.developer.kernel.extended-virtual-addressing + + com.apple.developer.kernel.increased-memory-limit + + com.apple.security.application-groups + + group.com.SideStore.SideStore + + get-task-allow + diff --git a/entitlements_setup.xml b/entitlements_setup.xml new file mode 100644 index 0000000..fa8aad2 --- /dev/null +++ b/entitlements_setup.xml @@ -0,0 +1,18 @@ + + + + + application-identifier + com.kdt.livecontainer + com.apple.security.application-groups + + group.com.SideStore.SideStore + + get-task-allow + + keychain-access-groups + + group.* + + + diff --git a/main.m b/main.m index 7286b78..a3f7572 100644 --- a/main.m +++ b/main.m @@ -167,14 +167,6 @@ static void overwriteExecPath(NSString *bundlePath) { static NSString* invokeAppMain(NSString *selectedApp, int argc, char *argv[]) { NSString *appError = nil; - // First of all, let's check if we have JIT - for (int i = 0; i < 10 && !checkJITEnabled(); i++) { - usleep(1000*100); - } - if (!checkJITEnabled()) { - appError = @"JIT was not enabled"; - return appError; - } NSFileManager *fm = NSFileManager.defaultManager; NSString *docPath = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] @@ -197,8 +189,10 @@ static void overwriteExecPath(NSString *bundlePath) { symlink(target.UTF8String, tweakLoaderPath.UTF8String); } - // Bypass library validation so we can load arbitrary binaries - init_bypassDyldLibValidation(); + // If JIT is enabled, bypass library validation so we can load arbitrary binaries + if (checkJITEnabled()) { + init_bypassDyldLibValidation(); + } // Bind _dyld_get_all_image_infos init_fixCydiaSubstrate();