#import "TSUtil.h" #import #import #import @interface PSAppDataUsagePolicyCache : NSObject + (instancetype)sharedInstance; - (void)setUsagePoliciesForBundle:(NSString*)bundleId cellular:(BOOL)cellular wifi:(BOOL)wifi; @end #define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1 extern int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t); extern int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t); extern int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t); void chineseWifiFixup(void) { NSBundle *bundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/SettingsCellular.framework"]; [bundle load]; PSAppDataUsagePolicyCache* policyCache = [NSClassFromString(@"PSAppDataUsagePolicyCache") sharedInstance]; if([policyCache respondsToSelector:@selector(setUsagePoliciesForBundle:cellular:wifi:)]) { [policyCache setUsagePoliciesForBundle:NSBundle.mainBundle.bundleIdentifier cellular:true wifi:true]; } } extern char*** _NSGetArgv(); NSString* safe_getExecutablePath() { char* executablePathC = **_NSGetArgv(); return [NSString stringWithUTF8String:executablePathC]; } #ifdef EMBEDDED_ROOT_HELPER NSString* rootHelperPath(void) { return safe_getExecutablePath(); } #else NSString* rootHelperPath(void) { return [[NSBundle mainBundle].bundlePath stringByAppendingPathComponent:@"trollstorehelper"]; } #endif int fd_is_valid(int fd) { return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } NSString* getNSStringFromFile(int fd) { NSMutableString* ms = [NSMutableString new]; ssize_t num_read; char c; if(!fd_is_valid(fd)) return @""; while((num_read = read(fd, &c, sizeof(c)))) { [ms appendString:[NSString stringWithFormat:@"%c", c]]; if(c == '\n') break; } return ms.copy; } void printMultilineNSString(NSString* stringToPrint) { NSCharacterSet *separator = [NSCharacterSet newlineCharacterSet]; NSArray* lines = [stringToPrint componentsSeparatedByCharactersInSet:separator]; for(NSString* line in lines) { NSLog(@"%@", line); } } int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr) { NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new]; [argsM insertObject:path atIndex:0]; NSUInteger argCount = [argsM count]; char **argsC = (char **)malloc((argCount + 1) * sizeof(char*)); for (NSUInteger i = 0; i < argCount; i++) { argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]); } argsC[argCount] = NULL; posix_spawnattr_t attr; posix_spawnattr_init(&attr); posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); posix_spawnattr_set_persona_uid_np(&attr, 0); posix_spawnattr_set_persona_gid_np(&attr, 0); posix_spawn_file_actions_t action; posix_spawn_file_actions_init(&action); int outErr[2]; if(stdErr) { pipe(outErr); posix_spawn_file_actions_adddup2(&action, outErr[1], STDERR_FILENO); posix_spawn_file_actions_addclose(&action, outErr[0]); } int out[2]; if(stdOut) { pipe(out); posix_spawn_file_actions_adddup2(&action, out[1], STDOUT_FILENO); posix_spawn_file_actions_addclose(&action, out[0]); } pid_t task_pid; int status = -200; int spawnError = posix_spawn(&task_pid, [path UTF8String], &action, &attr, (char* const*)argsC, NULL); posix_spawnattr_destroy(&attr); for (NSUInteger i = 0; i < argCount; i++) { free(argsC[i]); } free(argsC); if(spawnError != 0) { NSLog(@"posix_spawn error %d\n", spawnError); return spawnError; } __block volatile BOOL _isRunning = YES; NSMutableString* outString = [NSMutableString new]; NSMutableString* errString = [NSMutableString new]; dispatch_semaphore_t sema = 0; dispatch_queue_t logQueue; if(stdOut || stdErr) { logQueue = dispatch_queue_create("com.opa334.TrollStore.LogCollector", NULL); sema = dispatch_semaphore_create(0); int outPipe = out[0]; int outErrPipe = outErr[0]; __block BOOL outEnabled = (BOOL)stdOut; __block BOOL errEnabled = (BOOL)stdErr; dispatch_async(logQueue, ^ { while(_isRunning) { @autoreleasepool { if(outEnabled) { [outString appendString:getNSStringFromFile(outPipe)]; } if(errEnabled) { [errString appendString:getNSStringFromFile(outErrPipe)]; } } } dispatch_semaphore_signal(sema); }); } do { if (waitpid(task_pid, &status, 0) != -1) { NSLog(@"Child status %d", WEXITSTATUS(status)); } else { perror("waitpid"); _isRunning = NO; return -222; } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); _isRunning = NO; if(stdOut || stdErr) { if(stdOut) { close(out[1]); } if(stdErr) { close(outErr[1]); } // wait for logging queue to finish dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if(stdOut) { *stdOut = outString.copy; } if(stdErr) { *stdErr = errString.copy; } } return WEXITSTATUS(status); } void enumerateProcessesUsingBlock(void (^enumerator)(pid_t pid, NSString* executablePath, BOOL* stop)) { static int maxArgumentSize = 0; if (maxArgumentSize == 0) { size_t size = sizeof(maxArgumentSize); if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { perror("sysctl argument size"); maxArgumentSize = 4096; // Default } } int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; struct kinfo_proc *info; size_t length; int count; if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) return; if (!(info = malloc(length))) return; if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { free(info); return; } count = length / sizeof(struct kinfo_proc); for (int i = 0; i < count; i++) { @autoreleasepool { pid_t pid = info[i].kp_proc.p_pid; if (pid == 0) { continue; } size_t size = maxArgumentSize; char* buffer = (char *)malloc(length); if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { NSString* executablePath = [NSString stringWithCString:(buffer+sizeof(int)) encoding:NSUTF8StringEncoding]; BOOL stop = NO; enumerator(pid, executablePath, &stop); if(stop) { free(buffer); break; } } free(buffer); } } free(info); } void killall(NSString* processName, BOOL softly) { enumerateProcessesUsingBlock(^(pid_t pid, NSString* executablePath, BOOL* stop) { if([executablePath.lastPathComponent isEqualToString:processName]) { if(softly) { kill(pid, SIGTERM); } else { kill(pid, SIGKILL); } } }); } void respring(void) { killall(@"SpringBoard", YES); exit(0); } void github_fetchLatestVersion(NSString* repo, void (^completionHandler)(NSString* latestVersion)) { NSString* urlString = [NSString stringWithFormat:@"https://api.github.com/repos/%@/releases/latest", repo]; NSURL* githubLatestAPIURL = [NSURL URLWithString:urlString]; NSURLSessionDataTask* task = [NSURLSession.sharedSession dataTaskWithURL:githubLatestAPIURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if(!error) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSError *jsonError; NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (!jsonError) { completionHandler(jsonResponse[@"tag_name"]); } } } }]; [task resume]; } void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)) { github_fetchLatestVersion(@"opa334/TrollStore", completionHandler); } NSArray* trollStoreInstalledAppContainerPaths() { NSMutableArray* appContainerPaths = [NSMutableArray new]; NSString* appContainersPath = @"/var/containers/Bundle/Application"; NSError* error; NSArray* containers = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appContainersPath error:&error]; if(error) { NSLog(@"error getting app bundles paths %@", error); } if(!containers) return nil; for(NSString* container in containers) { NSString* containerPath = [appContainersPath stringByAppendingPathComponent:container]; BOOL isDirectory = NO; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:containerPath isDirectory:&isDirectory]; if(exists && isDirectory) { NSString* trollStoreMark = [containerPath stringByAppendingPathComponent:@"_TrollStore"]; if([[NSFileManager defaultManager] fileExistsAtPath:trollStoreMark]) { NSString* trollStoreApp = [containerPath stringByAppendingPathComponent:@"TrollStore.app"]; if(![[NSFileManager defaultManager] fileExistsAtPath:trollStoreApp]) { [appContainerPaths addObject:containerPath]; } } } } return appContainerPaths.copy; } NSArray* trollStoreInstalledAppBundlePaths() { NSMutableArray* appPaths = [NSMutableArray new]; for(NSString* containerPath in trollStoreInstalledAppContainerPaths()) { NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:containerPath error:nil]; if(!items) return nil; for(NSString* item in items) { if([item.pathExtension isEqualToString:@"app"]) { [appPaths addObject:[containerPath stringByAppendingPathComponent:item]]; } } } return appPaths.copy; } NSString* trollStorePath() { NSError* mcmError; MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:@"com.opa334.TrollStore" createIfNecessary:NO existed:NULL error:&mcmError]; if(!appContainer) return nil; return appContainer.url.path; } NSString* trollStoreAppPath() { return [trollStorePath() stringByAppendingPathComponent:@"TrollStore.app"]; } BOOL isRemovableSystemApp(NSString* appId) { return [[NSFileManager defaultManager] fileExistsAtPath:[@"/System/Library/AppSignatures" stringByAppendingPathComponent:appId]]; } LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes) { __block LSApplicationProxy* outProxy; void (^searchBlock)(LSApplicationProxy* appProxy) = ^(LSApplicationProxy* appProxy) { if(appProxy.installed && !appProxy.restricted) { if([appProxy.bundleURL.path hasPrefix:@"/private/var/containers"]) { NSURL* trollStorePersistenceMarkURL = [appProxy.bundleURL URLByAppendingPathComponent:@".TrollStorePersistenceHelper"]; if([trollStorePersistenceMarkURL checkResourceIsReachableAndReturnError:nil]) { outProxy = appProxy; } } } }; if(allowedTypes & PERSISTENCE_HELPER_TYPE_USER) { [[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:0 block:searchBlock]; } if(allowedTypes & PERSISTENCE_HELPER_TYPE_SYSTEM) { [[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:1 block:searchBlock]; } return outProxy; } SecStaticCodeRef getStaticCodeRef(NSString *binaryPath) { if(binaryPath == nil) { return NULL; } CFURLRef binaryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)binaryPath, kCFURLPOSIXPathStyle, false); if(binaryURL == NULL) { NSLog(@"[getStaticCodeRef] failed to get URL to binary %@", binaryPath); return NULL; } SecStaticCodeRef codeRef = NULL; OSStatus result; result = SecStaticCodeCreateWithPathAndAttributes(binaryURL, kSecCSDefaultFlags, NULL, &codeRef); CFRelease(binaryURL); if(result != errSecSuccess) { NSLog(@"[getStaticCodeRef] failed to create static code for binary %@", binaryPath); return NULL; } return codeRef; } NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef) { if(codeRef == NULL) { NSLog(@"[dumpEntitlements] attempting to dump entitlements without a StaticCodeRef"); return nil; } CFDictionaryRef signingInfo = NULL; OSStatus result; result = SecCodeCopySigningInformation(codeRef, kSecCSRequirementInformation, &signingInfo); if(result != errSecSuccess) { NSLog(@"[dumpEntitlements] failed to copy signing info from static code"); return nil; } NSDictionary *entitlementsNSDict = nil; CFDictionaryRef entitlements = CFDictionaryGetValue(signingInfo, kSecCodeInfoEntitlementsDict); if(entitlements == NULL) { NSLog(@"[dumpEntitlements] no entitlements specified"); } else if(CFGetTypeID(entitlements) != CFDictionaryGetTypeID()) { NSLog(@"[dumpEntitlements] invalid entitlements"); } else { entitlementsNSDict = (__bridge NSDictionary *)(entitlements); NSLog(@"[dumpEntitlements] dumped %@", entitlementsNSDict); } CFRelease(signingInfo); return entitlementsNSDict; } NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath) { // This function is intended for one-shot checks. Main-event functions should retain/release their own SecStaticCodeRefs if(binaryPath == nil) { return nil; } SecStaticCodeRef codeRef = getStaticCodeRef(binaryPath); if(codeRef == NULL) { return nil; } NSDictionary *entitlements = dumpEntitlements(codeRef); CFRelease(codeRef); return entitlements; } NSDictionary* dumpEntitlementsFromBinaryData(NSData* binaryData) { NSDictionary* entitlements; NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; NSURL* tmpURL = [NSURL fileURLWithPath:tmpPath]; if([binaryData writeToURL:tmpURL options:NSDataWritingAtomic error:nil]) { entitlements = dumpEntitlementsFromBinaryAtPath(tmpPath); [[NSFileManager defaultManager] removeItemAtURL:tmpURL error:nil]; } return entitlements; }