Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 127 additions & 36 deletions RootHelper/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,13 @@ int signApp(NSString* appPath)
}
#endif

void applyPatchesToInfoDictionary(NSString* appPath)
NSString* randomStealthBundleIdentifierSuffix(void)
{
uint64_t randomValue = ((uint64_t)arc4random() << 32) | arc4random();
return [NSString stringWithFormat:@"TS_%016llX", randomValue];
}

void applyPatchesToInfoDictionary(NSString* appPath, BOOL stealthInstall, BOOL stripURLSchemesInStealth)
{
NSURL* appURL = [NSURL fileURLWithPath:appPath];
NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"];
Expand All @@ -817,41 +823,84 @@ void applyPatchesToInfoDictionary(NSString* appPath)
// Enable Notifications
infoDictM[@"SBAppUsesLocalNotifications"] = @1;

// Remove system claimed URL schemes if existant
NSSet* appleSchemes = systemURLSchemes();
NSArray* CFBundleURLTypes = infoDictM[@"CFBundleURLTypes"];
if([CFBundleURLTypes isKindOfClass:[NSArray class]])
if(stealthInstall)
{
NSMutableArray* CFBundleURLTypesM = [NSMutableArray new];
NSString* originalAppId = infoDictM[@"TSOriginalBundleIdentifier"];
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0)
{
originalAppId = infoDictM[@"CFBundleIdentifier"];
}

for(NSDictionary* URLType in CFBundleURLTypes)
if([originalAppId isKindOfClass:NSString.class] && originalAppId.length > 0)
{
if(![URLType isKindOfClass:[NSDictionary class]]) continue;
NSString* stealthAppIdToUse = installedStealthAppIdForOriginalAppId(originalAppId);
if(!stealthAppIdToUse)
{
NSString* currentAppId = infoDictM[@"CFBundleIdentifier"];
NSString* currentOriginalAppId = infoDictM[@"TSOriginalBundleIdentifier"];
BOOL hasExistingStealthIdInInfoPlist = [currentAppId isKindOfClass:NSString.class] &&
currentAppId.length > 0 &&
![currentAppId isEqualToString:originalAppId] &&
[currentOriginalAppId isKindOfClass:NSString.class] &&
[currentOriginalAppId isEqualToString:originalAppId];
if(hasExistingStealthIdInInfoPlist)
{
stealthAppIdToUse = currentAppId;
}
else
{
stealthAppIdToUse = [originalAppId stringByAppendingFormat:@".%@", randomStealthBundleIdentifierSuffix()];
}
}

infoDictM[@"TSOriginalBundleIdentifier"] = originalAppId;
infoDictM[@"CFBundleIdentifier"] = stealthAppIdToUse;
}

NSMutableDictionary* modifiedURLType = URLType.mutableCopy;
NSArray* URLSchemes = URLType[@"CFBundleURLSchemes"];
if(URLSchemes)
if(stripURLSchemesInStealth)
{
// Strip URL schemes in stealth mode.
[infoDictM removeObjectForKey:@"CFBundleURLTypes"];
}
}
else
{
// Remove system claimed URL schemes if existant
NSSet* appleSchemes = systemURLSchemes();
NSArray* CFBundleURLTypes = infoDictM[@"CFBundleURLTypes"];
if([CFBundleURLTypes isKindOfClass:[NSArray class]])
{
NSMutableArray* CFBundleURLTypesM = [NSMutableArray new];

for(NSDictionary* URLType in CFBundleURLTypes)
{
NSMutableSet* URLSchemesSet = [NSMutableSet setWithArray:URLSchemes];
for(NSString* existingURLScheme in [URLSchemesSet copy])
if(![URLType isKindOfClass:[NSDictionary class]]) continue;

NSMutableDictionary* modifiedURLType = URLType.mutableCopy;
NSArray* URLSchemes = URLType[@"CFBundleURLSchemes"];
if(URLSchemes)
{
if(![existingURLScheme isKindOfClass:[NSString class]])
NSMutableSet* URLSchemesSet = [NSMutableSet setWithArray:URLSchemes];
for(NSString* existingURLScheme in [URLSchemesSet copy])
{
[URLSchemesSet removeObject:existingURLScheme];
continue;
}
if(![existingURLScheme isKindOfClass:[NSString class]])
{
[URLSchemesSet removeObject:existingURLScheme];
continue;
}

if([appleSchemes containsObject:existingURLScheme.lowercaseString])
{
[URLSchemesSet removeObject:existingURLScheme];
if([appleSchemes containsObject:existingURLScheme.lowercaseString])
{
[URLSchemesSet removeObject:existingURLScheme];
}
}
modifiedURLType[@"CFBundleURLSchemes"] = [URLSchemesSet allObjects];
}
modifiedURLType[@"CFBundleURLSchemes"] = [URLSchemesSet allObjects];
[CFBundleURLTypesM addObject:modifiedURLType.copy];
}
[CFBundleURLTypesM addObject:modifiedURLType.copy];
}

infoDictM[@"CFBundleURLTypes"] = CFBundleURLTypesM.copy;
infoDictM[@"CFBundleURLTypes"] = CFBundleURLTypesM.copy;
}
}

