#import #import #import #import #import #import #import #import #import #include #import #import #define PROC_PIDPATHINFO_MAXSIZE (1024) int proc_pidpath(pid_t pid, void *buffer, uint32_t buffersize); #define dylibDir @"/Library/TweakInject" NSArray *sbinjectGenerateDylibList() { NSString *processName = [[NSProcessInfo processInfo] processName]; // launchctl, amfid you are special cases if ([processName isEqualToString:@"launchctl"]) { return nil; } if ([processName isEqualToString:@"amfid"]) { return nil; } // Create an array containing all the filenames in dylibDir (/opt/simject) NSError *e = nil; NSArray *dylibDirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dylibDir error:&e]; if (e) { return nil; } // Read current bundle identifier //NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier; // We're only interested in the plist files NSArray *plists = [dylibDirContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF ENDSWITH %@", @"plist"]]; // Create an empty mutable array that will contain a list of dylib paths to be injected into the target process NSMutableArray *dylibsToInject = [NSMutableArray array]; // Loop through the list of plists for (NSString *plist in plists) { // We'll want to deal with absolute paths, so append the filename to dylibDir NSString *plistPath = [dylibDir stringByAppendingPathComponent:plist]; NSDictionary *filter = [NSDictionary dictionaryWithContentsOfFile:plistPath]; // This boolean indicates whether or not the dylib has already been injected BOOL isInjected = NO; // If supported iOS versions are specified within the plist, we check those first NSArray *supportedVersions = filter[@"CoreFoundationVersion"]; if (supportedVersions) { if (supportedVersions.count != 1 && supportedVersions.count != 2) { continue; // Supported versions are in the wrong format, we should skip } if (supportedVersions.count == 1 && [supportedVersions[0] doubleValue] > kCFCoreFoundationVersionNumber) { continue; // Doesn't meet lower bound } if (supportedVersions.count == 2 && ([supportedVersions[0] doubleValue] > kCFCoreFoundationVersionNumber || [supportedVersions[1] doubleValue] <= kCFCoreFoundationVersionNumber)) { continue; // Outside bounds } } // Decide whether or not to load the dylib based on the Bundles values for (NSString *entry in filter[@"Filter"][@"Bundles"]) { // Check to see whether or not this bundle is actually loaded in this application or not if (!CFBundleGetBundleWithIdentifier((CFStringRef)entry)) { // If not, skip it continue; } [dylibsToInject addObject:[[plistPath stringByDeletingPathExtension] stringByAppendingString:@".dylib"]]; isInjected = YES; break; } if (!isInjected) { // Decide whether or not to load the dylib based on the Executables values for (NSString *process in filter[@"Filter"][@"Executables"]) { if ([process isEqualToString:processName]) { [dylibsToInject addObject:[[plistPath stringByDeletingPathExtension] stringByAppendingString:@".dylib"]]; isInjected = YES; break; } } } if (!isInjected) { // Decide whether or not to load the dylib based on the Classes values for (NSString *clazz in filter[@"Filter"][@"Classes"]) { // Also check if this class is loaded in this application or not if (!NSClassFromString(clazz)) { // This class couldn't be loaded, skip continue; } // It's fine to add this dylib at this point [dylibsToInject addObject:[[plistPath stringByDeletingPathExtension] stringByAppendingString:@".dylib"]]; isInjected = YES; break; } } } [dylibsToInject sortUsingSelector:@selector(caseInsensitiveCompare:)]; return dylibsToInject; } int file_exist(char *filename) { struct stat buffer; int r = stat(filename, &buffer); return (r == 0); } @interface SpringBoard : UIApplication - (BOOL)launchApplicationWithIdentifier:(NSString *)identifier suspended:(BOOL)suspended; @end %group SafeMode %hook FBApplicationInfo - (NSDictionary *)environmentVariables { NSDictionary *originalVariables = %orig; NSMutableDictionary *newVariables = [originalVariables mutableCopy]; [newVariables setObject:@1 forKey:@"_SafeMode"]; return [newVariables autorelease]; } %end %hook SBLockScreenManager -(BOOL)_finishUIUnlockFromSource:(int)arg1 withOptions:(id)arg2 { BOOL ret = %orig; [(SpringBoard *)[%c(UIApplication) sharedApplication] launchApplicationWithIdentifier:@"org.coolstar.SafeMode" suspended:NO]; return ret; } // Necessary on iPhone X to show after swipe unlock gesture -(void)lockScreenViewControllerDidDismiss { %orig; [(SpringBoard *)[%c(UIApplication) sharedApplication] launchApplicationWithIdentifier:@"org.coolstar.SafeMode" suspended:NO]; } %end %end static BOOL isSpringBoardOrBackboard = NO; static NSString *processHash = @""; BOOL safeMode = false; void SpringBoardSigHandler(int signo, siginfo_t *info, void *uap){ if (isSpringBoardOrBackboard){ FILE *f = fopen("/var/mobile/Library/.sbinjectSafeMode", "w"); fprintf(f, "Hello World\n"); fclose(f); } if (processHash){ FILE *f = fopen([[NSString stringWithFormat:@"%@/.safeMode-%@", NSTemporaryDirectory(), processHash] UTF8String], "w"); fprintf(f, "Hello World!\n"); fclose(f); } raise(signo); } __attribute__ ((constructor)) static void ctor(void) { @autoreleasepool { unsetenv("DYLD_INSERT_LIBRARIES"); if (NSBundle.mainBundle.bundleIdentifier == nil || ![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.coolstar.SafeMode"]){ char pathbuf[PROC_PIDPATHINFO_MAXSIZE] = {0}; int ret = proc_pidpath(getpid(), pathbuf, sizeof(pathbuf)); if (ret > 0){ NSString *pathStr = [[NSString stringWithUTF8String:pathbuf] stringByResolvingSymlinksInPath]; NSLog(@"TweakInject: Loading for binary %@", pathStr.lastPathComponent); if ([pathStr hasPrefix:@"/Applications"] || [pathStr hasPrefix:@"/var/containers/Bundle/Application"]){ processHash = nil; } else { uint8_t digest[CC_SHA1_DIGEST_LENGTH]; CC_SHA1(pathbuf, ret, digest); NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { [output appendFormat:@"%02x", digest[i]]; } processHash = [[NSString alloc] initWithString:output]; } } safeMode = false; NSString *processName = [[NSProcessInfo processInfo] processName]; struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_sigaction = &SpringBoardSigHandler; action.sa_flags = SA_SIGINFO | SA_RESETHAND; sigemptyset(&action.sa_mask); sigaction(SIGQUIT, &action, NULL); sigaction(SIGILL, &action, NULL); sigaction(SIGTRAP, &action, NULL); sigaction(SIGABRT, &action, NULL); sigaction(SIGEMT, &action, NULL); sigaction(SIGFPE, &action, NULL); sigaction(SIGBUS, &action, NULL); sigaction(SIGSEGV, &action, NULL); sigaction(SIGSYS, &action, NULL); if ([processName isEqualToString:@"backboardd"] || [NSBundle.mainBundle.bundleIdentifier isEqualToString:@"com.apple.springboard"]){ isSpringBoardOrBackboard = YES; if (file_exist("/var/mobile/Library/.sbinjectSafeMode")){ safeMode = true; if ([NSBundle.mainBundle.bundleIdentifier isEqualToString:@"com.apple.springboard"]){ unlink("/var/mobile/Library/.sbinjectSafeMode"); NSLog(@"Entering Safe Mode!"); %init(SafeMode); } } } if ([NSBundle.mainBundle.bundleIdentifier isEqualToString:@"com.apple.springboard"]){ dlopen("/usr/lib/TweakInjectMapsCheck.dylib", RTLD_LAZY | RTLD_GLOBAL); } if (processHash){ const char *safeModeByProcPath = [[NSString stringWithFormat:@"%@/.safeMode-%@", NSTemporaryDirectory(), processHash] UTF8String]; if (file_exist((char *)safeModeByProcPath)){ safeMode = true; unlink(safeModeByProcPath); } } if (getenv("_MSSafeMode")){ if (strcmp(getenv("_MSSafeMode"),"1") == 0){ safeMode = true; } } if (getenv("_SafeMode")){ if (strcmp(getenv("_SafeMode"),"1") == 0){ safeMode = true; } } if (getenv("_SubstituteSafeMode")){ if (strcmp(getenv("_SubstituteSafeMode"),"1") == 0){ safeMode = true; } } if (!safeMode){ for (NSString *dylib in sbinjectGenerateDylibList()) { NSLog(@"Injecting %@", dylib); void *dl = dlopen([dylib UTF8String], RTLD_LAZY | RTLD_GLOBAL); if (dl == NULL) { NSLog(@"Injection failed: '%s'", dlerror()); } } } else { NSLog(@"TweakInject: Entering Safe Mode!"); } } } }