|
| 1 | +#import <Foundation/Foundation.h> |
| 2 | +#import "TDUtils.h" |
| 3 | +#import "LSApplicationProxy+AltList.h" |
| 4 | +#import "TDDumpDecrypted.h" |
| 5 | + |
| 6 | +// Helper function to find tool in common locations |
| 7 | +NSString *findTool(NSString *toolName) { |
| 8 | + NSFileManager *fm = [NSFileManager defaultManager]; |
| 9 | + |
| 10 | + // Check common locations |
| 11 | + NSArray *searchPaths = @[ |
| 12 | + @"/usr/local/bin", |
| 13 | + @"/usr/bin", |
| 14 | + [[NSBundle mainBundle] resourcePath] ?: @"", |
| 15 | + [[NSFileManager defaultManager] currentDirectoryPath], |
| 16 | + @"/var/jb/usr/local/bin", // For rootless jailbreaks |
| 17 | + @"/usr/bin" |
| 18 | + ]; |
| 19 | + |
| 20 | + for (NSString *path in searchPaths) { |
| 21 | + if ([path length] == 0) continue; |
| 22 | + NSString *fullPath = [path stringByAppendingPathComponent:toolName]; |
| 23 | + if ([fm fileExistsAtPath:fullPath]) { |
| 24 | + return fullPath; |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + // Check if tool is in PATH |
| 29 | + NSString *whichPath = [NSString stringWithFormat:@"/usr/bin/which %@", toolName]; |
| 30 | + FILE *pipe = popen([whichPath UTF8String], "r"); |
| 31 | + if (pipe) { |
| 32 | + char buffer[1024]; |
| 33 | + if (fgets(buffer, sizeof(buffer), pipe) != NULL) { |
| 34 | + NSString *result = [[NSString stringWithUTF8String:buffer] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| 35 | + pclose(pipe); |
| 36 | + if ([result length] > 0 && [fm fileExistsAtPath:result]) { |
| 37 | + return result; |
| 38 | + } |
| 39 | + } |
| 40 | + pclose(pipe); |
| 41 | + } |
| 42 | + |
| 43 | + return nil; |
| 44 | +} |
| 45 | + |
| 46 | +void printUsage(const char *programName) { |
| 47 | + printf("Usage: %s <bundle_id> <output_folder>\n", programName); |
| 48 | + printf("\n"); |
| 49 | + printf("Decrypts an iOS app and creates a decrypted IPA file.\n"); |
| 50 | + printf("\n"); |
| 51 | + printf("Arguments:\n"); |
| 52 | + printf(" bundle_id - Bundle identifier of the app to decrypt (e.g., com.apple.calculator)\n"); |
| 53 | + printf(" output_folder - Path to output folder where the decrypted IPA will be saved\n"); |
| 54 | + printf("\n"); |
| 55 | + printf("Example:\n"); |
| 56 | + printf(" %s com.apple.calculator /var/mobile/Documents/decrypted\n", programName); |
| 57 | +} |
| 58 | + |
| 59 | +NSDictionary *getAppInfoFromBundleID(NSString *bundleID) { |
| 60 | + LSApplicationProxy *appProxy = [LSApplicationProxy applicationProxyForIdentifier:bundleID]; |
| 61 | + |
| 62 | + if (!appProxy) { |
| 63 | + fprintf(stderr, "Error: App with bundle ID '%s' not found\n", [bundleID UTF8String]); |
| 64 | + return nil; |
| 65 | + } |
| 66 | + |
| 67 | + NSString *name = [appProxy atl_nameToDisplay]; |
| 68 | + NSString *version = [appProxy atl_shortVersionString]; |
| 69 | + NSString *executable = appProxy.canonicalExecutablePath; |
| 70 | + |
| 71 | + if (!name || !version || !executable) { |
| 72 | + fprintf(stderr, "Error: Failed to get app information for bundle ID '%s'\n", [bundleID UTF8String]); |
| 73 | + return nil; |
| 74 | + } |
| 75 | + |
| 76 | + NSDictionary *appInfo = @{ |
| 77 | + @"bundleID": bundleID, |
| 78 | + @"name": name, |
| 79 | + @"version": version, |
| 80 | + @"executable": executable |
| 81 | + }; |
| 82 | + |
| 83 | + return appInfo; |
| 84 | +} |
| 85 | + |
| 86 | +void decryptAppCLI(NSDictionary *app, NSString *outputFolder) { |
| 87 | + NSString *bundleID = app[@"bundleID"]; |
| 88 | + NSString *name = app[@"name"]; |
| 89 | + NSString *version = app[@"version"]; |
| 90 | + |
| 91 | + printf("[trolldecrypt] Starting decryption for: %s (%s)\n", [name UTF8String], [bundleID UTF8String]); |
| 92 | + printf("[trolldecrypt] Version: %s\n", [version UTF8String]); |
| 93 | + |
| 94 | + // Get the app bundle path |
| 95 | + LSApplicationProxy *appProxy = [LSApplicationProxy applicationProxyForIdentifier:bundleID]; |
| 96 | + if (!appProxy) { |
| 97 | + fprintf(stderr, "[trolldecrypt] Error: Failed to get app proxy for %s\n", [bundleID UTF8String]); |
| 98 | + exit(1); |
| 99 | + } |
| 100 | + |
| 101 | + NSString *appPath = [appProxy bundleURL].path; |
| 102 | + if (!appPath) { |
| 103 | + fprintf(stderr, "[trolldecrypt] Error: Failed to get app path for %s\n", [bundleID UTF8String]); |
| 104 | + exit(1); |
| 105 | + } |
| 106 | + |
| 107 | + printf("[trolldecrypt] App path: %s\n", [appPath UTF8String]); |
| 108 | + |
| 109 | + // Find all mach-o files in the app |
| 110 | + NSArray *machOFiles = findAllMachOFiles(appPath); |
| 111 | + printf("[trolldecrypt] Found %lu mach-o files\n", (unsigned long)machOFiles.count); |
| 112 | + |
| 113 | + if (machOFiles.count == 0) { |
| 114 | + fprintf(stderr, "[trolldecrypt] Error: No mach-o files found in %s\n", [appPath UTF8String]); |
| 115 | + exit(1); |
| 116 | + } |
| 117 | + |
| 118 | + // Create temporary working directory |
| 119 | + NSString *tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_decrypt_work", name]]; |
| 120 | + NSFileManager *fm = [NSFileManager defaultManager]; |
| 121 | + [fm removeItemAtPath:tempDir error:nil]; // Clean up any existing temp dir |
| 122 | + NSError *error; |
| 123 | + if (![fm createDirectoryAtPath:tempDir withIntermediateDirectories:YES attributes:nil error:&error]) { |
| 124 | + fprintf(stderr, "[trolldecrypt] Error: Failed to create temp directory: %s\n", [error.localizedDescription UTF8String]); |
| 125 | + exit(1); |
| 126 | + } |
| 127 | + |
| 128 | + // Copy the entire app bundle to temp directory |
| 129 | + NSString *tempAppPath = [tempDir stringByAppendingPathComponent:[appPath lastPathComponent]]; |
| 130 | + NSError *copyError; |
| 131 | + if (![fm copyItemAtPath:appPath toPath:tempAppPath error:©Error]) { |
| 132 | + fprintf(stderr, "[trolldecrypt] Error: Failed to copy app bundle: %s\n", [copyError.localizedDescription UTF8String]); |
| 133 | + exit(1); |
| 134 | + } |
| 135 | + |
| 136 | + printf("[trolldecrypt] Copied app bundle to: %s\n", [tempAppPath UTF8String]); |
| 137 | + |
| 138 | + // Decrypt each mach-o file and replace it in the temp app bundle |
| 139 | + NSUInteger totalFiles = machOFiles.count; |
| 140 | + for (NSUInteger i = 0; i < machOFiles.count; i++) { |
| 141 | + NSString *machOFile = machOFiles[i]; |
| 142 | + NSString *relativePath = [machOFile substringFromIndex:appPath.length + 1]; |
| 143 | + NSString *tempMachOPath = [tempAppPath stringByAppendingPathComponent:relativePath]; |
| 144 | + |
| 145 | + printf("[trolldecrypt] Decrypting [%lu/%lu]: %s\n", (unsigned long)(i + 1), (unsigned long)totalFiles, [relativePath UTF8String]); |
| 146 | + |
| 147 | + // Decrypt the file directly to the temp location (replacing original) |
| 148 | + decryptMachOFile(machOFile, tempMachOPath); |
| 149 | + } |
| 150 | + |
| 151 | + // Create output directory if it doesn't exist |
| 152 | + if (![fm fileExistsAtPath:outputFolder isDirectory:NULL]) { |
| 153 | + if (![fm createDirectoryAtPath:outputFolder withIntermediateDirectories:YES attributes:nil error:&error]) { |
| 154 | + fprintf(stderr, "[trolldecrypt] Error: Failed to create output directory: %s\n", [error.localizedDescription UTF8String]); |
| 155 | + exit(1); |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + // Create IPA from the modified app bundle |
| 160 | + NSString *ipaFileName = [NSString stringWithFormat:@"%@_%@_decrypted.ipa", name, version]; |
| 161 | + NSString *ipaPath = [outputFolder stringByAppendingPathComponent:ipaFileName]; |
| 162 | + |
| 163 | + printf("[trolldecrypt] Creating IPA file: %s\n", [ipaPath UTF8String]); |
| 164 | + createIPAFromAppBundle(tempAppPath, ipaPath); |
| 165 | + |
| 166 | + // Clean up temp directory |
| 167 | + [fm removeItemAtPath:tempDir error:nil]; |
| 168 | + |
| 169 | + printf("[trolldecrypt] ========================================\n"); |
| 170 | + printf("[trolldecrypt] Decryption completed successfully!\n"); |
| 171 | + printf("[trolldecrypt] IPA saved to: %s\n", [ipaPath UTF8String]); |
| 172 | + printf("[trolldecrypt] ========================================\n"); |
| 173 | +} |
| 174 | + |
| 175 | +int main(int argc, char *argv[]) { |
| 176 | + @autoreleasepool { |
| 177 | + if (argc != 3) { |
| 178 | + printUsage(argv[0]); |
| 179 | + exit(1); |
| 180 | + } |
| 181 | + |
| 182 | + NSString *bundleID = [NSString stringWithUTF8String:argv[1]]; |
| 183 | + NSString *outputFolder = [NSString stringWithUTF8String:argv[2]]; |
| 184 | + |
| 185 | + // Validate output folder path |
| 186 | + if (![outputFolder hasPrefix:@"/"]) { |
| 187 | + fprintf(stderr, "Error: Output folder must be an absolute path\n"); |
| 188 | + exit(1); |
| 189 | + } |
| 190 | + |
| 191 | + // Get app information from bundle ID |
| 192 | + NSDictionary *appInfo = getAppInfoFromBundleID(bundleID); |
| 193 | + if (!appInfo) { |
| 194 | + exit(1); |
| 195 | + } |
| 196 | + |
| 197 | + // Decrypt the app |
| 198 | + decryptAppCLI(appInfo, outputFolder); |
| 199 | + |
| 200 | + return 0; |
| 201 | + } |
| 202 | +} |
0 commit comments