diff --git a/LCJITLessSetupViewController.m b/LCJITLessSetupViewController.m
index 4403c28..3a0d986 100644
--- a/LCJITLessSetupViewController.m
+++ b/LCJITLessSetupViewController.m
@@ -1,3 +1,4 @@
+@import Darwin;
#import "LCJITLessSetupViewController.h"
#import "LCUtils.h"
#import "UIKitPrivate.h"
@@ -15,6 +16,7 @@ - (void)showDialogTitle:(NSString *)title message:(NSString *)message handler:(v
- (void)loadView {
[super loadView];
+
self.view.backgroundColor = UIColor.systemBackgroundColor;
self.title = @"LiveContainer JIT-less setup";
@@ -25,7 +27,7 @@ - (void)loadView {
*/
NSData *certData = [LCUtils keychainItem:@"signingCertificate" ofStore:@"com.SideStore.SideStore"];
if (!certData) {
- [self showDialogTitle:@"Error" message:@"Failed to find certificate data" handler:nil];
+ [self showDialogTitle:@"Error" message:@"Failed to find certificate data. Refresh app in SideStore and try again." handler:nil];
return;
}
LCUtils.certificateData = certData;
@@ -37,13 +39,55 @@ - (void)loadView {
}
LCUtils.certificatePassword = [NSString stringWithUTF8String:certPassword.bytes];
+ // Verify that the certificate is usable
+ // Create a test app bundle
+ NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CertificateValidation"];
+ [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+ NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"];
+ NSString *tmpLibPath = [path stringByAppendingPathComponent:@"TestJITLess.dylib"];
+ NSString *tmpInfoPath = [path stringByAppendingPathComponent:@"Info.plist"];
+ [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil];
+ [NSFileManager.defaultManager copyItemAtPath:[NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"Frameworks/TestJITLess.dylib"] toPath:tmpLibPath error:nil];
+ NSMutableDictionary *info = NSBundle.mainBundle.infoDictionary.mutableCopy;
+ info[@"CFBundleExecutable"] = @"LiveContainer.tmp";
+ [info writeToFile:tmpInfoPath atomically:YES];
+
+ // Sign the test app bundle
+ [LCUtils signAppBundle:[NSURL fileURLWithPath:path]
+ completionHandler:^(BOOL success, NSError *_Nullable error) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (!success) {
+ [self showDialogTitle:@"Error while performing signing test" message:error.localizedDescription handler:nil];
+ } else {
+ // Attempt to load the signed library
+ void *handle = dlopen(tmpLibPath.UTF8String, RTLD_LAZY);
+ [self validateSigningTest:(handle != NULL)];
+ }
+ });
+ }];
+}
+
+- (void)validateSigningTest:(BOOL)loaded {
+ BOOL success = loaded && getenv("LC_JITLESS_TEST_LOADED");
+
NSError *error;
- NSURL *url = [LCUtils archiveIPAWithSetupMode:NO error:&error];
+ NSURL *url = [LCUtils archiveIPAWithSetupMode:!success error:&error];
if (!url) {
[self showDialogTitle:@"Error" message:error.localizedDescription handler:nil];
return;
}
+ if (!success) {
+ [self showDialogTitle:@"Error" message:@"The test library has failed to load. This means your certificate may be having issue. LiveContainer will try to repair it. SideStore will refresh LiveContainer and then you will try again. Press OK to continue."
+ handler:^(UIAlertAction * action) {
+ // Erase signingCertificate
+ [LCUtils deleteKeychainItem:@"signingCertificate" ofStore:@"com.rileytestut.AltStore"];
+ [LCUtils deleteKeychainItem:@"signingCertificate" ofStore:@"com.SideStore.SideStore"];
+ [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:@"sidestore://install?url=%@", url]] options:@{} completionHandler: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];
diff --git a/LCTweakListViewController.m b/LCTweakListViewController.m
index 2794147..ee3ba82 100644
--- a/LCTweakListViewController.m
+++ b/LCTweakListViewController.m
@@ -16,6 +16,10 @@ @interface LCTweakListViewController()
@implementation LCTweakListViewController
+- (instancetype)init {
+ return self = [super initWithStyle:UITableViewStyleGrouped];
+}
+
- (void)loadView {
[super loadView];
@@ -171,11 +175,13 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-/*
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
- return @"N items";
+ if (self.navigationController.viewControllers.firstObject == self) {
+ return @"This is the global folder. All tweaks put here will be injected to all guest apps. Create a new folder if you use app-specific tweaks.";
+ } else {
+ return @"This is the app-specific folder. Set the tweak folder and the guest app will pick them up recursively.";
+ }
}
-*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.objects.count;
diff --git a/LCUtils.h b/LCUtils.h
index 8f273c1..1e1b335 100644
--- a/LCUtils.h
+++ b/LCUtils.h
@@ -27,6 +27,7 @@ void LCPatchExecSlice(const char *path, struct mach_header_64 *header);
+ (NSString *)certificatePassword;
+ (void)setCertificateData:(NSData *)data;
+ (void)setCertificatePassword:(NSString *)password;
++ (BOOL)deleteKeychainItem:(NSString *)key ofStore:(NSString *)store;
+ (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store;
+ (BOOL)launchToGuestApp;
diff --git a/LCUtils.m b/LCUtils.m
index 2b67737..f671777 100644
--- a/LCUtils.m
+++ b/LCUtils.m
@@ -13,6 +13,17 @@ + (NSString *)appGroupPath {
return [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID].path;
}
++ (BOOL)deleteKeychainItem:(NSString *)key ofStore:(NSString *)store {
+ NSDictionary *dict = @{
+ (id)kSecClass: (id)kSecClassGenericPassword,
+ (id)kSecAttrService: store,
+ (id)kSecAttrAccount: key,
+ (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny
+ };
+ OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
+ return status == errSecSuccess;
+}
+
+ (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store {
NSDictionary *dict = @{
(id)kSecClass: (id)kSecClassGenericPassword,
@@ -78,6 +89,24 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url {
#pragma mark Code signing
++ (void)loadStoreFrameworksWithError:(NSError **)error {
+ NSArray *signerFrameworks = @[@"OpenSSL.framework", @"Roxas.framework", @"AltStoreCore.framework"];
+ NSURL *storeFrameworksPath = [self.storeBundlePath URLByAppendingPathComponent:@"Frameworks"];
+ for (NSString *framework in signerFrameworks) {
+ NSBundle *frameworkBundle = [NSBundle bundleWithURL:[storeFrameworksPath URLByAppendingPathComponent:framework]];
+ if (!frameworkBundle) {
+ //completionHandler(NO, error);
+ abort();
+ }
+ [frameworkBundle loadAndReturnError:error];
+ }
+}
+
++ (NSURL *)storeBundlePath {
+ NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID];
+ return [appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore/App.app"];
+}
+
+ (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL {
int32_t cpusubtype;
sysctlbyname("hw.cpusubtype", &cpusubtype, NULL, NULL, 0);
@@ -103,65 +132,24 @@ + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL {
continue;
}
- // We cannot use NSMutableData as it copies the whole file data instead of directly mapping
- int fd = open(fileURL.path.UTF8String, O_RDWR, (mode_t)0600);
- struct stat s;
- fstat(fd, &s);
- void *map = mmap(NULL, s.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
- uint32_t magic = *(uint32_t *)map;
- struct mach_header_64 *header = NULL;
-
- if (magic == FAT_CIGAM) {
- // Find compatible slice
- struct fat_header *fatHeader = (struct fat_header *)map;
- struct fat_arch *arch = (struct fat_arch *)(map + sizeof(struct fat_header));
- struct fat_arch *chosenArch = NULL;
- for (int i = 0; i < OSSwapInt32(fatHeader->nfat_arch); i++) {
- if (OSSwapInt32(arch->cputype) == CPU_TYPE_ARM64) {
- header = (struct mach_header_64 *)(map + OSSwapInt32(arch->offset));
- chosenArch = arch;
- if (OSSwapInt32(arch->cpusubtype) == cpusubtype) {
- break;
- }
+ // Remove LC_CODE_SIGNATURE
+ NSString *error = LCParseMachO(fileURL.path.UTF8String, ^(const char *path, struct mach_header_64 *header) {
+ uint8_t *imageHeaderPtr = (uint8_t *)header + sizeof(struct mach_header_64);
+ struct load_command *command = (struct load_command *)imageHeaderPtr;
+ for(int i = 0; i < header->ncmds > 0; i++) {
+ if (command->cmd == LC_CODE_SIGNATURE) {
+ struct linkedit_data_command *csCommand = (struct linkedit_data_command *)command;
+ void *csData = (void *)((uint8_t *)header + csCommand->dataoff);
+ // Nuke it.
+ NSLog(@"Removing code signature of %@", fileURL);
+ bzero(csData, csCommand->datasize);
+ break;
}
- arch = (struct fat_arch *)((void *)arch + sizeof(struct fat_arch));
- }
- if (header) {
- // Extract slice
- uint32_t offset = OSSwapInt32(chosenArch->offset);
- uint32_t size = OSSwapInt32(chosenArch->size);
- memmove(map, (void *)((uint64_t)map + offset), size);
- msync(map, size, MS_SYNC);
- ftruncate(fd, size);
- fstat(fd, &s);
- header = (struct mach_header_64 *)map;
+ command = (struct load_command *)((void *)command + command->cmdsize);
}
- } else if (magic == MH_MAGIC_64) {
- header = (struct mach_header_64 *)map;
- }
-
- if (!header || header->cputype != CPU_TYPE_ARM64 || header->filetype != MH_DYLIB) {
- munmap(map, s.st_size);
- close(fd);
- continue;
- }
-
- uint8_t *imageHeaderPtr = (uint8_t *)header + sizeof(struct mach_header_64);
- struct load_command *command = (struct load_command *)imageHeaderPtr;
- for(int i = 0; i < header->ncmds > 0; i++) {
- if (command->cmd == LC_CODE_SIGNATURE) {
- struct linkedit_data_command *csCommand = (struct linkedit_data_command *)command;
- void *csData = (void *)((uint8_t *)header + csCommand->dataoff);
- // Nuke it.
- NSLog(@"Removing code signature of %@", fileURL);
- bzero(csData, csCommand->datasize);
- msync(map, s.st_size, MS_SYNC);
- munmap(map, s.st_size);
- close(fd);
- break;
- }
- command = (struct load_command *)((void *)command + command->cmdsize);
+ });
+ if (error) {
+ NSLog(@"[Error] %@ (%@)", error, fileURL);
}
}
}
@@ -171,26 +159,10 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc
// I'm too lazy to reimplement signer, so let's borrow everything from SideStore
// For sure this will break in the future as SideStore team planned to rewrite it
- NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID];
- NSURL *storeBundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore/App.app"];
- NSURL *storeFrameworksPath = [storeBundlePath URLByAppendingPathComponent:@"Frameworks"];
NSURL *profilePath = [NSBundle.mainBundle URLForResource:@"embedded" withExtension:@"mobileprovision"];
// Load libraries from Documents, yeah
- NSArray *signerFrameworks = @[@"OpenSSL.framework", @"Roxas.framework", @"AltStoreCore.framework"];
- for (NSString *framework in signerFrameworks) {
- NSBundle *frameworkBundle = [NSBundle bundleWithURL:[storeFrameworksPath URLByAppendingPathComponent:framework]];
- if (!frameworkBundle) {
- //completionHandler(NO, error);
- abort();
- return nil;
- }
- [frameworkBundle loadAndReturnError:&error];
- if (error) {
- completionHandler(NO, error);
- return nil;
- }
- }
+ [self loadStoreFrameworksWithError:&error];
ALTCertificate *cert = [[NSClassFromString(@"ALTCertificate") alloc] initWithP12Data:self.certificateData password:self.certificatePassword];
ALTProvisioningProfile *profile = [[NSClassFromString(@"ALTProvisioningProfile") alloc] initWithURL:profilePath];
@@ -210,9 +182,7 @@ + (NSString *)appGroupID {
+ (BOOL)isAppGroupSideStore {
if (![self.appGroupID containsString:@"com.SideStore.SideStore"]) return NO;
- NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID];
- NSURL *storeBundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore/App.app"];
- return [NSFileManager.defaultManager fileExistsAtPath:storeBundlePath.path];
+ return [NSFileManager.defaultManager fileExistsAtPath:self.storeBundlePath.path];
}
+ (void)changeMainExecutableTo:(NSString *)exec error:(NSError **)error {
@@ -231,12 +201,19 @@ + (void)writeStoreIDToSetupExecutableWithError:(NSError **)error {
NSMutableData *data = [NSMutableData dataWithContentsOfURL:execPath options:0 error:error];
if (!data) return;
+ // We must get SideStore's exact application-identifier, otherwise JIT-less setup will bug out to hell for using the wrong, expired certificate
+ [self loadStoreFrameworksWithError:nil];
+ NSURL *profilePath = [self.storeBundlePath URLByAppendingPathComponent:@"embedded.mobileprovision"];
+ ALTProvisioningProfile *profile = [[NSClassFromString(@"ALTProvisioningProfile") alloc] initWithURL:profilePath];
+ NSString *storeKeychainID = profile.entitlements[@"application-identifier"];
+ assert(storeKeychainID);
+
NSData *findPattern = [@"KeychainAccessGroupWillBeWrittenByLiveContainerAAAAAAAAAAAAAAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding];
NSRange range = [data rangeOfData:findPattern options:0 range:NSMakeRange(0, data.length)];
if (range.location == NSNotFound) return;
memset((char *)data.mutableBytes + range.location, ' ', range.length);
- NSString *replacement = [NSString stringWithFormat:@"%@", self.appGroupID];
+ NSString *replacement = [NSString stringWithFormat:@"%@", storeKeychainID];
assert(replacement.length < range.length);
memcpy((char *)data.mutableBytes + range.location, replacement.UTF8String, replacement.length);
[data writeToURL:execPath options:0 error:error];
diff --git a/Makefile b/Makefile
index cc45890..56fd408 100644
--- a/Makefile
+++ b/Makefile
@@ -22,12 +22,15 @@ LiveContainerUI_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks
# Build the tweak loader
TweakLoader_FILES = TweakLoader.m
-TweakLoader_CFLAGS = \
- -fobjc-arc
-TweakLoader_FRAMEWORKS = Foundation
+TweakLoader_CFLAGS = -fobjc-arc
TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks
-LIBRARY_NAME = LiveContainerUI TweakLoader
+# Build the test library
+TestJITLess_FILES = TestJITLess.m
+TestJITLess_CFLAGS = -fobjc-arc
+TestJITLess_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks
+
+LIBRARY_NAME = LiveContainerUI TweakLoader TestJITLess
include $(THEOS_MAKE_PATH)/library.mk
# Build the app
diff --git a/README.md b/README.md
index a82139e..e42ba13 100644
--- a/README.md
+++ b/README.md
@@ -74,9 +74,8 @@ To install tweaks, you can use the built-in tweak manager in LiveContainer, whic
- File picker might be broken for unknown reasons.
## TODO
-- Auto lock orientation
-- Simulate App Group(?)
-- More(?)
+- Isolate Keychain per app
+- Use Ch0ma instead of custom MachO parser
## License
[Apache License 2.0](https://github.com/khanhduytran0/LiveContainer/blob/main/LICENSE)
diff --git a/Resources/Info.plist b/Resources/Info.plist
index 1647ddf..0c9555c 100644
--- a/Resources/Info.plist
+++ b/Resources/Info.plist
@@ -42,7 +42,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2.1
+ 2.1.1
CFBundleSignature
????
CFBundleSupportedPlatforms
@@ -61,7 +61,7 @@
CFBundleVersion
- 2.1
+ 2.1.1
LSApplicationQueriesSchemes
sidestore
diff --git a/Resources/Root.plist b/Resources/Root.plist
index b614773..aa18b76 100644
--- a/Resources/Root.plist
+++ b/Resources/Root.plist
@@ -45,6 +45,8 @@
PSGroupCell
label
Miscellaneous
+ footerText
+ Frame shortcut icons with LiveContainer icon.
cell
@@ -81,6 +83,8 @@
PSGroupCell
label
About me
+ footerText
+ Don't use LiveContainer for piracy.
action
diff --git a/TestJITLess.m b/TestJITLess.m
new file mode 100644
index 0000000..f289cdd
--- /dev/null
+++ b/TestJITLess.m
@@ -0,0 +1,7 @@
+@import Foundation;
+
+ __attribute__((constructor))
+static void TestJITLessConstructor() {
+ NSLog(@"JIT-less test succeed");
+ setenv("LC_JITLESS_TEST_LOADED", "1", 1);
+}
diff --git a/control b/control
index 3737c4d..f053343 100644
--- a/control
+++ b/control
@@ -1,6 +1,6 @@
Package: com.kdt.livecontainer
Name: livecontainer
-Version: 2.1
+Version: 2.1.1
Architecture: iphoneos-arm
Description: Run iOS app without actually installing it!
Maintainer: khanhduytran0