diff --git a/.gitignore b/.gitignore index a3c36dd..f336f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ packages/ *.DS_Store Payload/ -TrollDecrypt.tipa \ No newline at end of file +TrollDecrypt.tipa +compile_commands.json +.cache \ No newline at end of file diff --git a/LSApplicationProxy+AltList.h b/LSApplicationProxy+AltList.h deleted file mode 100644 index 055c4de..0000000 --- a/LSApplicationProxy+AltList.h +++ /dev/null @@ -1,37 +0,0 @@ -#import -#import - -@interface LSApplicationRecord : NSObject -@property (nonatomic,readonly) NSArray* appTags; // 'hidden' -@property (getter=isLaunchProhibited,readonly) BOOL launchProhibited; -@end - -@interface LSApplicationProxy (Additions) -@property (readonly, nonatomic) NSString *shortVersionString; -@property (nonatomic,readonly) NSString* localizedName; -@property (nonatomic,readonly) NSString* applicationType; // (User/System) -@property (nonatomic,readonly) NSArray* appTags; // 'hidden' -@property (getter=isLaunchProhibited,nonatomic,readonly) BOOL launchProhibited; -+ (instancetype)applicationProxyForIdentifier:(NSString*)identifier; -- (LSApplicationRecord*)correspondingApplicationRecord; -@end - -@interface LSApplicationWorkspace (Additions) -- (void)addObserver:(id)arg1; -- (void)removeObserver:(id)arg1; -- (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block; -@end - -@interface LSApplicationProxy (AltList) -- (BOOL)atl_isSystemApplication; -- (BOOL)atl_isUserApplication; -- (BOOL)atl_isHidden; -- (NSString*)atl_fastDisplayName; -- (NSString*)atl_nameToDisplay; -- (NSString*)atl_shortVersionString; -@property (nonatomic,readonly) NSString* atl_bundleIdentifier; -@end - -@interface LSApplicationWorkspace (AltList) -- (NSArray*)atl_allInstalledApplications; -@end \ No newline at end of file diff --git a/LSApplicationProxy+AltList.m b/LSApplicationProxy+AltList.m deleted file mode 100644 index a0ab461..0000000 --- a/LSApplicationProxy+AltList.m +++ /dev/null @@ -1,183 +0,0 @@ -#import -#import "LSApplicationProxy+AltList.h" - -@implementation LSApplicationProxy (AltList) - -- (BOOL)atl_isSystemApplication -{ - return [self.applicationType isEqualToString:@"System"] && ![self atl_isHidden]; -} - -- (BOOL)atl_isUserApplication -{ - return [self.applicationType isEqualToString:@"User"] && ![self atl_isHidden]; -} - -// the tag " hidden " is also valid, so we need to check if any strings contain "hidden" instead -BOOL tagArrayContainsTag(NSArray* tagArr, NSString* tag) -{ - if(!tagArr || !tag) return NO; - - __block BOOL found = NO; - - [tagArr enumerateObjectsUsingBlock:^(NSString* tagToCheck, NSUInteger idx, BOOL* stop) - { - if(![tagToCheck isKindOfClass:[NSString class]]) - { - return; - } - - if([tagToCheck rangeOfString:tag options:0].location != NSNotFound) - { - found = YES; - *stop = YES; - } - }]; - - return found; -} - -// always returns NO on iOS 7 -- (BOOL)atl_isHidden -{ - NSArray* appTags; - NSArray* recordAppTags; - NSArray* sbAppTags; - - BOOL launchProhibited = NO; - - if([self respondsToSelector:@selector(correspondingApplicationRecord)]) - { - // On iOS 14, self.appTags is always empty but the application record still has the correct ones - LSApplicationRecord* record = [self correspondingApplicationRecord]; - recordAppTags = record.appTags; - launchProhibited = record.launchProhibited; - } - if([self respondsToSelector:@selector(appTags)]) - { - appTags = self.appTags; - } - if(!launchProhibited && [self respondsToSelector:@selector(isLaunchProhibited)]) - { - launchProhibited = self.launchProhibited; - } - - NSURL* bundleURL = self.bundleURL; - if(bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil]) - { - NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; - sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"]; - } - - BOOL isWebApplication = ([self.atl_bundleIdentifier rangeOfString:@"com.apple.webapp" options:NSCaseInsensitiveSearch].location != NSNotFound); - return tagArrayContainsTag(appTags, @"hidden") || tagArrayContainsTag(recordAppTags, @"hidden") || tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || launchProhibited; -} - -// Getting the display name is slow (up to 2ms) because it uses an IPC call -// this stacks up if you do it for every single application -// This method provides a faster way (around 0.5ms) to get the display name -// This reduces the overall time needed to sort the applications from ~230 to ~120ms on my test device -- (NSString*)atl_fastDisplayName -{ - NSString* cachedDisplayName = [self valueForKey:@"_localizedName"]; - if(cachedDisplayName && ![cachedDisplayName isEqualToString:@""]) - { - return cachedDisplayName; - } - - NSString* localizedName; - - NSURL* bundleURL = self.bundleURL; - if(!bundleURL || ![bundleURL checkResourceIsReachableAndReturnError:nil]) - { - localizedName = self.localizedName; - } - else - { - NSBundle* bundle = [NSBundle bundleWithURL:bundleURL]; - - localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; - if(!localizedName || [localizedName isEqualToString:@""]) - { - localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; - if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; - if(!localizedName || [localizedName isEqualToString:@""]) - { - localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]; - if(![localizedName isKindOfClass:[NSString class]]) localizedName = nil; - if(!localizedName || [localizedName isEqualToString:@""]) - { - //last possible fallback: use slow IPC call - localizedName = self.localizedName; - } - } - } - } - - [self setValue:localizedName forKey:@"_localizedName"]; - return localizedName; -} - -- (NSString*)atl_nameToDisplay -{ - NSString* localizedName = [self atl_fastDisplayName]; - - if([self.atl_bundleIdentifier rangeOfString:@"carplay" options:NSCaseInsensitiveSearch].location != NSNotFound) - { - if([localizedName rangeOfString:@"carplay" options:NSCaseInsensitiveSearch range:NSMakeRange(0, localizedName.length) locale:[NSLocale currentLocale]].location == NSNotFound) - { - return [localizedName stringByAppendingString:@" (CarPlay)"]; - } - } - - return localizedName; -} - --(id)atl_bundleIdentifier -{ - // iOS 8-14 - if([self respondsToSelector:@selector(bundleIdentifier)]) - { - return [self bundleIdentifier]; - } - // iOS 7 - else - { - return [self applicationIdentifier]; - } -} - -- (NSString *)atl_shortVersionString { - NSString *version = self.shortVersionString; - if (version == nil || [version isEqualToString:@""]) { - version = @"1.0"; - } - - return version; -} - -@end - -@implementation LSApplicationWorkspace (AltList) - -- (NSArray*)atl_allInstalledApplications -{ - if(![self respondsToSelector:@selector(enumerateApplicationsOfType:block:)]) - { - return [self allInstalledApplications]; - } - - NSMutableArray* installedApplications = [NSMutableArray new]; - [self enumerateApplicationsOfType:0 block:^(LSApplicationProxy* appProxy) - { - [installedApplications addObject:appProxy]; - }]; - [self enumerateApplicationsOfType:1 block:^(LSApplicationProxy* appProxy) - { - [installedApplications addObject:appProxy]; - }]; - return installedApplications; -} - -@end \ No newline at end of file diff --git a/Makefile b/Makefile index 7ad6324..87e4ac2 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ include $(THEOS)/makefiles/common.mk APPLICATION_NAME = TrollDecrypt -TrollDecrypt_FILES = SSZipArchive/minizip/unzip.c SSZipArchive/minizip/crypt.c SSZipArchive/minizip/ioapi_buf.c SSZipArchive/minizip/ioapi_mem.c SSZipArchive/minizip/ioapi.c SSZipArchive/minizip/minishared.c SSZipArchive/minizip/zip.c SSZipArchive/minizip/aes/aes_ni.c SSZipArchive/minizip/aes/aescrypt.c SSZipArchive/minizip/aes/aeskey.c SSZipArchive/minizip/aes/aestab.c SSZipArchive/minizip/aes/fileenc.c SSZipArchive/minizip/aes/hmac.c SSZipArchive/minizip/aes/prng.c SSZipArchive/minizip/aes/pwd2key.c SSZipArchive/minizip/aes/sha1.c SSZipArchive/SSZipArchive.m -TrollDecrypt_FILES += main.m TDAppDelegate.m TDRootViewController.m TDDumpDecrypted.m TDUtils.m TDFileManagerViewController.m LSApplicationProxy+AltList.m +TrollDecrypt_FILES = $(wildcard SSZipArchive/minizip/*.c) $(wildcard SSZipArchive/minizip/aes/*.c) SSZipArchive/SSZipArchive.m +TrollDecrypt_FILES += $(wildcard src/*.m) $(wildcard src/*.mm) TrollDecrypt_FRAMEWORKS = UIKit CoreGraphics MobileCoreServices TrollDecrypt_CFLAGS = -fobjc-arc TrollDecrypt_CODESIGN_FLAGS = -Sentitlements.plist diff --git a/Resources/Info.plist b/Resources/Info.plist index 6a24d83..340549a 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -39,6 +39,17 @@ CFBundleIdentifier com.fiore.trolldecrypt + CFBundleURLTypes + + + CFBundleURLName + com.fiore.trolldecrypt + CFBundleURLSchemes + + trolldecrypt + + + CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/TDAppDelegate.h b/TDAppDelegate.h deleted file mode 100644 index bc7e573..0000000 --- a/TDAppDelegate.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - -@interface TDAppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; -@property (nonatomic, strong) UINavigationController *rootViewController; - -@end diff --git a/TDAppDelegate.m b/TDAppDelegate.m deleted file mode 100644 index 993c1b7..0000000 --- a/TDAppDelegate.m +++ /dev/null @@ -1,14 +0,0 @@ -#import "TDAppDelegate.h" -#import "TDRootViewController.h" - -@implementation TDAppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - _rootViewController = [[UINavigationController alloc] initWithRootViewController:[[TDRootViewController alloc] init]]; - _window.rootViewController = _rootViewController; - [_window makeKeyAndVisible]; - return YES; -} - -@end diff --git a/TDDumpDecrypted.h b/TDDumpDecrypted.h deleted file mode 100644 index ffe588d..0000000 --- a/TDDumpDecrypted.h +++ /dev/null @@ -1,17 +0,0 @@ -@interface DumpDecrypted : NSObject { - char decryptedAppPathStr[PATH_MAX]; - char *filename; - char *appDirName; - char *appDirPath; -} - -@property (assign) NSString *appPath; -@property (assign) NSString *docPath; -@property (assign) NSString *appName; -@property (assign) NSString *appVersion; - -- (id)initWithPathToBinary:(NSString *)pathToBinary appName:(NSString *)appName appVersion:(NSString *)appVersion; -- (void)createIPAFile:(pid_t)pid; -- (BOOL)dumpDecryptedImage:(const struct mach_header *)image_mh fileName:(const char *)encryptedImageFilenameStr image:(int)imageNum task:(vm_map_t)targetTask; -- (NSString *)IPAPath; -@end \ No newline at end of file diff --git a/TDDumpDecrypted.m b/TDDumpDecrypted.m deleted file mode 100644 index c10a460..0000000 --- a/TDDumpDecrypted.m +++ /dev/null @@ -1,616 +0,0 @@ -/* - bfinject - Inject shared libraries into running App Store apps on iOS 11.x < 11.2 - https://github.com/BishopFox/bfinject - - Carl Livitt @ Bishop Fox - - Based on code originally by 10n1c: https://github.com/stefanesser/dumpdecrypted/blob/master/dumpdecrypted.c - Now with the following enhancements: - - Dump ALL encrypted images in the target application: the app itself, its frameworks, etc. - - Create a valid .ipa containing the decrypted binaries. Save it in ~/Documents/decrypted-app.ipa - - The .ipa can be modified and re-signed with a developer cert for redeployment to non-jailbroken devices - - Auto detection of all the necessary sandbox paths - - Converted into an Objective-C class for ease of use. -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#import "SSZipArchive/SSZipArchive.h" -#import -#import - -#include -#include -#include -#include -#include - -#include "TDDumpDecrypted.h" -#import "TDUtils.h" - -kern_return_t -mach_vm_read_overwrite(vm_map_t, mach_vm_address_t, mach_vm_size_t, mach_vm_address_t, mach_vm_size_t *); -// kern_return_t mach_vm_read(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, vm_offset_t *data, mach_msg_type_number_t *dataCnt); -kern_return_t -mach_vm_region(vm_map_read_t target_task, mach_vm_address_t *address, mach_vm_size_t *size, vm_region_flavor_t flavor, vm_region_info_t info, mach_msg_type_number_t *infoCnt, mach_port_t *object_name); - - -//#define DEBUG(...) NSLog(__VA_ARGS__); -// #define DEBUG(...) {} - -#define swap32(value) (((value & 0xFF000000) >> 24) | ((value & 0x00FF0000) >> 8) | ((value & 0x0000FF00) << 8) | ((value & 0x000000FF) << 24) ) - -unsigned char *readProcessMemory (mach_port_t proc, mach_vm_address_t addr, mach_msg_type_number_t* size) { - mach_msg_type_number_t dataCnt = (mach_msg_type_number_t) *size; - vm_offset_t readMem; - - kern_return_t kr = vm_read(proc, addr, *size, &readMem, &dataCnt); - - if (kr) { - //fprintf (stderr, "Unable to read target task's memory @%p - kr 0x%x\n", (void *) addr, kr); - return NULL; - } - - return ((unsigned char *) readMem); -} - -static kern_return_t -readmem(mach_vm_offset_t *buffer, mach_vm_address_t address, mach_vm_size_t size, vm_map_t targetTask, vm_region_basic_info_data_64_t *info) -{ - // get task for pid - vm_map_t port = targetTask; - - kern_return_t kr; - mach_msg_type_number_t info_cnt = sizeof (vm_region_basic_info_data_64_t); - mach_port_t object_name; - mach_vm_size_t size_info; - mach_vm_address_t address_info = address; - kr = mach_vm_region(port, &address_info, &size_info, VM_REGION_BASIC_INFO_64, (vm_region_info_t)info, &info_cnt, &object_name); - if (kr) - { - fprintf(stderr, "[ERROR] mach_vm_region failed with error %d\n", (int)kr); - return KERN_FAILURE; - } - - /* read memory - vm_read_overwrite because we supply the buffer */ - mach_vm_size_t nread; - kr = mach_vm_read_overwrite(port, address, size, (mach_vm_address_t)buffer, &nread); - if (kr) - { - fprintf(stderr, "[ERROR] vm_read failed! %d\n", kr); - return KERN_FAILURE; - } - else if (nread != size) - { - fprintf(stderr, "[ERROR] vm_read failed! requested size: 0x%llx read: 0x%llx\n", size, nread); - return KERN_FAILURE; - } - return KERN_SUCCESS; -} - -int64_t -get_image_size(mach_vm_address_t address, vm_map_t targetTask, uint64_t *vmaddr_slide) -{ - vm_region_basic_info_data_64_t region_info = {0}; - // allocate a buffer to read the header info - // NOTE: this is not exactly correct since the 64bit version has an extra 4 bytes - // but this will work for this purpose so no need for more complexity! - struct mach_header header = {0}; - if (readmem((mach_vm_offset_t*)&header, address, sizeof(struct mach_header), targetTask, ®ion_info)) - { - printf("Can't read header!\n"); - return -1; - } - - if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64) - { - printf("[ERROR] Target is not a mach-o binary!\n"); - return -1; - } - - int64_t imagefilesize = -1; - /* read the load commands */ - uint8_t *loadcmds = (uint8_t*)malloc(header.sizeofcmds); - uint16_t mach_header_size = sizeof(struct mach_header); - if (header.magic == MH_MAGIC_64) - { - mach_header_size = sizeof(struct mach_header_64); - } - if (readmem((mach_vm_offset_t*)loadcmds, address+mach_header_size, header.sizeofcmds, targetTask, ®ion_info)) - { - printf("Can't read load commands\n"); - free(loadcmds); - return -1; - } - - /* process and retrieve address and size of linkedit */ - uint8_t *loadCmdAddress = 0; - loadCmdAddress = (uint8_t*)loadcmds; - struct load_command *loadCommand = NULL; - struct segment_command *segCmd = NULL; - struct segment_command_64 *segCmd64 = NULL; - for (uint32_t i = 0; i < header.ncmds; i++) - { - loadCommand = (struct load_command*)loadCmdAddress; - if (loadCommand->cmd == LC_SEGMENT) - { - segCmd = (struct segment_command*)loadCmdAddress; - if (strncmp(segCmd->segname, "__PAGEZERO", 16) != 0) - { - if (strncmp(segCmd->segname, "__TEXT", 16) == 0) - { - *vmaddr_slide = address - segCmd->vmaddr; - } - imagefilesize += segCmd->filesize; - } - } - else if (loadCommand->cmd == LC_SEGMENT_64) - { - segCmd64 = (struct segment_command_64*)loadCmdAddress; - if (strncmp(segCmd64->segname, "__PAGEZERO", 16) != 0) - { - if (strncmp(segCmd64->segname, "__TEXT", 16) == 0) - { - *vmaddr_slide = address - segCmd64->vmaddr; - } - imagefilesize += segCmd64->filesize; - } - } - // advance to next command - loadCmdAddress += loadCommand->cmdsize; - } - free(loadcmds); - return imagefilesize; -} - -int find_off_cryptid(const char *filePath) { - NSLog(@"[trolldecrypt] %s filePath: %s", __FUNCTION__, filePath); - int off_cryptid = 0; - FILE* file = fopen(filePath, "rb"); - if (!file) return 1; - - fseek(file, 0, SEEK_END); - long fileSize = ftell(file); - fseek(file, 0, SEEK_SET); - - char* buffer = malloc(fileSize);//new char[fileSize]; - fread(buffer, 1, fileSize, file); - - struct mach_header_64* header = (struct mach_header_64*)buffer; - if (header->magic != MH_MAGIC_64) { - printf("[-] error: not a valid macho file\n"); - free(buffer); - fclose(file); - return 2; - } - - struct load_command* lc = (struct load_command*)((mach_vm_address_t)header + sizeof(struct mach_header_64)); - for (uint32_t i = 0; i < header->ncmds; i++) { - if (lc->cmd == LC_ENCRYPTION_INFO || lc->cmd == LC_ENCRYPTION_INFO_64) { - struct encryption_info_command *encryption_info = (struct encryption_info_command*) lc; - - - NSLog(@"[trolldecrypt] cryptid: %d\n", encryption_info->cryptid); - NSLog(@"[trolldecrypt] Found cryptid at offset: 0x%llx\n", (mach_vm_address_t)lc + offsetof(struct encryption_info_command, cryptid) - (mach_vm_address_t)header); - - off_cryptid = (mach_vm_address_t)lc + offsetof(struct encryption_info_command, cryptid) - (mach_vm_address_t)header; - break; - } - lc = (struct load_command*)((mach_vm_address_t)lc + lc->cmdsize); - } - free(buffer); - fclose(file); - - return off_cryptid; -} - -@implementation DumpDecrypted - --(id) initWithPathToBinary:(NSString *)pathToBinary appName:(NSString *)appName appVersion:(NSString *)appVersion { - if(!self) { - self = [super init]; - } - - self.appName = appName; - self.appVersion = appVersion; - - [self setAppPath:[pathToBinary stringByDeletingLastPathComponent]]; - // [self setDocPath:[NSString stringWithFormat:@"%@/Documents", NSHomeDirectory()]]; - [self setDocPath:docPath()]; - - char *lastPartOfAppPath = strdup([[self appPath] UTF8String]); - lastPartOfAppPath = strrchr(lastPartOfAppPath, '/') + 1; - NSLog(@"[trolldecrypt] init: appDirName: %s", lastPartOfAppPath); - self->appDirName = strdup(lastPartOfAppPath); - - return self; -} - - --(void) makeDirectories:(const char *)encryptedImageFilenameStr { - char *appPath = (char *)[[self appPath] UTF8String]; - char *docPath = (char *)[[self docPath] UTF8String]; - char *savePtr; - char *encryptedImagePathStr = savePtr = strdup(encryptedImageFilenameStr); - self->filename = strdup(strrchr(encryptedImagePathStr, '/') + 1); - - // Normalize the filenames - if(strstr(encryptedImagePathStr, "/private") == encryptedImagePathStr) - encryptedImagePathStr += 8; - if(strstr(appPath, "/private") == appPath) - appPath += 8; - - // Find start of image path, relative to the base of the app sandbox (ie. /var/mobile/.../FooBar.app/THIS_PART_HERE) - encryptedImagePathStr += strlen(appPath) + 1; // skip over the app path - char *p = strrchr(encryptedImagePathStr, '/'); - if(p) - *p = '\0'; - - NSLog(@"[trolldecrypt] encryptedImagePathStr: %s", encryptedImagePathStr); - - NSFileManager *fm = [[NSFileManager alloc] init]; - NSError *err; - char *lastPartOfAppPath = strdup(appPath); // Must free() - lastPartOfAppPath = strrchr(lastPartOfAppPath, '/'); - lastPartOfAppPath++; - NSString *path = [NSString stringWithFormat:@"%s/ipa/Payload/%s", docPath, lastPartOfAppPath]; - self->appDirPath = strdup([path UTF8String]); - if(p) - path = [NSString stringWithFormat:@"%@/%s", path, encryptedImagePathStr]; - - NSLog(@"[trolldecrypt] make_directories making dir: %@", path); - if(! [fm createDirectoryAtPath:path withIntermediateDirectories:true attributes:nil error:&err]) { - NSLog(@"[trolldecrypt] WARNING: make_directories failed to make directory %@. Error: %@", path, err); - } - - free(savePtr); - - snprintf(self->decryptedAppPathStr, PATH_MAX, "%s/%s", [path UTF8String], self->filename); - - return; -} - - --(BOOL) dumpDecryptedImage:(vm_address_t)imageAddress fileName:(const char *)encryptedImageFilenameStr image:(int)imageNum task:(vm_map_t)targetTask{ - // struct load_command *lc; - struct encryption_info_command *eic; - struct fat_header *fh; - struct fat_arch *arch; - // struct mach_header *mh; - char buffer[1024]; - unsigned int fileoffs = 0, off_cryptid = 0, restsize; - int i, fd, outfd, r, n; - - struct mach_header header = {0}; - vm_region_basic_info_data_64_t region_info = {0}; - if (readmem((mach_vm_offset_t*)&header, imageAddress, sizeof(struct mach_header), targetTask, ®ion_info)) - { - NSLog(@"[trolldecrypt] Can't read header!"); - exit(1); - } - //XXX: change all image_mh -> header NOW - - struct load_command *lc = (struct load_command*)malloc(header.sizeofcmds); - - /* detect if this is a arm64 binary */ - if (header.magic == MH_MAGIC_64) { - // lc = (struct load_command *)((unsigned char *)header + sizeof(struct mach_header_64)); - readmem((mach_vm_offset_t*)lc, imageAddress+sizeof(struct mach_header_64), header.sizeofcmds, targetTask, ®ion_info); - NSLog(@"[trolldecrypt] detected 64bit ARM binary in memory.\n"); - } else if(header.magic == MH_MAGIC) { /* we might want to check for other errors here, too */ - // lc = (struct load_command *)((unsigned char *)header + sizeof(struct mach_header)); - readmem((mach_vm_offset_t*)lc, imageAddress+sizeof(struct mach_header), header.sizeofcmds, targetTask, ®ion_info); - NSLog(@"[trolldecrypt] detected 32bit ARM binary in memory.\n"); - } else { - NSLog(@"[trolldecrypt] No valid header found!!"); - return false; - } - - - // sleep(1);exit(1); - const struct mach_header *image_mh = &header; - - /* searching all load commands for an LC_ENCRYPTION_INFO load command */ - for (i=0; incmds; i++) { - if (lc->cmd == LC_ENCRYPTION_INFO || lc->cmd == LC_ENCRYPTION_INFO_64) { - eic = (struct encryption_info_command *)lc; - // struct encryption_info_command *eic = (uint8_t*)malloc(sizeof(struct encryption_info_command);); - // readmem((mach_vm_offset_t*)eic, imageAddress+sizeof(struct mach_header_64), header.sizeofcmds, targetTask, ®ion_info); - - const char *appFilename = strrchr(encryptedImageFilenameStr, '/'); - if(appFilename == NULL) { - NSLog(@"[trolldecrypt] There are no / in the filename. This is an error.\n"); - return false; - } - appFilename++; - - /* If this load command is present, but data is not crypted then exit */ - if (eic->cryptid == 0) { - NSLog(@"[trolldecrypt] CryptID = 0!! "); - return false; - } - - // Create a dir structure in ~ just like in /path/to/FooApp.app/Whatever - [self makeDirectories:encryptedImageFilenameStr]; - - //0x1518 - uint32_t aslr_slide = 0; - get_image_size(imageAddress, targetTask, &aslr_slide); - NSLog(@"[trolldecrypt] aslr_slide= 0x%x", aslr_slide); - - off_cryptid=find_off_cryptid(encryptedImageFilenameStr); - - NSLog(@"[trolldecrypt] offset to cryptid (%d) found in memory @ %p (from %p). off_cryptid = %u (0x%x)\n", eic->cryptid, &eic->cryptid, image_mh, off_cryptid, off_cryptid); - //NSLog(@"[trolldecrypt] Found encrypted data at offset %u 0x%08x. image_mh @ %p. cryptedData @ 0x%x. cryptsize = %u (0x%x) bytes.\n", eic->cryptoff, eic->cryptoff, image_mh, (unsigned int)image_mh + eic->cryptoff, eic->cryptsize, eic->cryptsize); - - NSLog(@"[trolldecrypt] Dumping: %s", encryptedImageFilenameStr); - NSLog(@"[trolldecrypt] Into: %s", self->decryptedAppPathStr); - fd = open(encryptedImageFilenameStr, O_RDONLY); - if (fd == -1) { - NSLog(@"[trolldecrypt] Failed to open %s", encryptedImageFilenameStr); - return false; - } - - NSLog(@"[trolldecrypt] Reading header"); - n = read(fd, (void *)buffer, sizeof(buffer)); - if (n != sizeof(buffer)) { - NSLog(@"[trolldecrypt] Warning read only %d of %lu bytes from encrypted file.\n", n, sizeof(buffer)); - return false; - } - - NSLog(@"[trolldecrypt] Detecting header type\n"); - fh = (struct fat_header *)buffer; - - /* Is this a FAT file - we assume the right endianess */ - if (fh->magic == FAT_CIGAM) { - NSLog(@"[trolldecrypt] Executable is a FAT image - searching for right architecture\n"); - arch = (struct fat_arch *)&fh[1]; - for (i=0; infat_arch); i++) { - if ((image_mh->cputype == swap32(arch->cputype)) && (image_mh->cpusubtype == swap32(arch->cpusubtype))) { - fileoffs = swap32(arch->offset); - NSLog(@"[trolldecrypt] Correct arch is at offset 0x%x in the file.\n", fileoffs); - break; - } - arch++; - } - if (fileoffs == 0) { - NSLog(@"[trolldecrypt] Could not find correct arch in FAT image\n"); - return false; - } - } else if (fh->magic == MH_MAGIC || fh->magic == MH_MAGIC_64) { - NSLog(@"[trolldecrypt] Executable is a plain MACH-O image, fileoffs = 0\n"); - } else { - NSLog(@"[trolldecrypt] Executable is of unknown type, fileoffs = 0\n"); - return false; - } - - NSLog(@"[trolldecrypt] Opening %s for writing.\n", decryptedAppPathStr); - outfd = open(decryptedAppPathStr, O_RDWR|O_CREAT|O_TRUNC, 0644); - if (outfd == -1) { - NSLog(@"[trolldecrypt] Failed opening: "); - return false; - } - - /* calculate address of beginning of crypted data */ - n = fileoffs + eic->cryptoff; - - restsize = lseek(fd, 0, SEEK_END) - n - eic->cryptsize; - //NSLog(@"[trolldecrypt] restsize = %u, n = %u, cryptsize = %u, total = %u", restsize, n, eic->cryptsize, n + eic->cryptsize + restsize); - lseek(fd, 0, SEEK_SET); - - NSLog(@"[trolldecrypt] Copying the not encrypted start of the file (%u bytes)\n", n); - - /* first copy all the data before the encrypted data */ - char *buf = (char *)malloc((size_t)n); - r = read(fd, buf, n); - if(r != n) { - NSLog(@"[trolldecrypt] Error reading start of file\n"); - return false; - } - r = write(outfd, buf, n); - if(r != n) { - NSLog(@"[trolldecrypt] Error writing start of file\n"); - return false; - } - free(buf); - - /* now write the previously encrypted data */ - - NSLog(@"[trolldecrypt] Dumping the decrypted data into the file (%u bytes)\n", eic->cryptsize); - buf = (char *)malloc((size_t)eic->cryptsize); - readmem((mach_vm_offset_t*)buf, imageAddress+eic->cryptoff, eic->cryptsize, targetTask, ®ion_info); - // r = write(outfd, (unsigned char *)image_mh + eic->cryptoff, eic->cryptsize); - r = write(outfd, buf, eic->cryptsize); - if (r != eic->cryptsize) { - NSLog(@"[trolldecrypt] Error writing encrypted part of file\n"); - return false; - } - free(buf); - - - /* and finish with the remainder of the file */ - NSLog(@"[trolldecrypt] Copying the not encrypted remainder of the file (%u bytes)\n", restsize); - lseek(fd, eic->cryptsize, SEEK_CUR); - buf = (char *)malloc((size_t)restsize); - r = read(fd, buf, restsize); - if (r != restsize) { - NSLog(@"[trolldecrypt] Error reading rest of file, got %u bytes\n", r); - return false; - } - r = write(outfd, buf, restsize); - if (r != restsize) { - NSLog(@"[trolldecrypt] Error writing rest of file\n"); - return false; - } - free(buf); - - - if (off_cryptid) { - uint32_t zero=0; - NSLog(@"[trolldecrypt] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 0x%x into file\n", off_cryptid); - if (lseek(outfd, off_cryptid, SEEK_SET) == off_cryptid) { - if(write(outfd, &zero, 4) != 4) { - NSLog(@"[trolldecrypt] Error writing cryptid value!!\n"); - // Not a fatal error, just warn - } - } else { - NSLog(@"[trolldecrypt] Failed to seek to cryptid offset!!"); - // this error is not treated as fatal - } - } - - close(fd); - close(outfd); - sync(); - // exit(1); - - return true; - } - - lc = (struct load_command *)((unsigned char *)lc+lc->cmdsize); - // readmem((mach_vm_offset_t*)lc, lc+lc->cmdsize, header.sizeofcmds, targetTask, ®ion_info); - } - // DEBUG(@"[!] This mach-o file is not encrypted. Nothing was decrypted.\n"); - return false; -} - - --(void) dumpDecrypted:(pid_t)pid { - - vm_map_t targetTask = 0; - if (task_for_pid(mach_task_self(), pid, &targetTask)) - { - NSLog(@"[trolldecrypt] Can't execute task_for_pid! Do you have the right permissions/entitlements?\n"); - exit(1); - } - - //numberOfImages - struct task_dyld_info dyld_info; - mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; - - if(task_info(targetTask, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) != KERN_SUCCESS) exit(1); - - mach_msg_type_number_t size = sizeof(struct dyld_all_image_infos); - uint8_t* data = readProcessMemory(targetTask, dyld_info.all_image_info_addr, &size); - struct dyld_all_image_infos* infos = (struct dyld_all_image_infos *) data; - - mach_msg_type_number_t size2 = sizeof(struct dyld_image_info) * infos->infoArrayCount; - uint8_t* info_addr = readProcessMemory(targetTask, (mach_vm_address_t) infos->infoArray, &size2); - struct dyld_image_info* info = (struct dyld_image_info*) info_addr; - - uint32_t numberOfImages = infos->infoArrayCount; - mach_vm_address_t imageAddress = 0; - const char *appPath = [[self appPath] UTF8String]; - - NSLog(@"[trolldecrypt] There are %d images mapped.", numberOfImages); - - for (int i = 0; i < numberOfImages; i++) { - NSLog(@"[trolldecrypt] image %d", i); - mach_msg_type_number_t size3 = PATH_MAX; - uint8_t *fpath_addr = readProcessMemory(targetTask, (mach_vm_address_t) info[i].imageFilePath, &size3); - - imageAddress = (struct mach_header *)info[i].imageLoadAddress; - const char *imageName = fpath_addr; - - if(!imageName || !imageAddress) - continue; - - NSLog(@"[trolldecrypt] Comparing %s to %s", imageName, appPath); - - if(strstr(imageName, appPath) != NULL) { - NSLog(@"[trolldecrypt] Dumping image %d: %s", i, imageName); - [self dumpDecryptedImage:imageAddress fileName:imageName image:i task: targetTask]; - } - } -} - - --(BOOL)fileManager:(NSFileManager *)f shouldProceedAfterError:(BOOL)proceed copyingItemAtPath:(NSString *)path toPath:(NSString *)dest { - return true; -} - --(NSString *)IPAPath { - return [NSString stringWithFormat:@"%@/%@_%@_decrypted.ipa", [self docPath], self.appName, self.appVersion]; -} - --(void) createIPAFile:(pid_t)pid { - NSString *IPAFile = [self IPAPath]; - NSString *appDir = [self appPath]; - NSString *appCopyDir = [NSString stringWithFormat:@"%@/ipa/Payload/%s", [self docPath], self->appDirName]; - NSString *zipDir = [NSString stringWithFormat:@"%@/ipa", [self docPath]]; - NSFileManager *fm = [[NSFileManager alloc] init]; - NSError *err; - - [fm removeItemAtPath:IPAFile error:nil]; - [fm removeItemAtPath:appCopyDir error:nil]; - [fm createDirectoryAtPath:appCopyDir withIntermediateDirectories:true attributes:nil error:nil]; - - [fm setDelegate:(id)self]; - - NSLog(@"[trolldecrypt] ======== START FILE COPY - IGNORE ANY SANDBOX WARNINGS ========"); - NSLog(@"[trolldecrypt] IPAFile: %@", IPAFile); - NSLog(@"[trolldecrypt] appDir: %@", appDir); - NSLog(@"[trolldecrypt] appCopyDir: %@", appCopyDir); - NSLog(@"[trolldecrypt] zipDir: %@", zipDir); - - [fm copyItemAtPath:appDir toPath:appCopyDir error:&err]; - NSLog(@"[trolldecrypt] ======== END OF FILE COPY ========"); - // sleep(1); - // exit(1); - // Replace encrypted binaries with decrypted versions - NSLog(@"[trolldecrypt] ======== START DECRYPTION PROCESS ========"); - [self dumpDecrypted:pid]; - NSLog(@"[trolldecrypt] ======== DECRYPTION COMPLETE ========"); - - // ZIP it up - NSLog(@"[trolldecrypt] ======== STARTING ZIP ========"); - NSLog(@"[trolldecrypt] IPA file: %@", IPAFile); - NSLog(@"[trolldecrypt] ZIP dir: %@", zipDir); - unlink([IPAFile UTF8String]); - @try { - BOOL success = [SSZipArchive createZipFileAtPath:IPAFile - withContentsOfDirectory:zipDir - keepParentDirectory:NO - compressionLevel:1 - password:nil - AES:NO - progressHandler:nil - ]; - NSLog(@"[trolldecrypt] ======== ZIP operation complete: %s ========", (success)?"success":"failed"); - } - @catch(NSException *e) { - NSLog(@"[trolldecrypt] BAAAAAAAARF during ZIP operation!!! , %@", e); - } - - - // Clean up. Leave only the .ipa file. - [fm removeItemAtPath:zipDir error:nil]; - - NSLog(@"[trolldecrypt] ======== Wrote %@ ========", [self IPAPath]); - return; -} - -@end \ No newline at end of file diff --git a/TDFileManagerViewController.m b/TDFileManagerViewController.m deleted file mode 100644 index 46bdac5..0000000 --- a/TDFileManagerViewController.m +++ /dev/null @@ -1,97 +0,0 @@ -#import "TDFileManagerViewController.h" -#import "TDUtils.h" - -@implementation TDFileManagerViewController - -- (void)loadView { - [super loadView]; - - self.title = @"Decrypted IPAs"; - self.fileList = decryptedFileList(); - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(done)]; -} - -- (void)refresh { - self.fileList = decryptedFileList(); - [self.tableView reloadData]; -} - -- (void)done { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (NSInteger)numberOfSelectionsInTableView:(UITableView *)tableView { - return 1; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.fileList.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *cellIdentifier = @"FileCell"; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; - - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; - } - - NSString *path = [docPath() stringByAppendingPathComponent:self.fileList[indexPath.row]]; - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; - NSDate *date = attributes[NSFileModificationDate]; - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"MMM d, yyyy h:mm a"]; - - NSNumber *fileSize = attributes[NSFileSize]; - - - cell.textLabel.text = self.fileList[indexPath.row]; - cell.detailTextLabel.text = [dateFormatter stringFromDate:date]; - cell.detailTextLabel.textColor = [UIColor systemGray2Color]; - cell.imageView.image = [UIImage systemImageNamed:@"doc.fill"]; - - UILabel *label = [[UILabel alloc] init]; - label.text = [NSString stringWithFormat:@"%.2f MB", [fileSize doubleValue] / 1000000.0f]; - label.textColor = [UIColor systemGray2Color]; - label.font = [UIFont systemFontOfSize:12.0f]; - [label sizeToFit]; - label.textAlignment = NSTextAlignmentCenter; - cell.accessoryView = label; - - - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 65.0f; -} - -- (bool)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { - return YES; -} - -- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { - UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"Delete" handler:^(UIContextualAction *action, UIView *sourceView, void (^completionHandler)(BOOL)) { - NSString *file = self.fileList[indexPath.row]; - NSString *path = [docPath() stringByAppendingPathComponent:file]; - [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; - [self refresh]; - }]; - - UISwipeActionsConfiguration *swipeActions = [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]]; - return swipeActions; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - NSString *file = self.fileList[indexPath.row]; - NSString *path = [docPath() stringByAppendingPathComponent:file]; - NSURL *url = [NSURL fileURLWithPath:path]; - - UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[url] applicationActivities:nil]; - [self presentViewController:activityViewController animated:YES completion:nil]; - - [tableView deselectRowAtIndexPath:indexPath animated:YES]; -} - -@end \ No newline at end of file diff --git a/TDRootViewController.m b/TDRootViewController.m deleted file mode 100644 index 40dff9d..0000000 --- a/TDRootViewController.m +++ /dev/null @@ -1,132 +0,0 @@ -#import "TDRootViewController.h" -#import "TDFileManagerViewController.h" -#import "TDUtils.h" - -@implementation TDRootViewController - -- (void)loadView { - [super loadView]; - - self.apps = appList(); - self.title = @"TrollDecrypt"; - self.navigationController.navigationBar.prefersLargeTitles = YES; - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"info.circle"] style:UIBarButtonItemStylePlain target:self action:@selector(about:)]; - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"folder"] style:UIBarButtonItemStylePlain target:self action:@selector(openDocs:)]; - - UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; - [refreshControl addTarget:self action:@selector(refreshApps:) forControlEvents:UIControlEventValueChanged]; - self.refreshControl = refreshControl; -} - -- (void)viewDidAppear:(bool)animated { - [super viewDidAppear:animated]; - - fetchLatestTrollDecryptVersion(^(NSString *latestVersion) { - NSString *currentVersion = trollDecryptVersion(); - NSComparisonResult result = [currentVersion compare:latestVersion options:NSNumericSearch]; - NSLog(@"[trolldecrypter] Current version: %@, Latest version: %@", currentVersion, latestVersion); - if (result == NSOrderedAscending) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Update Available" message:@"An update for TrollDecrypt is available." preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; - UIAlertAction *update = [UIAlertAction actionWithTitle:@"Download" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://github.com/donato-fiore/TrollDecrypt/releases/latest"]] options:@{} completionHandler:nil]; - }]; - - [alert addAction:update]; - [alert addAction:cancel]; - [self presentViewController:alert animated:YES completion:nil]; - }); - } - }); -} - -- (void)openDocs:(id)sender { - TDFileManagerViewController *fmVC = [[TDFileManagerViewController alloc] init]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:fmVC]; - [self presentViewController:navController animated:YES completion:nil]; -} - -- (void)about:(id)sender { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"TrollDecrypt" message:@"by fiore\nIcon by @super.user\nbfdecrypt by @bishopfox\ndumpdecrypted by @i0n1c\nUpdated for TrollStore by @wh1te4ever" preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)refreshApps:(UIRefreshControl *)refreshControl { - self.apps = appList(); - [self.tableView reloadData]; - [refreshControl endRefreshing]; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.apps.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - static NSString *cellIdentifier = @"AppCell"; - UITableViewCell *cell; - if (([self.apps count] - 1) != indexPath.row) { - cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; - if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier]; - - NSDictionary *app = self.apps[indexPath.row]; - - cell.textLabel.text = app[@"name"]; - cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ • %@", app[@"version"], app[@"bundleID"]]; - cell.imageView.image = [UIImage _applicationIconImageForBundleIdentifier:app[@"bundleID"] format:iconFormat() scale:[UIScreen mainScreen].scale]; - } else { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]; - - cell.textLabel.text = @"Advanced"; - cell.detailTextLabel.text = @"Decrypt app from a specified PID"; - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - } - - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 80.0f; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - UIAlertController *alert; - - if (([self.apps count] - 1) != indexPath.row) { - NSDictionary *app = self.apps[indexPath.row]; - - alert = [UIAlertController alertControllerWithTitle:@"Decrypt" message:[NSString stringWithFormat:@"Decrypt %@?", app[@"name"]] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; - UIAlertAction *decrypt = [UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - decryptApp(app); - }]; - - [alert addAction:decrypt]; - [alert addAction:cancel]; - } else { - alert = [UIAlertController alertControllerWithTitle:@"Decrypt" message:@"Enter PID to decrypt" preferredStyle:UIAlertControllerStyleAlert]; - [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = @"PID"; - textField.keyboardType = UIKeyboardTypeNumberPad; - }]; - - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; - UIAlertAction *decrypt = [UIAlertAction actionWithTitle:@"Decrypt" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - NSString *pid = alert.textFields.firstObject.text; - decryptAppWithPID([pid intValue]); - }]; - - [alert addAction:decrypt]; - [alert addAction:cancel]; - } - - [self presentViewController:alert animated:YES completion:nil]; - [tableView deselectRowAtIndexPath:indexPath animated:YES]; -} - -@end \ No newline at end of file diff --git a/TDUtils.m b/TDUtils.m deleted file mode 100644 index a803467..0000000 --- a/TDUtils.m +++ /dev/null @@ -1,385 +0,0 @@ -#import "TDUtils.h" -#import "TDDumpDecrypted.h" -#import "LSApplicationProxy+AltList.h" - -UIWindow *alertWindow = NULL; -UIWindow *kw = NULL; -UIViewController *root = NULL; -UIAlertController *alertController = NULL; -UIAlertController *doneController = NULL; -UIAlertController *errorController = NULL; - -NSArray *appList(void) { - NSMutableArray *apps = [NSMutableArray array]; - - NSArray *installedApplications = [[LSApplicationWorkspace defaultWorkspace] atl_allInstalledApplications]; - [installedApplications enumerateObjectsUsingBlock:^(LSApplicationProxy *proxy, NSUInteger idx, BOOL *stop) { - if (![proxy atl_isUserApplication]) return; - - NSString *bundleID = [proxy atl_bundleIdentifier]; - NSString *name = [proxy atl_nameToDisplay]; - NSString *version = [proxy atl_shortVersionString]; - NSString *executable = proxy.canonicalExecutablePath; - - if (!bundleID || !name || !version || !executable) return; - - NSDictionary *item = @{ - @"bundleID":bundleID, - @"name":name, - @"version":version, - @"executable":executable - }; - - [apps addObject:item]; - }]; - - NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]; - [apps sortUsingDescriptors:@[descriptor]]; - - [apps addObject:@{@"bundleID":@"", @"name":@"", @"version":@"", @"executable":@""}]; - - return [apps copy]; -} - -NSUInteger iconFormat(void) { - return (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) ? 8 : 10; -} - -NSArray *sysctl_ps(void) { - NSMutableArray *array = [[NSMutableArray alloc] init]; - - int numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); - pid_t pids[numberOfProcesses]; - bzero(pids, sizeof(pids)); - proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); - for (int i = 0; i < numberOfProcesses; ++i) { - if (pids[i] == 0) { continue; } - char pathBuffer[PROC_PIDPATHINFO_MAXSIZE]; - bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE); - proc_pidpath(pids[i], pathBuffer, sizeof(pathBuffer)); - - if (strlen(pathBuffer) > 0) { - NSString *processID = [[NSString alloc] initWithFormat:@"%d", pids[i]]; - NSString *processName = [[NSString stringWithUTF8String:pathBuffer] lastPathComponent]; - NSDictionary *dict = [[NSDictionary alloc] initWithObjects:[NSArray arrayWithObjects:processID, processName, nil] forKeys:[NSArray arrayWithObjects:@"pid", @"proc_name", nil]]; - - [array addObject:dict]; - } - } - - return [array copy]; -} - -void decryptApp(NSDictionary *app) { - dispatch_async(dispatch_get_main_queue(), ^{ - alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; - alertWindow.rootViewController = [UIViewController new]; - alertWindow.windowLevel = UIWindowLevelAlert + 1; - [alertWindow makeKeyAndVisible]; - - // Show a "Decrypting!" alert on the device and block the UI - - kw = alertWindow; - if([kw respondsToSelector:@selector(topmostPresentedViewController)]) - root = [kw performSelector:@selector(topmostPresentedViewController)]; - else - root = [kw rootViewController]; - root.modalPresentationStyle = UIModalPresentationFullScreen; - }); - - NSLog(@"[trolldecrypt] spawning thread to do decryption in background..."); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSLog(@"[trolldecrypt] inside decryption thread."); - - NSString *bundleID = app[@"bundleID"]; - NSString *name = app[@"name"]; - NSString *version = app[@"version"]; - NSString *executable = app[@"executable"]; - NSString *binaryName = [executable lastPathComponent]; - - NSLog(@"[trolldecrypt] bundleID: %@", bundleID); - NSLog(@"[trolldecrypt] name: %@", name); - NSLog(@"[trolldecrypt] version: %@", version); - NSLog(@"[trolldecrypt] executable: %@", executable); - NSLog(@"[trolldecrypt] binaryName: %@", binaryName); - - [[UIApplication sharedApplication] launchApplicationWithIdentifier:bundleID suspended:YES]; - sleep(1); - - pid_t pid = -1; - NSArray *processes = sysctl_ps(); - for (NSDictionary *process in processes) { - NSString *proc_name = process[@"proc_name"]; - if ([proc_name isEqualToString:binaryName]) { - pid = [process[@"pid"] intValue]; - break; - } - } - - if (pid == -1) { - dispatch_async(dispatch_get_main_queue(), ^{ - [alertController dismissViewControllerAnimated:NO completion:nil]; - NSLog(@"[trolldecrypt] failed to get pid for binary name: %@", binaryName); - - errorController = [UIAlertController alertControllerWithTitle:@"Error: -1" message:[NSString stringWithFormat:@"Failed to get PID for binary name: %@", binaryName] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Ok", @"Ok") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - NSLog(@"[trolldecrypt] Ok action"); - [errorController dismissViewControllerAnimated:NO completion:nil]; - [kw removeFromSuperview]; - kw.hidden = YES; - }]; - - [errorController addAction:okAction]; - [root presentViewController:errorController animated:YES completion:nil]; - }); - - return; - } - - NSLog(@"[trolldecrypt] pid: %d", pid); - - bfinject_rocknroll(pid, name, version); - }); -} - -void bfinject_rocknroll(pid_t pid, NSString *appName, NSString *version) { - NSLog(@"[trolldecrypt] Spawning thread to do decryption in the background..."); - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSLog(@"[trolldecrypt] Inside decryption thread"); - - char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - // int proc_pidpath(pid, pathbuf, sizeof(pathbuf)); - proc_pidpath(pid, pathbuf, sizeof(pathbuf)); - const char *fullPathStr = pathbuf; - - - NSLog(@"[trolldecrypt] fullPathStr: %s", fullPathStr); - DumpDecrypted *dd = [[DumpDecrypted alloc] initWithPathToBinary:[NSString stringWithUTF8String:fullPathStr] appName:appName appVersion:version]; - if(!dd) { - NSLog(@"[trolldecrypt] ERROR: failed to get DumpDecrypted instance"); - return; - } - - NSLog(@"[trolldecrypt] Full path to app: %s /// IPA File: %@", fullPathStr, [dd IPAPath]); - - dispatch_async(dispatch_get_main_queue(), ^{ - alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; - alertWindow.rootViewController = [UIViewController new]; - alertWindow.windowLevel = UIWindowLevelAlert + 1; - [alertWindow makeKeyAndVisible]; - - // Show a "Decrypting!" alert on the device and block the UI - alertController = [UIAlertController - alertControllerWithTitle:@"Decrypting" - message:@"Please wait, this will take a few seconds..." - preferredStyle:UIAlertControllerStyleAlert]; - - kw = alertWindow; - if([kw respondsToSelector:@selector(topmostPresentedViewController)]) - root = [kw performSelector:@selector(topmostPresentedViewController)]; - else - root = [kw rootViewController]; - root.modalPresentationStyle = UIModalPresentationFullScreen; - [root presentViewController:alertController animated:YES completion:nil]; - }); - - // Do the decryption - [dd createIPAFile:pid]; - - // Dismiss the alert box - dispatch_async(dispatch_get_main_queue(), ^{ - [alertController dismissViewControllerAnimated:NO completion:nil]; - - doneController = [UIAlertController alertControllerWithTitle:@"Decryption Complete!" message:[NSString stringWithFormat:@"IPA file saved to:\n%@", [dd IPAPath]] preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Ok", @"Ok") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [kw removeFromSuperview]; - kw.hidden = YES; - }]; - [doneController addAction:okAction]; - - if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"filza://"]]) { - UIAlertAction *openAction = [UIAlertAction actionWithTitle:@"Show in Filza" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [kw removeFromSuperview]; - kw.hidden = YES; - - NSString *urlString = [NSString stringWithFormat:@"filza://view%@", [dd IPAPath]]; - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString] options:@{} completionHandler:nil]; - }]; - [doneController addAction:openAction]; - } - - [root presentViewController:doneController animated:YES completion:nil]; - }); // dispatch on main - - NSLog(@"[trolldecrypt] Over and out."); - while(1) - sleep(9999999); - }); // dispatch in background - - NSLog(@"[trolldecrypt] All done, exiting constructor."); -} - -NSArray *decryptedFileList(void) { - NSMutableArray *files = [NSMutableArray array]; - NSMutableArray *fileNames = [NSMutableArray array]; - - // iterate through all files in the Documents directory - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:docPath()]; - - NSString *file; - while (file = [directoryEnumerator nextObject]) { - if ([[file pathExtension] isEqualToString:@"ipa"]) { - NSString *filePath = [[docPath() stringByAppendingPathComponent:file] stringByStandardizingPath]; - - NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:filePath error:nil]; - NSDate *modificationDate = fileAttributes[NSFileModificationDate]; - - NSDictionary *fileInfo = @{@"fileName": file, @"modificationDate": modificationDate}; - [files addObject:fileInfo]; - } - } - - // Sort the array based on modification date - NSArray *sortedFiles = [files sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - NSDate *date1 = [obj1 objectForKey:@"modificationDate"]; - NSDate *date2 = [obj2 objectForKey:@"modificationDate"]; - return [date2 compare:date1]; - }]; - - // Get the file names from the sorted array - for (NSDictionary *fileInfo in sortedFiles) { - [fileNames addObject:[fileInfo objectForKey:@"fileName"]]; - } - - return [fileNames copy]; -} - -NSString *docPath(void) { - NSError * error = nil; - [[NSFileManager defaultManager] createDirectoryAtPath:@"/var/mobile/Library/TrollDecrypt/decrypted" withIntermediateDirectories:YES attributes:nil error:&error]; - if (error != nil) { - NSLog(@"[trolldecrypt] error creating directory: %@", error); - } - - return @"/var/mobile/Library/TrollDecrypt/decrypted"; -} - -void decryptAppWithPID(pid_t pid) { - // generate App NSDictionary object to pass into decryptApp() - // proc_pidpath(self.pid, buffer, sizeof(buffer)); - NSString *message = nil; - NSString *error = nil; - - dispatch_async(dispatch_get_main_queue(), ^{ - alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; - alertWindow.rootViewController = [UIViewController new]; - alertWindow.windowLevel = UIWindowLevelAlert + 1; - [alertWindow makeKeyAndVisible]; - - // Show a "Decrypting!" alert on the device and block the UI - - kw = alertWindow; - if([kw respondsToSelector:@selector(topmostPresentedViewController)]) - root = [kw performSelector:@selector(topmostPresentedViewController)]; - else - root = [kw rootViewController]; - root.modalPresentationStyle = UIModalPresentationFullScreen; - }); - - NSLog(@"[trolldecrypt] pid: %d", pid); - - char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - proc_pidpath(pid, pathbuf, sizeof(pathbuf)); - - NSString *executable = [NSString stringWithUTF8String:pathbuf]; - NSString *path = [executable stringByDeletingLastPathComponent]; - NSDictionary *infoPlist = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Info.plist"]]; - NSString *bundleID = infoPlist[@"CFBundleIdentifier"]; - - if (!bundleID) { - error = @"Error: -2"; - message = [NSString stringWithFormat:@"Failed to get bundle id for pid: %d", pid]; - } - - LSApplicationProxy *app = [LSApplicationProxy applicationProxyForIdentifier:bundleID]; - if (!app) { - error = @"Error: -3"; - message = [NSString stringWithFormat:@"Failed to get LSApplicationProxy for bundle id: %@", bundleID]; - } - - if (message) { - dispatch_async(dispatch_get_main_queue(), ^{ - [alertController dismissViewControllerAnimated:NO completion:nil]; - NSLog(@"[trolldecrypt] failed to get bundleid for pid: %d", pid); - - errorController = [UIAlertController alertControllerWithTitle:error message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *okAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Ok", @"Ok") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - NSLog(@"[trolldecrypt] Ok action"); - [errorController dismissViewControllerAnimated:NO completion:nil]; - [kw removeFromSuperview]; - kw.hidden = YES; - }]; - - [errorController addAction:okAction]; - [root presentViewController:errorController animated:YES completion:nil]; - }); - } - - NSLog(@"[trolldecrypt] app: %@", app); - - NSDictionary *appInfo = @{ - @"bundleID":bundleID, - @"name":[app atl_nameToDisplay], - @"version":[app atl_shortVersionString], - @"executable":executable - }; - - NSLog(@"[trolldecrypt] appInfo: %@", appInfo); - - dispatch_async(dispatch_get_main_queue(), ^{ - [alertController dismissViewControllerAnimated:NO completion:nil]; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Decrypt" message:[NSString stringWithFormat:@"Decrypt %@?", appInfo[@"name"]] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; - UIAlertAction *decrypt = [UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - decryptApp(appInfo); - }]; - - [alert addAction:decrypt]; - [alert addAction:cancel]; - - [root presentViewController:alert animated:YES completion:nil]; - }); -} - -void github_fetchLatedVersion(NSString *repo, void (^completionHandler)(NSString *latestVersion)) { - NSString *urlString = [NSString stringWithFormat:@"https://api.github.com/repos/%@/releases/latest", repo]; - NSURL *url = [NSURL URLWithString:urlString]; - - NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (!error) { - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSError *jsonError; - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (!jsonError) { - NSString *version = [json[@"tag_name"] stringByReplacingOccurrencesOfString:@"v" withString:@""]; - completionHandler(version); - } - } - } - }]; - - [task resume]; -} - -void fetchLatestTrollDecryptVersion(void (^completionHandler)(NSString *version)) { - github_fetchLatedVersion(@"donato-fiore/TrollDecrypt", completionHandler); -} - -NSString *trollDecryptVersion(void) { - return [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; -} \ No newline at end of file diff --git a/main.m b/main.m deleted file mode 100644 index ad03dbd..0000000 --- a/main.m +++ /dev/null @@ -1,8 +0,0 @@ -#import -#import "TDAppDelegate.h" - -int main(int argc, char *argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass(TDAppDelegate.class)); - } -} diff --git a/src/LSApplicationProxy+AltList.h b/src/LSApplicationProxy+AltList.h new file mode 100644 index 0000000..973bee2 --- /dev/null +++ b/src/LSApplicationProxy+AltList.h @@ -0,0 +1,38 @@ +#import +#import + +@interface LSApplicationRecord : NSObject +@property(nonatomic, readonly) NSArray *appTags; // 'hidden' +@property(getter=isLaunchProhibited, readonly) BOOL launchProhibited; +@end + +@interface LSApplicationProxy (Additions) +@property(readonly, nonatomic) NSString *shortVersionString; +@property(nonatomic, readonly) NSString *localizedName; +@property(nonatomic, readonly) NSString *applicationType; // (User/System) +@property(nonatomic, readonly) NSArray *appTags; // 'hidden' +@property(getter=isLaunchProhibited, nonatomic, readonly) BOOL launchProhibited; ++ (instancetype)applicationProxyForIdentifier:(NSString *)identifier; +- (LSApplicationRecord *)correspondingApplicationRecord; +@end + +@interface LSApplicationWorkspace (Additions) +- (void)addObserver:(id)arg1; +- (void)removeObserver:(id)arg1; +- (void)enumerateApplicationsOfType:(NSUInteger)type + block:(void (^)(LSApplicationProxy *))block; +@end + +@interface LSApplicationProxy (AltList) +- (BOOL)atl_isSystemApplication; +- (BOOL)atl_isUserApplication; +- (BOOL)atl_isHidden; +- (NSString *)atl_fastDisplayName; +- (NSString *)atl_nameToDisplay; +- (NSString *)atl_shortVersionString; +@property(nonatomic, readonly) NSString *atl_bundleIdentifier; +@end + +@interface LSApplicationWorkspace (AltList) +- (NSArray *)atl_allInstalledApplications; +@end \ No newline at end of file diff --git a/src/LSApplicationProxy+AltList.m b/src/LSApplicationProxy+AltList.m new file mode 100644 index 0000000..d5e4bf6 --- /dev/null +++ b/src/LSApplicationProxy+AltList.m @@ -0,0 +1,180 @@ +#import "LSApplicationProxy+AltList.h" +#import + +@implementation LSApplicationProxy (AltList) + +- (BOOL)atl_isSystemApplication { + return + [self.applicationType isEqualToString:@"System"] && ![self atl_isHidden]; +} + +- (BOOL)atl_isUserApplication { + return [self.applicationType isEqualToString:@"User"] && ![self atl_isHidden]; +} + +// the tag " hidden " is also valid, so we need to check if any strings contain +// "hidden" instead +BOOL tagArrayContainsTag(NSArray *tagArr, NSString *tag) { + if (!tagArr || !tag) + return NO; + + __block BOOL found = NO; + + [tagArr enumerateObjectsUsingBlock:^(NSString *tagToCheck, NSUInteger idx, + BOOL *stop) { + if (![tagToCheck isKindOfClass:[NSString class]]) { + return; + } + + if ([tagToCheck rangeOfString:tag options:0].location != NSNotFound) { + found = YES; + *stop = YES; + } + }]; + + return found; +} + +// always returns NO on iOS 7 +- (BOOL)atl_isHidden { + NSArray *appTags; + NSArray *recordAppTags; + NSArray *sbAppTags; + + BOOL launchProhibited = NO; + + if ([self respondsToSelector:@selector(correspondingApplicationRecord)]) { + // On iOS 14, self.appTags is always empty but the application record still + // has the correct ones + LSApplicationRecord *record = [self correspondingApplicationRecord]; + recordAppTags = record.appTags; + launchProhibited = record.launchProhibited; + } + if ([self respondsToSelector:@selector(appTags)]) { + appTags = self.appTags; + } + if (!launchProhibited && + [self respondsToSelector:@selector(isLaunchProhibited)]) { + launchProhibited = self.launchProhibited; + } + + NSURL *bundleURL = self.bundleURL; + if (bundleURL && [bundleURL checkResourceIsReachableAndReturnError:nil]) { + NSBundle *bundle = [NSBundle bundleWithURL:bundleURL]; + sbAppTags = [bundle objectForInfoDictionaryKey:@"SBAppTags"]; + } + + BOOL isWebApplication = + ([self.atl_bundleIdentifier rangeOfString:@"com.apple.webapp" + options:NSCaseInsensitiveSearch] + .location != NSNotFound); + return tagArrayContainsTag(appTags, @"hidden") || + tagArrayContainsTag(recordAppTags, @"hidden") || + tagArrayContainsTag(sbAppTags, @"hidden") || isWebApplication || + launchProhibited; +} + +// Getting the display name is slow (up to 2ms) because it uses an IPC call +// this stacks up if you do it for every single application +// This method provides a faster way (around 0.5ms) to get the display name +// This reduces the overall time needed to sort the applications from ~230 to +// ~120ms on my test device +- (NSString *)atl_fastDisplayName { + NSString *cachedDisplayName = [self valueForKey:@"_localizedName"]; + if (cachedDisplayName && ![cachedDisplayName isEqualToString:@""]) { + return cachedDisplayName; + } + + NSString *localizedName; + + NSURL *bundleURL = self.bundleURL; + if (!bundleURL || ![bundleURL checkResourceIsReachableAndReturnError:nil]) { + localizedName = self.localizedName; + } else { + NSBundle *bundle = [NSBundle bundleWithURL:bundleURL]; + + localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if (![localizedName isKindOfClass:[NSString class]]) + localizedName = nil; + if (!localizedName || [localizedName isEqualToString:@""]) { + localizedName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; + if (![localizedName isKindOfClass:[NSString class]]) + localizedName = nil; + if (!localizedName || [localizedName isEqualToString:@""]) { + localizedName = + [bundle objectForInfoDictionaryKey:@"CFBundleExecutable"]; + if (![localizedName isKindOfClass:[NSString class]]) + localizedName = nil; + if (!localizedName || [localizedName isEqualToString:@""]) { + // last possible fallback: use slow IPC call + localizedName = self.localizedName; + } + } + } + } + + [self setValue:localizedName forKey:@"_localizedName"]; + return localizedName; +} + +- (NSString *)atl_nameToDisplay { + NSString *localizedName = [self atl_fastDisplayName]; + + if ([self.atl_bundleIdentifier rangeOfString:@"carplay" + options:NSCaseInsensitiveSearch] + .location != NSNotFound) { + if ([localizedName rangeOfString:@"carplay" + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, localizedName.length) + locale:[NSLocale currentLocale]] + .location == NSNotFound) { + return [localizedName stringByAppendingString:@" (CarPlay)"]; + } + } + + return localizedName; +} + +- (id)atl_bundleIdentifier { + // iOS 8-14 + if ([self respondsToSelector:@selector(bundleIdentifier)]) { + return [self bundleIdentifier]; + } + // iOS 7 + else { + return [self applicationIdentifier]; + } +} + +- (NSString *)atl_shortVersionString { + NSString *version = self.shortVersionString; + if (version == nil || [version isEqualToString:@""]) { + version = @"1.0"; + } + + return version; +} + +@end + +@implementation LSApplicationWorkspace (AltList) + +- (NSArray *)atl_allInstalledApplications { + if (![self respondsToSelector:@selector(enumerateApplicationsOfType: + block:)]) { + return [self allInstalledApplications]; + } + + NSMutableArray *installedApplications = [NSMutableArray new]; + [self enumerateApplicationsOfType:0 + block:^(LSApplicationProxy *appProxy) { + [installedApplications addObject:appProxy]; + }]; + [self enumerateApplicationsOfType:1 + block:^(LSApplicationProxy *appProxy) { + [installedApplications addObject:appProxy]; + }]; + return installedApplications; +} + +@end \ No newline at end of file diff --git a/src/TDAppDelegate.h b/src/TDAppDelegate.h new file mode 100644 index 0000000..32f838a --- /dev/null +++ b/src/TDAppDelegate.h @@ -0,0 +1,8 @@ +#import + +@interface TDAppDelegate : UIResponder + +@property(nonatomic, strong) UIWindow *window; +@property(nonatomic, strong) UINavigationController *rootViewController; + +@end diff --git a/src/TDAppDelegate.m b/src/TDAppDelegate.m new file mode 100644 index 0000000..4b1d128 --- /dev/null +++ b/src/TDAppDelegate.m @@ -0,0 +1,49 @@ +#import "TDAppDelegate.h" +#import "TDRootViewController.h" +#import "TDUtils.h" + +@implementation TDAppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + _rootViewController = [[UINavigationController alloc] + initWithRootViewController:[[TDRootViewController alloc] init]]; + _window.rootViewController = _rootViewController; + [_window makeKeyAndVisible]; + return YES; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options: + (NSDictionary *)options { + if ([url.scheme isEqualToString:@"trolldecrypt"] && + [url.host isEqualToString:@"decrypt"]) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url + resolvingAgainstBaseURL:NO]; + __block NSString *bundleID = nil; + [components.queryItems + enumerateObjectsUsingBlock:^(NSURLQueryItem *item, NSUInteger idx, + BOOL *stop) { + if ([item.name isEqualToString:@"id"]) { + bundleID = item.value; + *stop = YES; + } + }]; + + if (bundleID == nil) + return NO; + + for (NSDictionary *app in appList()) { + if ([app[@"bundleID"] isEqualToString:bundleID]) { + decryptApp(app); + return YES; + } + } + return NO; + } + return NO; +} + +@end diff --git a/src/TDDumpDecrypted.h b/src/TDDumpDecrypted.h new file mode 100644 index 0000000..4181125 --- /dev/null +++ b/src/TDDumpDecrypted.h @@ -0,0 +1,22 @@ +@interface DumpDecrypted : NSObject { + char decryptedAppPathStr[PATH_MAX]; + char *filename; + char *appDirName; + char *appDirPath; +} + +@property(assign) NSString *appPath; +@property(assign) NSString *docPath; +@property(assign) NSString *appName; +@property(assign) NSString *appVersion; + +- (id)initWithPathToBinary:(NSString *)pathToBinary + appName:(NSString *)appName + appVersion:(NSString *)appVersion; +- (void)createIPAFile:(pid_t)pid; +- (BOOL)dumpDecryptedImage:(vm_address_t)imageAddress + fileName:(const char *)encryptedImageFilenameStr + image:(int)imageNum + task:(vm_map_t)targetTask; +- (NSString *)IPAPath; +@end \ No newline at end of file diff --git a/src/TDDumpDecrypted.m b/src/TDDumpDecrypted.m new file mode 100644 index 0000000..9e0fb0c --- /dev/null +++ b/src/TDDumpDecrypted.m @@ -0,0 +1,681 @@ +/* + bfinject - Inject shared libraries into running App Store apps on iOS 11.x + < 11.2 https://github.com/BishopFox/bfinject + + Carl Livitt @ Bishop Fox + + Based on code originally by 10n1c: + https://github.com/stefanesser/dumpdecrypted/blob/master/dumpdecrypted.c Now + with the following enhancements: + - Dump ALL encrypted images in the target application: the app itself, + its frameworks, etc. + - Create a valid .ipa containing the decrypted binaries. Save it in + ~/Documents/decrypted-app.ipa + - The .ipa can be modified and re-signed with a developer cert for + redeployment to non-jailbroken devices + - Auto detection of all the necessary sandbox paths + - Converted into an Objective-C class for ease of use. +*/ +#import "SSZipArchive/SSZipArchive.h" +#import +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "TDDumpDecrypted.h" +#import "TDUtils.h" + +kern_return_t mach_vm_read_overwrite(vm_map_t, mach_vm_address_t, + mach_vm_size_t, mach_vm_address_t, + mach_vm_size_t *); +// kern_return_t mach_vm_read(vm_map_t target_task, mach_vm_address_t address, +// mach_vm_size_t size, vm_offset_t *data, mach_msg_type_number_t *dataCnt); +kern_return_t mach_vm_region(vm_map_read_t target_task, + mach_vm_address_t *address, mach_vm_size_t *size, + vm_region_flavor_t flavor, vm_region_info_t info, + mach_msg_type_number_t *infoCnt, + mach_port_t *object_name); + +// #define DEBUG(...) NSLog(__VA_ARGS__); +// #define DEBUG(...) {} + +#define swap32(value) \ + (((value & 0xFF000000) >> 24) | ((value & 0x00FF0000) >> 8) | \ + ((value & 0x0000FF00) << 8) | ((value & 0x000000FF) << 24)) + +uint8_t *readProcessMemory(mach_port_t proc, mach_vm_address_t addr, + mach_msg_type_number_t *size) { + mach_msg_type_number_t dataCnt = (mach_msg_type_number_t)*size; + vm_offset_t readMem; + + kern_return_t kr = vm_read(proc, addr, *size, &readMem, &dataCnt); + + if (kr) { + // fprintf (stderr, "Unable to read target task's memory @%p - kr 0x%x\n", + // (void *) addr, kr); + return NULL; + } + + return ((unsigned char *)readMem); +} + +static kern_return_t readmem(mach_vm_offset_t *buffer, + mach_vm_address_t address, mach_vm_size_t size, + vm_map_t targetTask, + vm_region_basic_info_data_64_t *info) { + // get task for pid + vm_map_t port = targetTask; + + kern_return_t kr; + mach_msg_type_number_t info_cnt = sizeof(vm_region_basic_info_data_64_t); + mach_port_t object_name; + mach_vm_size_t size_info; + mach_vm_address_t address_info = address; + kr = mach_vm_region(port, &address_info, &size_info, VM_REGION_BASIC_INFO_64, + (vm_region_info_t)info, &info_cnt, &object_name); + if (kr) { + fprintf(stderr, "[ERROR] mach_vm_region failed with error %d\n", (int)kr); + return KERN_FAILURE; + } + + /* read memory - vm_read_overwrite because we supply the buffer */ + mach_vm_size_t nread; + kr = mach_vm_read_overwrite(port, address, size, (mach_vm_address_t)buffer, + &nread); + if (kr) { + fprintf(stderr, "[ERROR] vm_read failed! %d\n", kr); + return KERN_FAILURE; + } else if (nread != size) { + fprintf(stderr, + "[ERROR] vm_read failed! requested size: 0x%llx read: 0x%llx\n", + size, nread); + return KERN_FAILURE; + } + return KERN_SUCCESS; +} + +int64_t get_image_size(mach_vm_address_t address, vm_map_t targetTask, + uint64_t *vmaddr_slide) { + vm_region_basic_info_data_64_t region_info = {0}; + // allocate a buffer to read the header info + // NOTE: this is not exactly correct since the 64bit version has an extra 4 + // bytes but this will work for this purpose so no need for more complexity! + struct mach_header header = {0}; + if (readmem((mach_vm_offset_t *)&header, address, sizeof(struct mach_header), + targetTask, ®ion_info)) { + printf("Can't read header!\n"); + return -1; + } + + if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64) { + printf("[ERROR] Target is not a mach-o binary!\n"); + return -1; + } + + int64_t imagefilesize = -1; + /* read the load commands */ + uint8_t *loadcmds = (uint8_t *)malloc(header.sizeofcmds); + uint16_t mach_header_size = sizeof(struct mach_header); + if (header.magic == MH_MAGIC_64) { + mach_header_size = sizeof(struct mach_header_64); + } + if (readmem((mach_vm_offset_t *)loadcmds, address + mach_header_size, + header.sizeofcmds, targetTask, ®ion_info)) { + printf("Can't read load commands\n"); + free(loadcmds); + return -1; + } + + /* process and retrieve address and size of linkedit */ + uint8_t *loadCmdAddress = 0; + loadCmdAddress = (uint8_t *)loadcmds; + struct load_command *loadCommand = NULL; + struct segment_command *segCmd = NULL; + struct segment_command_64 *segCmd64 = NULL; + for (uint32_t i = 0; i < header.ncmds; i++) { + loadCommand = (struct load_command *)loadCmdAddress; + if (loadCommand->cmd == LC_SEGMENT) { + segCmd = (struct segment_command *)loadCmdAddress; + if (strncmp(segCmd->segname, "__PAGEZERO", 16) != 0) { + if (strncmp(segCmd->segname, "__TEXT", 16) == 0) { + *vmaddr_slide = address - segCmd->vmaddr; + } + imagefilesize += segCmd->filesize; + } + } else if (loadCommand->cmd == LC_SEGMENT_64) { + segCmd64 = (struct segment_command_64 *)loadCmdAddress; + if (strncmp(segCmd64->segname, "__PAGEZERO", 16) != 0) { + if (strncmp(segCmd64->segname, "__TEXT", 16) == 0) { + *vmaddr_slide = address - segCmd64->vmaddr; + } + imagefilesize += segCmd64->filesize; + } + } + // advance to next command + loadCmdAddress += loadCommand->cmdsize; + } + free(loadcmds); + return imagefilesize; +} + +int find_off_cryptid(const char *filePath) { + NSLog(@"[trolldecrypt] %s filePath: %s", __FUNCTION__, filePath); + int off_cryptid = 0; + FILE *file = fopen(filePath, "rb"); + if (!file) + return 1; + + fseek(file, 0, SEEK_END); + long fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + + char *buffer = malloc(fileSize); // new char[fileSize]; + fread(buffer, 1, fileSize, file); + + struct mach_header_64 *header = (struct mach_header_64 *)buffer; + if (header->magic != MH_MAGIC_64) { + printf("[-] error: not a valid macho file\n"); + free(buffer); + fclose(file); + return 2; + } + + struct load_command *lc = + (struct load_command *)((mach_vm_address_t)header + + sizeof(struct mach_header_64)); + for (uint32_t i = 0; i < header->ncmds; i++) { + if (lc->cmd == LC_ENCRYPTION_INFO || lc->cmd == LC_ENCRYPTION_INFO_64) { + struct encryption_info_command *encryption_info = + (struct encryption_info_command *)lc; + + NSLog(@"[trolldecrypt] cryptid: %d\n", encryption_info->cryptid); + NSLog(@"[trolldecrypt] Found cryptid at offset: 0x%llx\n", + (mach_vm_address_t)lc + + offsetof(struct encryption_info_command, cryptid) - + (mach_vm_address_t)header); + + off_cryptid = (mach_vm_address_t)lc + + offsetof(struct encryption_info_command, cryptid) - + (mach_vm_address_t)header; + break; + } + lc = (struct load_command *)((mach_vm_address_t)lc + lc->cmdsize); + } + free(buffer); + fclose(file); + + return off_cryptid; +} + +@implementation DumpDecrypted + +- (id)initWithPathToBinary:(NSString *)pathToBinary + appName:(NSString *)appName + appVersion:(NSString *)appVersion { + if (!self) { + self = [super init]; + } + + self.appName = appName; + self.appVersion = appVersion; + + [self setAppPath:[pathToBinary stringByDeletingLastPathComponent]]; + // [self setDocPath:[NSString stringWithFormat:@"%@/Documents", + // NSHomeDirectory()]]; + [self setDocPath:docPath()]; + + char *lastPartOfAppPath = strdup([[self appPath] UTF8String]); + lastPartOfAppPath = strrchr(lastPartOfAppPath, '/') + 1; + NSLog(@"[trolldecrypt] init: appDirName: %s", lastPartOfAppPath); + self->appDirName = strdup(lastPartOfAppPath); + + return self; +} + +- (void)makeDirectories:(const char *)encryptedImageFilenameStr { + char *appPath = (char *)[[self appPath] UTF8String]; + char *docPath = (char *)[[self docPath] UTF8String]; + char *savePtr; + char *encryptedImagePathStr = savePtr = strdup(encryptedImageFilenameStr); + self->filename = strdup(strrchr(encryptedImagePathStr, '/') + 1); + + // Normalize the filenames + if (strstr(encryptedImagePathStr, "/private") == encryptedImagePathStr) + encryptedImagePathStr += 8; + if (strstr(appPath, "/private") == appPath) + appPath += 8; + + // Find start of image path, relative to the base of the app sandbox (ie. + // /var/mobile/.../FooBar.app/THIS_PART_HERE) + encryptedImagePathStr += strlen(appPath) + 1; // skip over the app path + char *p = strrchr(encryptedImagePathStr, '/'); + if (p) + *p = '\0'; + + NSLog(@"[trolldecrypt] encryptedImagePathStr: %s", encryptedImagePathStr); + + NSFileManager *fm = [[NSFileManager alloc] init]; + NSError *err; + char *lastPartOfAppPath = strdup(appPath); // Must free() + lastPartOfAppPath = strrchr(lastPartOfAppPath, '/'); + lastPartOfAppPath++; + NSString *path = [NSString + stringWithFormat:@"%s/ipa/Payload/%s", docPath, lastPartOfAppPath]; + self->appDirPath = strdup([path UTF8String]); + if (p) + path = [NSString stringWithFormat:@"%@/%s", path, encryptedImagePathStr]; + + NSLog(@"[trolldecrypt] make_directories making dir: %@", path); + if (![fm createDirectoryAtPath:path + withIntermediateDirectories:true + attributes:nil + error:&err]) { + NSLog(@"[trolldecrypt] WARNING: make_directories failed to make directory " + @"%@. Error: %@", + path, err); + } + + free(savePtr); + + snprintf(self->decryptedAppPathStr, PATH_MAX, "%s/%s", [path UTF8String], + self -> filename); + + return; +} + +- (BOOL)dumpDecryptedImage:(vm_address_t)imageAddress + fileName:(const char *)encryptedImageFilenameStr + image:(int)imageNum + task:(vm_map_t)targetTask { + // struct load_command *lc; + struct encryption_info_command *eic; + struct fat_header *fh; + struct fat_arch *arch; + // struct mach_header *mh; + char buffer[1024]; + unsigned int fileoffs = 0, off_cryptid = 0, restsize; + int i, fd, outfd, r, n; + + struct mach_header header = {0}; + vm_region_basic_info_data_64_t region_info = {0}; + if (readmem((mach_vm_offset_t *)&header, imageAddress, + sizeof(struct mach_header), targetTask, ®ion_info)) { + NSLog(@"[trolldecrypt] Can't read header!"); + exit(1); + } + // XXX: change all image_mh -> header NOW + + struct load_command *lc = (struct load_command *)malloc(header.sizeofcmds); + + /* detect if this is a arm64 binary */ + if (header.magic == MH_MAGIC_64) { + // lc = (struct load_command *)((unsigned char *)header + sizeof(struct + // mach_header_64)); + readmem((mach_vm_offset_t *)lc, + imageAddress + sizeof(struct mach_header_64), header.sizeofcmds, + targetTask, ®ion_info); + NSLog(@"[trolldecrypt] detected 64bit ARM binary in memory.\n"); + } else if (header.magic == + MH_MAGIC) { /* we might want to check for other errors here, too */ + // lc = (struct load_command *)((unsigned char *)header + sizeof(struct + // mach_header)); + readmem((mach_vm_offset_t *)lc, imageAddress + sizeof(struct mach_header), + header.sizeofcmds, targetTask, ®ion_info); + NSLog(@"[trolldecrypt] detected 32bit ARM binary in memory.\n"); + } else { + NSLog(@"[trolldecrypt] No valid header found!!"); + return false; + } + + // sleep(1);exit(1); + const struct mach_header *image_mh = &header; + + /* searching all load commands for an LC_ENCRYPTION_INFO load command */ + for (i = 0; i < image_mh->ncmds; i++) { + if (lc->cmd == LC_ENCRYPTION_INFO || lc->cmd == LC_ENCRYPTION_INFO_64) { + eic = (struct encryption_info_command *)lc; + // struct encryption_info_command *eic = (uint8_t*)malloc(sizeof(struct + // encryption_info_command);); readmem((mach_vm_offset_t*)eic, + // imageAddress+sizeof(struct mach_header_64), header.sizeofcmds, + // targetTask, ®ion_info); + + const char *appFilename = strrchr(encryptedImageFilenameStr, '/'); + if (appFilename == NULL) { + NSLog(@"[trolldecrypt] There are no / in the filename. This is an " + @"error.\n"); + return false; + } + appFilename++; + + /* If this load command is present, but data is not crypted then exit */ + if (eic->cryptid == 0) { + NSLog(@"[trolldecrypt] CryptID = 0!! "); + return false; + } + + // Create a dir structure in ~ just like in /path/to/FooApp.app/Whatever + [self makeDirectories:encryptedImageFilenameStr]; + + // 0x1518 + uint64_t aslr_slide = 0; + get_image_size(imageAddress, targetTask, &aslr_slide); + NSLog(@"[trolldecrypt] aslr_slide= 0x%llx", aslr_slide); + + off_cryptid = find_off_cryptid(encryptedImageFilenameStr); + + NSLog(@"[trolldecrypt] offset to cryptid (%d) found in memory @ %p (from " + @"%p). off_cryptid = %u (0x%x)\n", + eic->cryptid, &eic->cryptid, image_mh, off_cryptid, off_cryptid); + // NSLog(@"[trolldecrypt] Found encrypted data at offset %u 0x%08x. + // image_mh @ %p. cryptedData @ 0x%x. cryptsize = %u (0x%x) bytes.\n", + // eic->cryptoff, eic->cryptoff, image_mh, (unsigned int)image_mh + + // eic->cryptoff, eic->cryptsize, eic->cryptsize); + + NSLog(@"[trolldecrypt] Dumping: %s", encryptedImageFilenameStr); + NSLog(@"[trolldecrypt] Into: %s", self->decryptedAppPathStr); + fd = open(encryptedImageFilenameStr, O_RDONLY); + if (fd == -1) { + NSLog(@"[trolldecrypt] Failed to open %s", encryptedImageFilenameStr); + return false; + } + + NSLog(@"[trolldecrypt] Reading header"); + n = read(fd, (void *)buffer, sizeof(buffer)); + if (n != sizeof(buffer)) { + NSLog(@"[trolldecrypt] Warning read only %d of %lu bytes from " + @"encrypted file.\n", + n, sizeof(buffer)); + return false; + } + + NSLog(@"[trolldecrypt] Detecting header type\n"); + fh = (struct fat_header *)buffer; + + /* Is this a FAT file - we assume the right endianess */ + if (fh->magic == FAT_CIGAM) { + NSLog(@"[trolldecrypt] Executable is a FAT image - searching for right " + @"architecture\n"); + arch = (struct fat_arch *)&fh[1]; + for (i = 0; i < swap32(fh->nfat_arch); i++) { + if ((image_mh->cputype == swap32(arch->cputype)) && + (image_mh->cpusubtype == swap32(arch->cpusubtype))) { + fileoffs = swap32(arch->offset); + NSLog( + @"[trolldecrypt] Correct arch is at offset 0x%x in the file.\n", + fileoffs); + break; + } + arch++; + } + if (fileoffs == 0) { + NSLog(@"[trolldecrypt] Could not find correct arch in FAT image\n"); + return false; + } + } else if (fh->magic == MH_MAGIC || fh->magic == MH_MAGIC_64) { + NSLog(@"[trolldecrypt] Executable is a plain MACH-O image, fileoffs = " + @"0\n"); + } else { + NSLog(@"[trolldecrypt] Executable is of unknown type, fileoffs = 0\n"); + return false; + } + + NSLog(@"[trolldecrypt] Opening %s for writing.\n", decryptedAppPathStr); + outfd = open(decryptedAppPathStr, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (outfd == -1) { + NSLog(@"[trolldecrypt] Failed opening: "); + return false; + } + + /* calculate address of beginning of crypted data */ + n = fileoffs + eic->cryptoff; + + restsize = lseek(fd, 0, SEEK_END) - n - eic->cryptsize; + // NSLog(@"[trolldecrypt] restsize = %u, n = %u, cryptsize = %u, total = + // %u", restsize, n, eic->cryptsize, n + eic->cryptsize + restsize); + lseek(fd, 0, SEEK_SET); + + NSLog(@"[trolldecrypt] Copying the not encrypted start of the file (%u " + @"bytes)\n", + n); + + /* first copy all the data before the encrypted data */ + char *buf = (char *)malloc((size_t)n); + r = read(fd, buf, n); + if (r != n) { + NSLog(@"[trolldecrypt] Error reading start of file\n"); + return false; + } + r = write(outfd, buf, n); + if (r != n) { + NSLog(@"[trolldecrypt] Error writing start of file\n"); + return false; + } + free(buf); + + /* now write the previously encrypted data */ + + NSLog(@"[trolldecrypt] Dumping the decrypted data into the file (%u " + @"bytes)\n", + eic->cryptsize); + buf = (char *)malloc((size_t)eic->cryptsize); + readmem((mach_vm_offset_t *)buf, imageAddress + eic->cryptoff, + eic->cryptsize, targetTask, ®ion_info); + // r = write(outfd, (unsigned char *)image_mh + eic->cryptoff, + // eic->cryptsize); + r = write(outfd, buf, eic->cryptsize); + if (r != eic->cryptsize) { + NSLog(@"[trolldecrypt] Error writing encrypted part of file\n"); + return false; + } + free(buf); + + /* and finish with the remainder of the file */ + NSLog(@"[trolldecrypt] Copying the not encrypted remainder of the file " + @"(%u bytes)\n", + restsize); + lseek(fd, eic->cryptsize, SEEK_CUR); + buf = (char *)malloc((size_t)restsize); + r = read(fd, buf, restsize); + if (r != restsize) { + NSLog(@"[trolldecrypt] Error reading rest of file, got %u bytes\n", r); + return false; + } + r = write(outfd, buf, restsize); + if (r != restsize) { + NSLog(@"[trolldecrypt] Error writing rest of file\n"); + return false; + } + free(buf); + + if (off_cryptid) { + uint32_t zero = 0; + NSLog(@"[trolldecrypt] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at " + @"offset 0x%x into file\n", + off_cryptid); + if (lseek(outfd, off_cryptid, SEEK_SET) == off_cryptid) { + if (write(outfd, &zero, 4) != 4) { + NSLog(@"[trolldecrypt] Error writing cryptid value!!\n"); + // Not a fatal error, just warn + } + } else { + NSLog(@"[trolldecrypt] Failed to seek to cryptid offset!!"); + // this error is not treated as fatal + } + } + + close(fd); + close(outfd); + sync(); + // exit(1); + + return true; + } + + lc = (struct load_command *)((unsigned char *)lc + lc->cmdsize); + // readmem((mach_vm_offset_t*)lc, lc+lc->cmdsize, header.sizeofcmds, + // targetTask, ®ion_info); + } + // DEBUG(@"[!] This mach-o file is not encrypted. Nothing was decrypted.\n"); + return false; +} + +- (void)dumpDecrypted:(pid_t)pid { + + vm_map_t targetTask = 0; + if (task_for_pid(mach_task_self(), pid, &targetTask)) { + NSLog(@"[trolldecrypt] Can't execute task_for_pid! Do you have the right " + @"permissions/entitlements?\n"); + exit(1); + } + + // numberOfImages + struct task_dyld_info dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + + if (task_info(targetTask, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count) != + KERN_SUCCESS) + exit(1); + + mach_msg_type_number_t size = sizeof(struct dyld_all_image_infos); + uint8_t *data = + readProcessMemory(targetTask, dyld_info.all_image_info_addr, &size); + struct dyld_all_image_infos *infos = (struct dyld_all_image_infos *)data; + + mach_msg_type_number_t size2 = + sizeof(struct dyld_image_info) * infos->infoArrayCount; + uint8_t *info_addr = readProcessMemory( + targetTask, (mach_vm_address_t)infos->infoArray, &size2); + struct dyld_image_info *info = (struct dyld_image_info *)info_addr; + + uint32_t numberOfImages = infos->infoArrayCount; + mach_vm_address_t imageAddress = 0; + const char *appPath = [[self appPath] UTF8String]; + + NSLog(@"[trolldecrypt] There are %d images mapped.", numberOfImages); + + for (int i = 0; i < numberOfImages; i++) { + NSLog(@"[trolldecrypt] image %d", i); + mach_msg_type_number_t size3 = PATH_MAX; + const char *imageName = (char *)readProcessMemory( + targetTask, (mach_vm_address_t)info[i].imageFilePath, &size3); + imageAddress = (vm_address_t)info[i].imageLoadAddress; + + if (!imageName || !imageAddress) + continue; + + NSLog(@"[trolldecrypt] Comparing %s to %s", imageName, appPath); + + if (strstr(imageName, appPath) != NULL) { + NSLog(@"[trolldecrypt] Dumping image %d: %s", i, imageName); + [self dumpDecryptedImage:imageAddress + fileName:imageName + image:i + task:targetTask]; + } + } +} + +- (BOOL)fileManager:(NSFileManager *)f + shouldProceedAfterError:(BOOL)proceed + copyingItemAtPath:(NSString *)path + toPath:(NSString *)dest { + return true; +} + +- (NSString *)IPAPath { + return [NSString stringWithFormat:@"%@/%@_%@_decrypted.ipa", [self docPath], + self.appName, self.appVersion]; +} + +- (void)createIPAFile:(pid_t)pid { + NSString *IPAFile = [self IPAPath]; + NSString *appDir = [self appPath]; + NSString *appCopyDir = + [NSString stringWithFormat:@"%@/ipa/Payload/%s", [self docPath], + self -> appDirName]; + NSString *zipDir = [NSString stringWithFormat:@"%@/ipa", [self docPath]]; + NSFileManager *fm = [[NSFileManager alloc] init]; + NSError *err; + + [fm removeItemAtPath:IPAFile error:nil]; + [fm removeItemAtPath:appCopyDir error:nil]; + [fm createDirectoryAtPath:appCopyDir + withIntermediateDirectories:true + attributes:nil + error:nil]; + + [fm setDelegate:(id)self]; + + NSLog(@"[trolldecrypt] ======== START FILE COPY - IGNORE ANY SANDBOX " + @"WARNINGS ========"); + NSLog(@"[trolldecrypt] IPAFile: %@", IPAFile); + NSLog(@"[trolldecrypt] appDir: %@", appDir); + NSLog(@"[trolldecrypt] appCopyDir: %@", appCopyDir); + NSLog(@"[trolldecrypt] zipDir: %@", zipDir); + + [fm copyItemAtPath:appDir toPath:appCopyDir error:&err]; + NSLog(@"[trolldecrypt] ======== END OF FILE COPY ========"); + // sleep(1); + // exit(1); + // Replace encrypted binaries with decrypted versions + NSLog(@"[trolldecrypt] ======== START DECRYPTION PROCESS ========"); + [self dumpDecrypted:pid]; + NSLog(@"[trolldecrypt] ======== DECRYPTION COMPLETE ========"); + + // ZIP it up + NSLog(@"[trolldecrypt] ======== STARTING ZIP ========"); + NSLog(@"[trolldecrypt] IPA file: %@", IPAFile); + NSLog(@"[trolldecrypt] ZIP dir: %@", zipDir); + unlink([IPAFile UTF8String]); + @try { + BOOL success = [SSZipArchive createZipFileAtPath:IPAFile + withContentsOfDirectory:zipDir + keepParentDirectory:NO + compressionLevel:1 + password:nil + AES:NO + progressHandler:nil]; + NSLog(@"[trolldecrypt] ======== ZIP operation complete: %s ========", + (success) ? "success" : "failed"); + } @catch (NSException *e) { + NSLog(@"[trolldecrypt] BAAAAAAAARF during ZIP operation!!! , %@", e); + } + + // Clean up. Leave only the .ipa file. + [fm removeItemAtPath:zipDir error:nil]; + + NSLog(@"[trolldecrypt] ======== Wrote %@ ========", [self IPAPath]); + return; +} + +@end \ No newline at end of file diff --git a/TDFileManagerViewController.h b/src/TDFileManagerViewController.h similarity index 65% rename from TDFileManagerViewController.h rename to src/TDFileManagerViewController.h index cc3aaae..687c07d 100644 --- a/TDFileManagerViewController.h +++ b/src/TDFileManagerViewController.h @@ -2,6 +2,6 @@ @interface TDFileManagerViewController : UITableViewController -@property (nonatomic, strong) NSArray *fileList; +@property(nonatomic, strong) NSArray *fileList; @end \ No newline at end of file diff --git a/src/TDFileManagerViewController.m b/src/TDFileManagerViewController.m new file mode 100644 index 0000000..efd85cb --- /dev/null +++ b/src/TDFileManagerViewController.m @@ -0,0 +1,123 @@ +#import "TDFileManagerViewController.h" +#import "TDUtils.h" + +@implementation TDFileManagerViewController + +- (void)loadView { + [super loadView]; + + self.title = @"Decrypted IPAs"; + self.fileList = decryptedFileList(); + + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithTitle:@"Done" + style:UIBarButtonItemStyleDone + target:self + action:@selector(done)]; +} + +- (void)refresh { + self.fileList = decryptedFileList(); + [self.tableView reloadData]; +} + +- (void)done { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (NSInteger)numberOfSelectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return self.fileList.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellIdentifier = @"FileCell"; + UITableViewCell *cell = + [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:cellIdentifier]; + } + + NSString *path = + [docPath() stringByAppendingPathComponent:self.fileList[indexPath.row]]; + NSDictionary *attributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]; + NSDate *date = attributes[NSFileModificationDate]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"MMM d, yyyy h:mm a"]; + + NSNumber *fileSize = attributes[NSFileSize]; + + cell.textLabel.text = self.fileList[indexPath.row]; + cell.detailTextLabel.text = [dateFormatter stringFromDate:date]; + cell.detailTextLabel.textColor = [UIColor systemGray2Color]; + cell.imageView.image = [UIImage systemImageNamed:@"doc.fill"]; + + UILabel *label = [[UILabel alloc] init]; + label.text = [NSString + stringWithFormat:@"%.2f MB", [fileSize doubleValue] / 1000000.0f]; + label.textColor = [UIColor systemGray2Color]; + label.font = [UIFont systemFontOfSize:12.0f]; + [label sizeToFit]; + label.textAlignment = NSTextAlignmentCenter; + cell.accessoryView = label; + + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView + heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 65.0f; +} + +- (bool)tableView:(UITableView *)tableView + canEditRowAtIndexPath:(NSIndexPath *)indexPath { + return YES; +} + +- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView + trailingSwipeActionsConfigurationForRowAtIndexPath: + (NSIndexPath *)indexPath { + UIContextualAction *deleteAction = [UIContextualAction + contextualActionWithStyle:UIContextualActionStyleDestructive + title:@"Delete" + handler:^(UIContextualAction *action, + UIView *sourceView, + void (^completionHandler)(BOOL)) { + NSString *file = self.fileList[indexPath.row]; + NSString *path = + [docPath() stringByAppendingPathComponent:file]; + [[NSFileManager defaultManager] removeItemAtPath:path + error:nil]; + [self refresh]; + }]; + + UISwipeActionsConfiguration *swipeActions = + [UISwipeActionsConfiguration configurationWithActions:@[ deleteAction ]]; + return swipeActions; +} + +- (void)tableView:(UITableView *)tableView + didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + NSString *file = self.fileList[indexPath.row]; + NSString *path = [docPath() stringByAppendingPathComponent:file]; + NSURL *url = [NSURL fileURLWithPath:path]; + + UIActivityViewController *activityViewController = + [[UIActivityViewController alloc] initWithActivityItems:@[ url ] + applicationActivities:nil]; + [self presentViewController:activityViewController + animated:YES + completion:nil]; + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end \ No newline at end of file diff --git a/TDRootViewController.h b/src/TDRootViewController.h similarity index 66% rename from TDRootViewController.h rename to src/TDRootViewController.h index 7b50b79..f9b18ed 100644 --- a/TDRootViewController.h +++ b/src/TDRootViewController.h @@ -2,6 +2,6 @@ @interface TDRootViewController : UITableViewController -@property (nonatomic, strong) NSArray *apps; +@property(nonatomic, strong) NSArray *apps; @end diff --git a/src/TDRootViewController.m b/src/TDRootViewController.m new file mode 100644 index 0000000..c06b6ff --- /dev/null +++ b/src/TDRootViewController.m @@ -0,0 +1,203 @@ +#import "TDRootViewController.h" +#import "TDFileManagerViewController.h" +#import "TDUtils.h" + +@implementation TDRootViewController + +- (void)loadView { + [super loadView]; + + self.apps = appList(); + self.title = @"TrollDecrypt"; + self.navigationController.navigationBar.prefersLargeTitles = YES; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] + initWithImage:[UIImage systemImageNamed:@"info.circle"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(about:)]; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] + initWithImage:[UIImage systemImageNamed:@"folder"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(openDocs:)]; + + UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init]; + [refreshControl addTarget:self + action:@selector(refreshApps:) + forControlEvents:UIControlEventValueChanged]; + self.refreshControl = refreshControl; +} + +- (void)viewDidAppear:(bool)animated { + [super viewDidAppear:animated]; + + fetchLatestTrollDecryptVersion(^(NSString *latestVersion) { + NSString *currentVersion = trollDecryptVersion(); + NSComparisonResult result = [currentVersion compare:latestVersion + options:NSNumericSearch]; + NSLog(@"[trolldecrypter] Current version: %@, Latest version: %@", + currentVersion, latestVersion); + if (result == NSOrderedAscending) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"Update Available" + message:@"An update for TrollDecrypt is available." + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = + [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]; + UIAlertAction *update = [UIAlertAction + actionWithTitle:@"Download" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [[UIApplication sharedApplication] + openURL: + [NSURL + URLWithString: + [NSString + stringWithFormat: + @"https://github.com/" + @"donato-fiore/" + @"TrollDecrypt/" + @"releases/latest"]] + options:@{} + completionHandler:nil]; + }]; + + [alert addAction:update]; + [alert addAction:cancel]; + [self presentViewController:alert animated:YES completion:nil]; + }); + } + }); +} + +- (void)openDocs:(id)sender { + TDFileManagerViewController *fmVC = + [[TDFileManagerViewController alloc] init]; + UINavigationController *navController = + [[UINavigationController alloc] initWithRootViewController:fmVC]; + [self presentViewController:navController animated:YES completion:nil]; +} + +- (void)about:(id)sender { + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"TrollDecrypt" + message:@"by fiore\nIcon by @super.user\nbfdecrypt by " + @"@bishopfox\ndumpdecrypted by @i0n1c\nUpdated " + @"for TrollStore by @wh1te4ever" + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" + style:UIAlertActionStyleCancel + handler:nil]]; + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)refreshApps:(UIRefreshControl *)refreshControl { + self.apps = appList(); + [self.tableView reloadData]; + [refreshControl endRefreshing]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView + numberOfRowsInSection:(NSInteger)section { + return self.apps.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellIdentifier = @"AppCell"; + UITableViewCell *cell; + if (([self.apps count] - 1) != indexPath.row) { + cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; + if (cell == nil) + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:cellIdentifier]; + + NSDictionary *app = self.apps[indexPath.row]; + + cell.textLabel.text = app[@"name"]; + cell.detailTextLabel.text = [NSString + stringWithFormat:@"%@ • %@", app[@"version"], app[@"bundleID"]]; + cell.imageView.image = [UIImage + _applicationIconImageForBundleIdentifier:app[@"bundleID"] + format:iconFormat() + scale:[UIScreen mainScreen].scale]; + } else { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:nil]; + + cell.textLabel.text = @"Advanced"; + cell.detailTextLabel.text = @"Decrypt app from a specified PID"; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView + heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 80.0f; +} + +- (void)tableView:(UITableView *)tableView + didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + UIAlertController *alert; + + if (([self.apps count] - 1) != indexPath.row) { + NSDictionary *app = self.apps[indexPath.row]; + + alert = [UIAlertController + alertControllerWithTitle:@"Decrypt" + message:[NSString stringWithFormat:@"Decrypt %@?", + app[@"name"]] + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = + [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]; + UIAlertAction *decrypt = + [UIAlertAction actionWithTitle:@"Yes" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + decryptApp(app); + }]; + + [alert addAction:decrypt]; + [alert addAction:cancel]; + } else { + alert = [UIAlertController + alertControllerWithTitle:@"Decrypt" + message:@"Enter PID to decrypt" + preferredStyle:UIAlertControllerStyleAlert]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"PID"; + textField.keyboardType = UIKeyboardTypeNumberPad; + }]; + + UIAlertAction *cancel = + [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]; + UIAlertAction *decrypt = [UIAlertAction + actionWithTitle:@"Decrypt" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + NSString *pid = alert.textFields.firstObject.text; + decryptAppWithPID([pid intValue]); + }]; + + [alert addAction:decrypt]; + [alert addAction:cancel]; + } + + [self presentViewController:alert animated:YES completion:nil]; + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end \ No newline at end of file diff --git a/TDUtils.h b/src/TDUtils.h similarity index 59% rename from TDUtils.h rename to src/TDUtils.h index 0d4ab96..cd573d7 100644 --- a/TDUtils.h +++ b/src/TDUtils.h @@ -1,16 +1,16 @@ +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#import -#import #import +#import +#import @interface UIApplication (tweakName) + (id)sharedApplication; @@ -18,20 +18,24 @@ @end @interface UIImage (Private) -+ (UIImage *)_applicationIconImageForBundleIdentifier:(NSString *)bundleIdentifier format:(NSUInteger)format scale:(CGFloat)scale; ++ (UIImage *)_applicationIconImageForBundleIdentifier: + (NSString *)bundleIdentifier + format:(NSUInteger)format + scale:(CGFloat)scale; @end -#define PROC_PIDPATHINFO 11 -#define PROC_PIDPATHINFO_SIZE (MAXPATHLEN) -#define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN) -#define PROC_ALL_PIDS 1 +#define PROC_PIDPATHINFO 11 +#define PROC_PIDPATHINFO_SIZE (MAXPATHLEN) +#define PROC_PIDPATHINFO_MAXSIZE (4 * MAXPATHLEN) +#define PROC_ALL_PIDS 1 #ifndef DEBUG -# define NSLog(...) (void)0 +#define NSLog(...) (void)0 #endif int proc_pidpath(int pid, void *buffer, uint32_t buffersize); -int proc_listpids(uint32_t type, uint32_t typeinfo, void *buffer, int buffersize); +int proc_listpids(uint32_t type, uint32_t typeinfo, void *buffer, + int buffersize); NSArray *appList(void); NSUInteger iconFormat(void); NSArray *sysctl_ps(void); @@ -40,6 +44,8 @@ void decryptAppWithPID(pid_t pid); void bfinject_rocknroll(pid_t pid, NSString *appName, NSString *version); NSArray *decryptedFileList(void); NSString *docPath(void); -void fetchLatestTrollDecryptVersion(void (^completionHandler)(NSString *version)); -void github_fetchLatedVersion(NSString *repo, void (^completionHandler)(NSString *latestVersion)); +void fetchLatestTrollDecryptVersion( + void (^completionHandler)(NSString *version)); +void github_fetchLatedVersion( + NSString *repo, void (^completionHandler)(NSString *latestVersion)); NSString *trollDecryptVersion(void); \ No newline at end of file diff --git a/src/TDUtils.m b/src/TDUtils.m new file mode 100644 index 0000000..29e4877 --- /dev/null +++ b/src/TDUtils.m @@ -0,0 +1,493 @@ +#import "TDUtils.h" +#import "LSApplicationProxy+AltList.h" +#import "TDDumpDecrypted.h" + +UIWindow *alertWindow = NULL; +UIWindow *kw = NULL; +UIViewController *root = NULL; +UIAlertController *alertController = NULL; +UIAlertController *doneController = NULL; +UIAlertController *errorController = NULL; + +NSArray *appList(void) { + NSMutableArray *apps = [NSMutableArray array]; + + NSArray *installedApplications = + [[LSApplicationWorkspace defaultWorkspace] atl_allInstalledApplications]; + [installedApplications + enumerateObjectsUsingBlock:^(LSApplicationProxy *proxy, NSUInteger idx, + BOOL *stop) { + if (![proxy atl_isUserApplication]) + return; + + NSString *bundleID = [proxy atl_bundleIdentifier]; + NSString *name = [proxy atl_nameToDisplay]; + NSString *version = [proxy atl_shortVersionString]; + NSString *executable = proxy.canonicalExecutablePath; + + if (!bundleID || !name || !version || !executable) + return; + + NSDictionary *item = @{ + @"bundleID" : bundleID, + @"name" : name, + @"version" : version, + @"executable" : executable + }; + + [apps addObject:item]; + }]; + + NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] + initWithKey:@"name" + ascending:YES + selector:@selector(localizedCaseInsensitiveCompare:)]; + [apps sortUsingDescriptors:@[ descriptor ]]; + + [apps addObject:@{ + @"bundleID" : @"", + @"name" : @"", + @"version" : @"", + @"executable" : @"" + }]; + + return [apps copy]; +} + +NSUInteger iconFormat(void) { + return (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) + ? 8 + : 10; +} + +NSArray *sysctl_ps(void) { + NSMutableArray *array = [[NSMutableArray alloc] init]; + + int numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + pid_t pids[numberOfProcesses]; + bzero(pids, sizeof(pids)); + proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); + for (int i = 0; i < numberOfProcesses; ++i) { + if (pids[i] == 0) { + continue; + } + char pathBuffer[PROC_PIDPATHINFO_MAXSIZE]; + bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE); + proc_pidpath(pids[i], pathBuffer, sizeof(pathBuffer)); + + if (strlen(pathBuffer) > 0) { + NSString *processID = [[NSString alloc] initWithFormat:@"%d", pids[i]]; + NSString *processName = + [[NSString stringWithUTF8String:pathBuffer] lastPathComponent]; + NSDictionary *dict = [[NSDictionary alloc] + initWithObjects:[NSArray arrayWithObjects:processID, processName, nil] + forKeys:[NSArray arrayWithObjects:@"pid", @"proc_name", nil]]; + + [array addObject:dict]; + } + } + + return [array copy]; +} + +void decryptApp(NSDictionary *app) { + dispatch_async(dispatch_get_main_queue(), ^{ + alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + alertWindow.rootViewController = [UIViewController new]; + alertWindow.windowLevel = UIWindowLevelAlert + 1; + [alertWindow makeKeyAndVisible]; + + // Show a "Decrypting!" alert on the device and block the UI + + kw = alertWindow; + if ([kw respondsToSelector:@selector(topmostPresentedViewController)]) + root = [kw performSelector:@selector(topmostPresentedViewController)]; + else + root = [kw rootViewController]; + root.modalPresentationStyle = UIModalPresentationFullScreen; + }); + + NSLog(@"[trolldecrypt] spawning thread to do decryption in background..."); + + dispatch_async( + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSLog(@"[trolldecrypt] inside decryption thread."); + + NSString *bundleID = app[@"bundleID"]; + NSString *name = app[@"name"]; + NSString *version = app[@"version"]; + NSString *executable = app[@"executable"]; + NSString *binaryName = [executable lastPathComponent]; + + NSLog(@"[trolldecrypt] bundleID: %@", bundleID); + NSLog(@"[trolldecrypt] name: %@", name); + NSLog(@"[trolldecrypt] version: %@", version); + NSLog(@"[trolldecrypt] executable: %@", executable); + NSLog(@"[trolldecrypt] binaryName: %@", binaryName); + + [[UIApplication sharedApplication] + launchApplicationWithIdentifier:bundleID + suspended:YES]; + sleep(1); + + pid_t pid = -1; + NSArray *processes = sysctl_ps(); + for (NSDictionary *process in processes) { + NSString *proc_name = process[@"proc_name"]; + if ([proc_name isEqualToString:binaryName]) { + pid = [process[@"pid"] intValue]; + break; + } + } + + if (pid == -1) { + dispatch_async(dispatch_get_main_queue(), ^{ + [alertController dismissViewControllerAnimated:NO completion:nil]; + NSLog(@"[trolldecrypt] failed to get pid for binary name: %@", + binaryName); + + errorController = [UIAlertController + alertControllerWithTitle:@"Error: -1" + message:[NSString stringWithFormat: + @"Failed to get PID for " + @"binary name: %@", + binaryName] + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"Ok", @"Ok") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + NSLog(@"[trolldecrypt] Ok action"); + [errorController dismissViewControllerAnimated:NO + completion:nil]; + [kw removeFromSuperview]; + kw.hidden = YES; + }]; + + [errorController addAction:okAction]; + [root presentViewController:errorController + animated:YES + completion:nil]; + }); + + return; + } + + NSLog(@"[trolldecrypt] pid: %d", pid); + + bfinject_rocknroll(pid, name, version); + }); +} + +void bfinject_rocknroll(pid_t pid, NSString *appName, NSString *version) { + NSLog( + @"[trolldecrypt] Spawning thread to do decryption in the background..."); + + dispatch_async( + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSLog(@"[trolldecrypt] Inside decryption thread"); + + char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; + // int proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + const char *fullPathStr = pathbuf; + + NSLog(@"[trolldecrypt] fullPathStr: %s", fullPathStr); + DumpDecrypted *dd = [[DumpDecrypted alloc] + initWithPathToBinary:[NSString stringWithUTF8String:fullPathStr] + appName:appName + appVersion:version]; + if (!dd) { + NSLog(@"[trolldecrypt] ERROR: failed to get DumpDecrypted instance"); + return; + } + + NSLog(@"[trolldecrypt] Full path to app: %s /// IPA File: %@", + fullPathStr, [dd IPAPath]); + + dispatch_async(dispatch_get_main_queue(), ^{ + alertWindow = + [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + alertWindow.rootViewController = [UIViewController new]; + alertWindow.windowLevel = UIWindowLevelAlert + 1; + [alertWindow makeKeyAndVisible]; + + // Show a "Decrypting!" alert on the device and block the UI + alertController = [UIAlertController + alertControllerWithTitle:@"Decrypting" + message:@"Please wait, this will take a few " + @"seconds..." + preferredStyle:UIAlertControllerStyleAlert]; + + kw = alertWindow; + if ([kw respondsToSelector:@selector(topmostPresentedViewController)]) + root = + [kw performSelector:@selector(topmostPresentedViewController)]; + else + root = [kw rootViewController]; + root.modalPresentationStyle = UIModalPresentationFullScreen; + [root presentViewController:alertController + animated:YES + completion:nil]; + }); + + // Do the decryption + [dd createIPAFile:pid]; + + // Dismiss the alert box + dispatch_async(dispatch_get_main_queue(), ^{ + [alertController dismissViewControllerAnimated:NO completion:nil]; + + doneController = [UIAlertController + alertControllerWithTitle:@"Decryption Complete!" + message:[NSString stringWithFormat: + @"IPA file saved to:\n%@", + [dd IPAPath]] + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction *okAction = + [UIAlertAction actionWithTitle:NSLocalizedString(@"Ok", @"Ok") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [kw removeFromSuperview]; + kw.hidden = YES; + }]; + [doneController addAction:okAction]; + + if ([[UIApplication sharedApplication] + canOpenURL:[NSURL URLWithString:@"filza://"]]) { + UIAlertAction *openAction = [UIAlertAction + actionWithTitle:@"Show in Filza" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [kw removeFromSuperview]; + kw.hidden = YES; + + NSString *urlString = [NSString + stringWithFormat:@"filza://view%@", [dd IPAPath]]; + [[UIApplication sharedApplication] + openURL:[NSURL URLWithString:urlString] + options:@{} + completionHandler:nil]; + }]; + [doneController addAction:openAction]; + } + + [root presentViewController:doneController + animated:YES + completion:nil]; + }); // dispatch on main + + NSLog(@"[trolldecrypt] Over and out."); + while (1) + sleep(9999999); + }); // dispatch in background + + NSLog(@"[trolldecrypt] All done, exiting constructor."); +} + +NSArray *decryptedFileList(void) { + NSMutableArray *files = [NSMutableArray array]; + NSMutableArray *fileNames = [NSMutableArray array]; + + // iterate through all files in the Documents directory + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSDirectoryEnumerator *directoryEnumerator = + [fileManager enumeratorAtPath:docPath()]; + + NSString *file; + while (file = [directoryEnumerator nextObject]) { + if ([[file pathExtension] isEqualToString:@"ipa"]) { + NSString *filePath = [[docPath() stringByAppendingPathComponent:file] + stringByStandardizingPath]; + + NSDictionary *fileAttributes = + [fileManager attributesOfItemAtPath:filePath error:nil]; + NSDate *modificationDate = fileAttributes[NSFileModificationDate]; + + NSDictionary *fileInfo = + @{@"fileName" : file, @"modificationDate" : modificationDate}; + [files addObject:fileInfo]; + } + } + + // Sort the array based on modification date + NSArray *sortedFiles = + [files sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSDate *date1 = [obj1 objectForKey:@"modificationDate"]; + NSDate *date2 = [obj2 objectForKey:@"modificationDate"]; + return [date2 compare:date1]; + }]; + + // Get the file names from the sorted array + for (NSDictionary *fileInfo in sortedFiles) { + [fileNames addObject:[fileInfo objectForKey:@"fileName"]]; + } + + return [fileNames copy]; +} + +NSString *docPath(void) { + NSError *error = nil; + [[NSFileManager defaultManager] + createDirectoryAtPath:@"/var/mobile/Library/TrollDecrypt/decrypted" + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error != nil) { + NSLog(@"[trolldecrypt] error creating directory: %@", error); + } + + return @"/var/mobile/Library/TrollDecrypt/decrypted"; +} + +void decryptAppWithPID(pid_t pid) { + // generate App NSDictionary object to pass into decryptApp() + // proc_pidpath(self.pid, buffer, sizeof(buffer)); + NSString *message = nil; + NSString *error = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + alertWindow.rootViewController = [UIViewController new]; + alertWindow.windowLevel = UIWindowLevelAlert + 1; + [alertWindow makeKeyAndVisible]; + + // Show a "Decrypting!" alert on the device and block the UI + + kw = alertWindow; + if ([kw respondsToSelector:@selector(topmostPresentedViewController)]) + root = [kw performSelector:@selector(topmostPresentedViewController)]; + else + root = [kw rootViewController]; + root.modalPresentationStyle = UIModalPresentationFullScreen; + }); + + NSLog(@"[trolldecrypt] pid: %d", pid); + + char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; + proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + + NSString *executable = [NSString stringWithUTF8String:pathbuf]; + NSString *path = [executable stringByDeletingLastPathComponent]; + NSDictionary *infoPlist = + [NSDictionary dictionaryWithContentsOfFile: + [path stringByAppendingPathComponent:@"Info.plist"]]; + NSString *bundleID = infoPlist[@"CFBundleIdentifier"]; + + if (!bundleID) { + error = @"Error: -2"; + message = + [NSString stringWithFormat:@"Failed to get bundle id for pid: %d", pid]; + } + + LSApplicationProxy *app = + [LSApplicationProxy applicationProxyForIdentifier:bundleID]; + if (!app) { + error = @"Error: -3"; + message = [NSString + stringWithFormat:@"Failed to get LSApplicationProxy for bundle id: %@", + bundleID]; + } + + if (message) { + dispatch_async(dispatch_get_main_queue(), ^{ + [alertController dismissViewControllerAnimated:NO completion:nil]; + NSLog(@"[trolldecrypt] failed to get bundleid for pid: %d", pid); + + errorController = [UIAlertController + alertControllerWithTitle:error + message:message + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction + actionWithTitle:NSLocalizedString(@"Ok", @"Ok") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + NSLog(@"[trolldecrypt] Ok action"); + [errorController dismissViewControllerAnimated:NO + completion:nil]; + [kw removeFromSuperview]; + kw.hidden = YES; + }]; + + [errorController addAction:okAction]; + [root presentViewController:errorController animated:YES completion:nil]; + }); + } + + NSLog(@"[trolldecrypt] app: %@", app); + + NSDictionary *appInfo = @{ + @"bundleID" : bundleID, + @"name" : [app atl_nameToDisplay], + @"version" : [app atl_shortVersionString], + @"executable" : executable + }; + + NSLog(@"[trolldecrypt] appInfo: %@", appInfo); + + dispatch_async(dispatch_get_main_queue(), ^{ + [alertController dismissViewControllerAnimated:NO completion:nil]; + UIAlertController *alert = [UIAlertController + alertControllerWithTitle:@"Decrypt" + message:[NSString stringWithFormat:@"Decrypt %@?", + appInfo[@"name"]] + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancel = + [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:nil]; + UIAlertAction *decrypt = + [UIAlertAction actionWithTitle:@"Yes" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + decryptApp(appInfo); + }]; + + [alert addAction:decrypt]; + [alert addAction:cancel]; + + [root presentViewController:alert animated:YES completion:nil]; + }); +} + +void github_fetchLatedVersion( + NSString *repo, void (^completionHandler)(NSString *latestVersion)) { + NSString *urlString = [NSString + stringWithFormat:@"https://api.github.com/repos/%@/releases/latest", + repo]; + NSURL *url = [NSURL URLWithString:urlString]; + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] + dataTaskWithURL:url + completionHandler:^(NSData *data, NSURLResponse *response, + NSError *error) { + if (!error) { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSError *jsonError; + NSDictionary *json = + [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&jsonError]; + + if (!jsonError) { + NSString *version = + [json[@"tag_name"] stringByReplacingOccurrencesOfString:@"v" + withString:@""]; + completionHandler(version); + } + } + } + }]; + + [task resume]; +} + +void fetchLatestTrollDecryptVersion( + void (^completionHandler)(NSString *version)) { + github_fetchLatedVersion(@"donato-fiore/TrollDecrypt", completionHandler); +} + +NSString *trollDecryptVersion(void) { + return [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; +} \ No newline at end of file diff --git a/src/main.m b/src/main.m new file mode 100644 index 0000000..16cbc12 --- /dev/null +++ b/src/main.m @@ -0,0 +1,9 @@ +#import "TDAppDelegate.h" +#import + +int main(int argc, char *argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, + NSStringFromClass(TDAppDelegate.class)); + } +}