[infoDictM writeToURL:infoPlistURL error:nil];
Expand All @@ -864,29 +913,54 @@ void applyPatchesToInfoDictionary(NSString* appPath)
// 174:
// 180: tried to sign app where the main binary is encrypted
// 184: tried to sign app where an additional binary is encrypted
// 186: stealth/non-stealth install mode conflicts with an existing install

int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, BOOL useInstalldMethod, BOOL skipUICache)
int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, BOOL useInstalldMethod, BOOL skipUICache, BOOL stealthInstall, BOOL stripURLSchemesInStealth)
{
NSLog(@"[installApp force = %d]", force);
NSLog(@"[installApp force = %d, stealthInstall = %d]", force, stealthInstall);

NSString* appPayloadPath = [appPackagePath stringByAppendingPathComponent:@"Payload"];

NSString* appBundleToInstallPath = findAppPathInBundlePath(appPayloadPath);
if(!appBundleToInstallPath) return 167;

NSString* appId = appIdForAppPath(appBundleToInstallPath);
if(!appId) return 176;
NSDictionary* appInfoDict = infoDictionaryForAppPath(appBundleToInstallPath);
if(!appInfoDict) return 172;

NSString* originalAppId = appInfoDict[@"CFBundleIdentifier"];
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0) return 176;

if(([appId.lowercaseString isEqualToString:@"com.opa334.trollstore"] && !isTSUpdate) || [immutableAppBundleIdentifiers() containsObject:appId.lowercaseString])
NSSet* immutableBundleIds = immutableAppBundleIdentifiers();
BOOL isTrollStoreIdentifier = [originalAppId.lowercaseString isEqualToString:@"com.opa334.trollstore"] || [originalAppId.lowercaseString hasPrefix:@"com.opa334.trollstore."];
if((isTrollStoreIdentifier && !isTSUpdate) || [immutableBundleIds containsObject:originalAppId.lowercaseString])
{
return 179;
}

if(!infoDictionaryForAppPath(appBundleToInstallPath)) return 172;

if(!isTSUpdate)
{
applyPatchesToInfoDictionary(appBundleToInstallPath);
NSString* existingStealthAppId = installedStealthAppIdForOriginalAppId(originalAppId);
BOOL hasStealthInstall = [existingStealthAppId isKindOfClass:NSString.class] && existingStealthAppId.length > 0;
BOOL hasNonStealthInstall = [LSApplicationProxy applicationProxyForIdentifier:originalAppId].installed;

// Disallow mixing install modes for the same original app identifier.
if((stealthInstall && hasNonStealthInstall) || (!stealthInstall && hasStealthInstall))
{
return 186;
}
}

if(!isTSUpdate || stealthInstall)
{
applyPatchesToInfoDictionary(appBundleToInstallPath, stealthInstall, stripURLSchemesInStealth);
}

NSString* appId = appIdForAppPath(appBundleToInstallPath);
if(!appId) return 176;

if([immutableBundleIds containsObject:appId.lowercaseString])
{
return 179;
}

BOOL requiresDevMode = NO;
Expand Down Expand Up @@ -1190,7 +1264,7 @@ int uninstallAppById(NSString* appId, BOOL useCustomMethod)
// 167: IPA does not appear to contain an app
// 180: IPA's main binary is encrypted
// 184: IPA contains additional encrypted binaries
int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipUICache)
int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipUICache, BOOL stealthInstall)
{
cleanRestrictions();

Expand All @@ -1209,7 +1283,7 @@ int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipU
return 168;
}

