diff --git a/Clutch/.DS_Store b/Clutch/.DS_Store index 041604fb..b73f67ae 100644 Binary files a/Clutch/.DS_Store and b/Clutch/.DS_Store differ diff --git a/Clutch/ARM64Dumper.m b/Clutch/ARM64Dumper.m index b0b5a9b3..abf3e40c 100644 --- a/Clutch/ARM64Dumper.m +++ b/Clutch/ARM64Dumper.m @@ -30,18 +30,19 @@ - (BOOL)dumpBinary { NSString* swappedBinaryPath = _originalBinary.binaryPath, *newSinf = _originalBinary.sinfPath, *newSupp = _originalBinary.suppPath, *newSupf = _originalBinary.supfPath; // default values if we dont need to swap archs //check if cpusubtype matches - if ((_thinHeader.header.cpusubtype != [Device cpu_subtype]) && _originalBinary.hasMultipleARM64Slices) { - + if ((_thinHeader.header.cpusubtype != [Device cpu_subtype]) && (_originalBinary.hasMultipleARMSlices || (_originalBinary.hasARM64Slice && ([Device cpu_type]==CPU_TYPE_ARM64)))) { + NSString* suffix = [NSString stringWithFormat:@"_%@", [Dumper readableArchFromHeader:_thinHeader]]; swappedBinaryPath = [_originalBinary.binaryPath stringByAppendingString:suffix]; newSinf = [_originalBinary.sinfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.sinfPath.pathExtension]]; newSupp = [_originalBinary.suppPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.suppPath.pathExtension]]; - newSupf = [_originalBinary.supfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.supfPath.pathExtension]]; - + [self swapArch]; + } + //actual dumping [newFileHandle seekToFileOffset:_thinHeader.offset + _thinHeader.size]; @@ -181,6 +182,7 @@ - (BOOL)dumpBinary { //done dumping, let's wait for pid _kill(pid); + [newFileHandle closeFile]; if (![swappedBinaryPath isEqualToString:_originalBinary.binaryPath]) [[NSFileManager defaultManager]removeItemAtPath:swappedBinaryPath error:nil]; if (![newSinf isEqualToString:_originalBinary.sinfPath]) @@ -195,6 +197,7 @@ - (BOOL)dumpBinary { gotofail: _kill(pid); + [newFileHandle closeFile]; if (![swappedBinaryPath isEqualToString:_originalBinary.binaryPath]) [[NSFileManager defaultManager]removeItemAtPath:swappedBinaryPath error:nil]; if (![newSinf isEqualToString:_originalBinary.sinfPath]) @@ -203,6 +206,7 @@ - (BOOL)dumpBinary { [[NSFileManager defaultManager]removeItemAtPath:newSupp error:nil]; if (![newSupf isEqualToString:_originalBinary.supfPath]) [[NSFileManager defaultManager]removeItemAtPath:newSupf error:nil]; + return NO; } diff --git a/Clutch/ARMDumper.m b/Clutch/ARMDumper.m index 9b6bdc2d..9284703f 100644 --- a/Clutch/ARMDumper.m +++ b/Clutch/ARMDumper.m @@ -40,11 +40,11 @@ - (BOOL)dumpBinary { swappedBinaryPath = [_originalBinary.binaryPath stringByAppendingString:suffix]; newSinf = [_originalBinary.sinfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.sinfPath.pathExtension]]; newSupp = [_originalBinary.suppPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.suppPath.pathExtension]]; - + [self swapArch]; } - + //actual dumping [newFileHandle seekToFileOffset:_thinHeader.offset + _thinHeader.size]; @@ -121,8 +121,7 @@ - (BOOL)dumpBinary { pid = [self posix_spawn:swappedBinaryPath disableASLR:self.shouldDisableASLR]; if ((err = task_for_pid(mach_task_self(), pid, &port) != KERN_SUCCESS)) { - [[ClutchPrint sharedInstance] printError:@"Could not obtain mach port, did you sign with proper entitlements?"]; - sleep(60); + [[ClutchPrint sharedInstance] printError:@"Could not obtain mach port, either the process is dead (codesign error?) or entitlements were not properly signed!?"]; goto gotofail; } @@ -188,7 +187,7 @@ - (BOOL)dumpBinary { [[NSFileManager defaultManager]removeItemAtPath:newSinf error:nil]; if (![newSupp isEqualToString:_originalBinary.suppPath]) [[NSFileManager defaultManager]removeItemAtPath:newSupp error:nil]; - + [newFileHandle closeFile]; _kill(pid); return dumpResult; @@ -196,7 +195,7 @@ - (BOOL)dumpBinary { gotofail: _kill(pid); - + [newFileHandle closeFile]; if (![swappedBinaryPath isEqualToString:_originalBinary.binaryPath]) [[NSFileManager defaultManager]removeItemAtPath:swappedBinaryPath error:nil]; if (![newSinf isEqualToString:_originalBinary.sinfPath]) diff --git a/Clutch/Binary.m b/Clutch/Binary.m index 07c569df..7307e380 100644 --- a/Clutch/Binary.m +++ b/Clutch/Binary.m @@ -53,7 +53,7 @@ - (instancetype)initWithBundle:(ClutchBundle *)path _dumpOperation = [[BundleDumpOperation alloc]initWithBundle:_bundle]; - NSFileHandle *tmpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_bundle.executablePath.UTF8String, "r+"))]; + NSFileHandle *tmpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_bundle.executablePath.UTF8String, "r+")) closeOnDealloc:YES]; NSData *headersData = tmpHandle.availableData; diff --git a/Clutch/BundleDumpOperation.m b/Clutch/BundleDumpOperation.m index c052592d..3fa013c9 100644 --- a/Clutch/BundleDumpOperation.m +++ b/Clutch/BundleDumpOperation.m @@ -104,7 +104,7 @@ - (void)main { //if (![_application isKindOfClass:[Framework class]]) [_fileManager copyItemAtPath:originalBinary.binaryPath toPath:_binaryDumpPath error:nil]; - NSFileHandle *tmpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(originalBinary.binaryPath.UTF8String, "r+"))]; + NSFileHandle *tmpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(originalBinary.binaryPath.UTF8String, "r+")) closeOnDealloc:YES]; NSData *headersData = tmpHandle.availableData; @@ -121,6 +121,7 @@ - (void)main { headersFromBinary(headers, headersData, &numHeaders); + if (numHeaders == 0) { gbprintln(@"No compatible architecture found"); } @@ -159,7 +160,7 @@ - (void)main { [[ClutchPrint sharedInstance] printDeveloper:@"Found compatible dumper %@ for binary %@ with arch %@",_dumper,originalBinary,[Dumper readableArchFromHeader:macho]]; - NSFileHandle *_handle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(originalBinary.binaryPath.UTF8String, "r+"))]; + NSFileHandle *_handle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(originalBinary.binaryPath.UTF8String, "r+")) closeOnDealloc:YES]; _dumper.originalFileHandle = _handle; @@ -186,7 +187,7 @@ - (void)main { #pragma mark "stripping" headers in FAT binary if ([_headersToStrip count] > 0) { - NSFileHandle *_dumpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_binaryDumpPath.UTF8String, "r+"))]; + NSFileHandle *_dumpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_binaryDumpPath.UTF8String, "r+")) closeOnDealloc:YES]; uint32_t magic = [_dumpHandle intAtOffset:0]; NSData *buffer = [_dumpHandle readDataOfLength:4096]; @@ -219,10 +220,10 @@ - (void)main { #pragma mark lipo ftw [[NSFileManager defaultManager]moveItemAtPath:_binaryDumpPath toPath:[_binaryDumpPath stringByAppendingPathExtension:@"fatty"] error:nil]; - NSFileHandle *_fattyHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen([_binaryDumpPath stringByAppendingPathExtension:@"fatty"].UTF8String, "r+"))]; + NSFileHandle *_fattyHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen([_binaryDumpPath stringByAppendingPathExtension:@"fatty"].UTF8String, "r+")) closeOnDealloc:YES]; [[NSFileManager defaultManager] createFileAtPath:_binaryDumpPath contents:nil attributes:nil]; - _dumpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_binaryDumpPath.UTF8String, "r+"))]; + _dumpHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(_binaryDumpPath.UTF8String, "r+")) closeOnDealloc:YES]; [_dumpHandle replaceBytesInRange:NSMakeRange(0, sizeof(uint32_t)) withBytes:&magic]; diff --git a/Clutch/Clutch-Prefix.pch b/Clutch/Clutch-Prefix.pch index ac4b7e0c..4fc96c53 100644 --- a/Clutch/Clutch-Prefix.pch +++ b/Clutch/Clutch-Prefix.pch @@ -26,7 +26,7 @@ int diff_ms(struct timeval t1, struct timeval t2); -#define CLUTCH_VERSION_ @"2.0.3" +#define CLUTCH_VERSION_ @"2.0.4" #ifdef DEBUG #define CLUTCH_VERSION [NSString stringWithFormat:@"%@ DEBUG",CLUTCH_VERSION_] diff --git a/Clutch/ClutchPrint.m b/Clutch/ClutchPrint.m index 4a4cfa5a..52f9ad5f 100644 --- a/Clutch/ClutchPrint.m +++ b/Clutch/ClutchPrint.m @@ -66,6 +66,7 @@ - (void)print:(NSString *)format, ... - (void)printDeveloper:(NSString *)format, ... { +#ifdef DEBUG if (verboseLevel == ClutchPrinterVerboseLevelDeveloper || verboseLevel == ClutchPrinterVerboseLevelFull) { if (format != nil) @@ -78,6 +79,7 @@ - (void)printDeveloper:(NSString *)format, ... va_end(args); } } +#endif } - (void)printError:(NSString *)format, ... diff --git a/Clutch/Dumper.h b/Clutch/Dumper.h index aa0ce44b..be6f9bad 100644 --- a/Clutch/Dumper.h +++ b/Clutch/Dumper.h @@ -28,6 +28,7 @@ void _kill(pid_t pid); @property NSFileHandle *originalFileHandle; @property BOOL shouldDisableASLR; + + (NSString *)readableArchFromHeader:(thin_header)macho; + (NSString *)readableArchFromMachHeader:(struct mach_header)header; - (pid_t)posix_spawn:(NSString *)binaryPath disableASLR:(BOOL)yrn; diff --git a/Clutch/Dumper.m b/Clutch/Dumper.m index 1ad66bd4..9952583e 100644 --- a/Clutch/Dumper.m +++ b/Clutch/Dumper.m @@ -136,11 +136,14 @@ -(void)swapArch { newSupf = [_originalBinary.supfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.supfPath.pathExtension]]; } - NSError *error; - [[NSFileManager defaultManager]removeItemAtPath:swappedBinaryPath error:&error]; - [[ClutchPrint sharedInstance] printDeveloper: @"%@",error]; + NSError *error; + + if ([[NSFileManager defaultManager] fileExistsAtPath:swappedBinaryPath isDirectory:NO]){ + [[NSFileManager defaultManager]removeItemAtPath:swappedBinaryPath error:nil]; + } + [[NSFileManager defaultManager] copyItemAtPath:_originalBinary.binaryPath toPath:swappedBinaryPath error:&error]; @@ -155,7 +158,7 @@ -(void)swapArch { } [self.originalFileHandle closeFile]; - self.originalFileHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(swappedBinaryPath.UTF8String, "r+"))]; + self.originalFileHandle = [[NSFileHandle alloc]initWithFileDescriptor:fileno(fopen(swappedBinaryPath.UTF8String, "r+")) closeOnDealloc:YES]; uint32_t magic = [self.originalFileHandle intAtOffset:0]; bool shouldSwap = magic == FAT_CIGAM; @@ -165,30 +168,68 @@ -(void)swapArch { struct fat_header fat = *(struct fat_header *)buffer.bytes; fat.nfat_arch = SWAP(fat.nfat_arch); - uint32_t offset = sizeof(struct fat_header); + int offset = sizeof(struct fat_header); for (int i = 0; i < fat.nfat_arch; i++) { struct fat_arch arch; arch = *(struct fat_arch *)([buffer bytes] + offset); - if (((SWAP(arch.cputype) == _thinHeader.header.cputype) && (SWAP(arch.cpusubtype) == _thinHeader.header.cpusubtype))) { - int origOffset = SWAP(arch.offset); - arch.offset = SWAP(pow(2.0, SWAP(arch.align))); - [self.originalFileHandle seekToFileOffset:origOffset]; - NSData *machOData = [self.originalFileHandle readDataOfLength:SWAP(arch.size)]; - [self.originalFileHandle replaceBytesInRange:NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch)) withBytes:&arch]; - [self.originalFileHandle replaceBytesInRange:NSMakeRange(SWAP(arch.offset), SWAP(arch.size)) withBytes:[machOData bytes]]; - offset = SWAP(arch.offset) + SWAP(arch.size); - break; + if (!((SWAP(arch.cputype) == _thinHeader.header.cputype) && (SWAP(arch.cpusubtype) == _thinHeader.header.cpusubtype))) { + + if (SWAP(arch.cputype) == CPU_TYPE_ARM) { + switch (SWAP(arch.cpusubtype)) { + case CPU_SUBTYPE_ARM_V6: + arch.cputype = SWAP(CPU_TYPE_I386); + arch.cpusubtype = SWAP(CPU_SUBTYPE_PENTIUM_3_XEON); + break; + case CPU_SUBTYPE_ARM_V7: + arch.cputype = SWAP(CPU_TYPE_I386); + arch.cpusubtype = SWAP(CPU_SUBTYPE_PENTIUM_4); + break; + case CPU_SUBTYPE_ARM_V7S: + arch.cputype = SWAP(CPU_TYPE_I386); + arch.cpusubtype = SWAP(CPU_SUBTYPE_ITANIUM); + break; + case CPU_SUBTYPE_ARM_V7K: // Apple Watch FTW + arch.cputype = SWAP(CPU_TYPE_I386); + arch.cpusubtype = SWAP(CPU_SUBTYPE_XEON); + break; + default: + [[ClutchPrint sharedInstance] printColor:ClutchPrinterColorPurple format:@"Warning: A wild 32-bit cpusubtype appeared! %u", SWAP(arch.cpusubtype)]; + arch.cputype = SWAP(CPU_TYPE_I386); + arch.cpusubtype = SWAP(CPU_SUBTYPE_PENTIUM_3_XEON); //pentium 3 ftw + break; + + } + }else { + + switch (SWAP(arch.cpusubtype)) { + case CPU_SUBTYPE_ARM64_ALL: + arch.cputype = SWAP(CPU_TYPE_X86_64); + arch.cpusubtype = SWAP(CPU_SUBTYPE_X86_64_ALL); + break; + case CPU_SUBTYPE_ARM64_V8: + arch.cputype = SWAP(CPU_TYPE_X86_64); + arch.cpusubtype = SWAP(CPU_SUBTYPE_X86_64_H); + break; + default: + [[ClutchPrint sharedInstance] printColor:ClutchPrinterColorPurple format:@"Warning: A wild 64-bit cpusubtype appeared! %u", SWAP(arch.cpusubtype)]; + arch.cputype = SWAP(CPU_TYPE_X86_64); + arch.cpusubtype = SWAP(CPU_SUBTYPE_X86_64_ALL); + break; + } + + } + + [self.originalFileHandle replaceBytesInRange:NSMakeRange(offset, sizeof(struct fat_arch)) withBytes:&arch]; } offset += sizeof(struct fat_arch); } - uint32_t nfat_arch = SWAP(1); - [self.originalFileHandle replaceBytesInRange:NSMakeRange(sizeof(uint32_t), sizeof(uint32_t)) withBytes:&nfat_arch]; - [self.originalFileHandle truncateFileAtOffset:offset]; + [[ClutchPrint sharedInstance] printDeveloper: @"wrote new header to binary"]; + //we don't close file handle here since it's reused later in dumping! } - (BOOL)_dumpToFileHandle:(NSFileHandle *)fileHandle withDumpSize:(uint32_t)togo pages:(uint32_t)pages fromPort:(mach_port_t)port pid:(pid_t)pid aslrSlide:(mach_vm_address_t)__text_start codeSignature_hashOffset:(uint32_t)hashOffset codesign_begin:(uint32_t)begin diff --git a/Clutch/Framework64Dumper.m b/Clutch/Framework64Dumper.m index 11bd30b9..eb9d7653 100644 --- a/Clutch/Framework64Dumper.m +++ b/Clutch/Framework64Dumper.m @@ -28,16 +28,16 @@ - (BOOL)dumpBinary NSString* swappedBinaryPath = _originalBinary.binaryPath, *newSinf = _originalBinary.sinfPath, *newSupp = _originalBinary.suppPath, *newSupf = _originalBinary.supfPath; // default values if we dont need to swap archs + //check if cpusubtype matches if ((_thinHeader.header.cpusubtype != [Device cpu_subtype]) && _originalBinary.hasMultipleARM64Slices) { - NSString* suffix = [NSString stringWithFormat:@"_%@", [Dumper readableArchFromHeader:_thinHeader]]; swappedBinaryPath = [_originalBinary.binaryPath stringByAppendingString:suffix]; - newSinf = [_originalBinary.sinfPath stringByAppendingString:suffix]; - newSupp = [_originalBinary.suppPath stringByAppendingString:suffix]; - newSupf = [_originalBinary.supfPath stringByAppendingString:suffix]; - + newSinf = [_originalBinary.sinfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.sinfPath.pathExtension]]; + newSupp = [_originalBinary.suppPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.suppPath.pathExtension]]; + newSupf = [_originalBinary.supfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.supfPath.pathExtension]]; + [self swapArch]; } diff --git a/Clutch/FrameworkDumper.m b/Clutch/FrameworkDumper.m index e2f010d4..7906c2ad 100644 --- a/Clutch/FrameworkDumper.m +++ b/Clutch/FrameworkDumper.m @@ -25,7 +25,8 @@ - (BOOL)dumpBinary NSString *binaryDumpPath = [_originalBinary.workingPath stringByAppendingPathComponent:_originalBinary.binaryPath.lastPathComponent]; - NSString* swappedBinaryPath = _originalBinary.binaryPath, *newSinf = _originalBinary.sinfPath, *newSupp = _originalBinary.suppPath; // default values if we dont need to swap archs + + NSString* swappedBinaryPath = _originalBinary.binaryPath, *newSinf = _originalBinary.sinfPath, *newSupp = _originalBinary.suppPath, *newSupf = _originalBinary.supfPath; // default values if we dont need to swap archs //check if cpusubtype matches if ((_thinHeader.header.cpusubtype != [Device cpu_subtype]) && (_originalBinary.hasMultipleARMSlices || (_originalBinary.hasARM64Slice && ([Device cpu_type]==CPU_TYPE_ARM64)))) { @@ -35,6 +36,8 @@ - (BOOL)dumpBinary swappedBinaryPath = [_originalBinary.binaryPath stringByAppendingString:suffix]; newSinf = [_originalBinary.sinfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.sinfPath.pathExtension]]; newSupp = [_originalBinary.suppPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.suppPath.pathExtension]]; + newSupf = [_originalBinary.supfPath.stringByDeletingPathExtension stringByAppendingString:[suffix stringByAppendingPathExtension:_originalBinary.supfPath.pathExtension]]; + [self swapArch]; diff --git a/Clutch/main.m b/Clutch/main.m index 9aed8dd5..aac18a20 100644 --- a/Clutch/main.m +++ b/Clutch/main.m @@ -13,6 +13,7 @@ #import "FrameworkLoader.h" #import #import "ClutchPrint.h" +#import "NSTask.h" int diff_ms(struct timeval t1, struct timeval t2) { @@ -70,6 +71,7 @@ int main (int argc, const char * argv[]) return 0; } + GBOptionsHelper *options = [[GBOptionsHelper alloc] init]; options.applicationVersion = ^{ return CLUTCH_VERSION; }; @@ -84,7 +86,9 @@ int main (int argc, const char * argv[]) [options registerOption:0 long:@"version" description:@"Display version and exit" flags:GBValueNone|GBOptionNoPrint]; [options registerOption:'?' long:@"help" description:@"Display this help and exit" flags:GBValueNone|GBOptionNoPrint]; [options registerOption:'n' long:@"no-color" description:@"Print with colors disabled" flags:GBValueNone|GBOptionNoPrint]; +#ifdef DEBUG [options registerOption:'v' long:@"verbose" description:@"Print verbose messages" flags:GBValueNone|GBOptionNoPrint]; +#endif if (argc == 1) { [options printHelp]; diff --git a/README.md b/README.md index 26eee972..7a229595 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ Clutch Usage ------------ + +*Important* +Clutch may encounter `Segmentation Fault: 11` when dumping apps with a large number of frameworks. Increase your device's maximum number of open file descriptors +`ulimit -n 512` (default is 256) + ```sh Clutch [OPTIONS] -b --binary-dump Only dump binary files from specified bundleID