/* 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 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; 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]; if(strstr(appPath, "/private") == appPath) { appPath += 8; } 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 = (mach_vm_address_t)info[i].imageLoadAddress; const char *imageName = (const char *)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.tipa", [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