int ret = installApp(tmpPackagePath, YES, force, NO, useInstalldMethod, skipUICache);
int ret = installApp(tmpPackagePath, YES, force, NO, useInstalldMethod, skipUICache, stealthInstall, YES);

[[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil];

Expand Down Expand Up @@ -1261,6 +1335,9 @@ int installTrollStore(NSString* pathToTar)

NSString* tmpTrollStorePath = [tmpPayloadPath stringByAppendingPathComponent:@"TrollStore.app"];
if(![[NSFileManager defaultManager] fileExistsAtPath:tmpTrollStorePath]) return 1;
NSString* previousTrollStorePath = trollStorePath();
NSString* previousTrollStoreAppPath = trollStoreAppPath();
NSString* previousTrollStoreAppId = appIdForAppPath(previousTrollStoreAppPath);

//if (@available(iOS 16, *)) {} else {
// Transfer existing ldid installation if it exists
Expand Down Expand Up @@ -1312,8 +1389,21 @@ int installTrollStore(NSString* pathToTar)
_installPersistenceHelper(persistenceHelperApp, trollStorePersistenceHelper, trollStoreRootHelper);
}

int ret = installApp(tmpPackagePath, NO, YES, YES, YES, NO);
int ret = installApp(tmpPackagePath, NO, YES, YES, YES, NO, YES, NO);
NSLog(@"[installTrollStore] installApp => %d", ret);
NSString* targetTrollStoreAppId = (ret == 0) ? appIdForAppPath(tmpTrollStorePath) : nil;
if(ret == 0 && previousTrollStorePath && previousTrollStoreAppPath && previousTrollStoreAppId && targetTrollStoreAppId)
{
BOOL didChangeBundleId = ![previousTrollStoreAppId isEqualToString:targetTrollStoreAppId];
MCMAppContainer* targetContainer = [MCMAppContainer containerWithIdentifier:targetTrollStoreAppId createIfNecessary:NO existed:nil error:nil];
BOOL sameContainerPath = [previousTrollStorePath isEqualToString:targetContainer.url.path];
if(didChangeBundleId && !sameContainerPath)
{
// Migrate away from legacy non-stealth install by removing the old app container.
registerPath(previousTrollStoreAppPath, YES, YES);
[[NSFileManager defaultManager] removeItemAtPath:previousTrollStorePath error:nil];
}
}
[[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil];
return ret;
}
Expand Down Expand Up @@ -1547,8 +1637,9 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
BOOL useInstalldMethod = [args containsObject:@"installd"];
BOOL force = [args containsObject:@"force"];
BOOL skipUICache = [args containsObject:@"skip-uicache"];
BOOL stealthInstall = [args containsObject:@"stealth"];
NSString* ipaPath = args.lastObject;
ret = installIpa(ipaPath, force, useInstalldMethod, skipUICache);
ret = installIpa(ipaPath, force, useInstalldMethod, skipUICache, stealthInstall);
}
else if([cmd isEqualToString:@"uninstall"])
{
Expand Down
3 changes: 2 additions & 1 deletion RootHelper/uicache.m
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ bool registerPath(NSString *path, BOOL unregister, BOOL forceSystem) {
dictToRegister[@"Container"] = containerPath;
dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath, appContainerized);
}
dictToRegister[@"IsDeletable"] = @(![appBundleID isEqualToString:@"com.opa334.TrollStore"] && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_15_0);
BOOL isTrollStoreMainApp = [path isEqualToString:trollStoreAppPath()];
dictToRegister[@"IsDeletable"] = @(!isTrollStoreMainApp && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_15_0);
dictToRegister[@"Path"] = path;

dictToRegister[@"SignerOrganization"] = @"Apple Inc.";
Expand Down
1 change: 1 addition & 0 deletions Shared/TSUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extern NSArray* trollStoreInactiveInstalledAppBundlePaths(void);
extern NSArray* trollStoreInstalledAppContainerPaths(void);
extern NSString* trollStorePath(void);
extern NSString* trollStoreAppPath(void);
extern NSString* installedStealthAppIdForOriginalAppId(NSString* originalAppId);

extern BOOL isRemovableSystemApp(NSString* appId);

Expand Down
52 changes: 52 additions & 0 deletions Shared/TSUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -406,12 +406,64 @@ void fetchLatestLdidVersion(void (^completionHandler)(NSString* latestVersion))
return trollStoreInstalledAppBundlePathsInternal(TS_INACTIVE_MARKER);
}

static NSString* trollStoreContainerPathForAppIdentifier(NSString* appId)
{
if(![appId isKindOfClass:NSString.class] || appId.length == 0) return nil;

MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:NULL error:nil];
NSString* appContainerPath = appContainer.url.path;
if(!appContainerPath) return nil;

NSString* appPath = [appContainerPath stringByAppendingPathComponent:@"TrollStore.app"];
if(![[NSFileManager defaultManager] fileExistsAtPath:appPath]) return nil;
return appContainerPath;
}

NSString* installedStealthAppIdForOriginalAppId(NSString* originalAppId)
{
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0) return nil;

LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0];
LSApplicationProxy* appProxy;
while(appProxy = [enumerator nextObject])
{
if(!appProxy.installed) continue;

NSString* appPath = appProxy.bundleURL.path;
if(![appPath isKindOfClass:NSString.class] || appPath.length == 0) continue;

NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:[appPath stringByAppendingPathComponent:@"Info.plist"]];
NSString* installedOriginalAppId = infoDict[@"TSOriginalBundleIdentifier"];
NSString* installedAppId = infoDict[@"CFBundleIdentifier"];
if([installedOriginalAppId isKindOfClass:NSString.class] &&
[installedAppId isKindOfClass:NSString.class] &&
installedAppId.length > 0 &&
[installedOriginalAppId.lowercaseString isEqualToString:originalAppId.lowercaseString])
{
return installedAppId;
}
}

return nil;
}

NSString* trollStorePath()
{
#ifndef TROLLSTORE_LITE
NSString* appContainerPath = trollStoreContainerPathForAppIdentifier(APP_ID);
if(appContainerPath) return appContainerPath;

appContainerPath = trollStoreContainerPathForAppIdentifier(@"com.opa334.trollstore");
if(appContainerPath) return appContainerPath;

NSString* stealthAppId = installedStealthAppIdForOriginalAppId(@"com.opa334.trollstore");
return trollStoreContainerPathForAppIdentifier(stealthAppId);
#else
NSError* mcmError;
MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:APP_ID createIfNecessary:NO existed:NULL error:&mcmError];
if(!appContainer) return nil;
return appContainer.url.path;
#endif
}

NSString* trollStoreAppPath()
Expand Down
5 changes: 2 additions & 3 deletions TrollStore/TSApplicationsManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
- (NSArray*)installedAppPaths;

- (NSError*)errorForCode:(int)code;
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut;
- (int)installIpa:(NSString*)pathToIpa;
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force stealth:(BOOL)stealth log:(NSString**)logOut;
- (int)uninstallApp:(NSString*)appId;
- (int)uninstallAppByPath:(NSString*)path;
- (BOOL)openApplicationWithBundleID:(NSString *)appID;
- (int)enableJITForBundleID:(NSString *)appID;
- (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState;

@end
@end
16 changes: 9 additions & 7 deletions TrollStore/TSApplicationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,27 @@ - (NSError*)errorForCode:(int)code
break;
case 185:
errorDescription = @"Failed to sign the app. The CoreTrust bypass returned a non zero status code.";
break;
case 186:
errorDescription = @"This app is already installed using the opposite install mode (Stealth vs Normal). Uninstall it first before switching modes.";
}

NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}];
return error;
}

- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force stealth:(BOOL)stealth log:(NSString**)logOut
{
NSMutableArray* args = [NSMutableArray new];
[args addObject:@"install"];
if(force)
{
[args addObject:@"force"];
}
if(stealth)
{
[args addObject:@"stealth"];
}
NSNumber* installationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"installationMethod"];
int installationMethodToUse = installationMethodToUseNum ? installationMethodToUseNum.intValue : 1;
if(installationMethodToUse == 1)
Expand All @@ -122,11 +129,6 @@ - (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut
return ret;
}

- (int)installIpa:(NSString*)pathToIpa
{
return [self installIpa:pathToIpa force:NO log:nil];
}

- (int)uninstallApp:(NSString*)appId
{
if(!appId) return -200;
Expand Down Expand Up @@ -193,4 +195,4 @@ - (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState
return spawnRoot(rootHelperPath(), @[@"modify-registration", appPath, newState], nil, nil);
}

@end
@end
Loading