├── .clang-format ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── Clutch.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── Clutch.xcscheme ├── Clutch ├── ARM64Dumper.h ├── ARM64Dumper.m ├── ARMDumper.h ├── ARMDumper.m ├── ASLRDisabler.h ├── ASLRDisabler.m ├── Application.h ├── Application.m ├── Binary.h ├── Binary.m ├── BinaryDumpProtocol.h ├── BundleDumpOperation.h ├── BundleDumpOperation.m ├── CMakeLists.txt ├── Clutch-Prefix.pch ├── Clutch.entitlements ├── ClutchBundle.h ├── ClutchBundle.m ├── ClutchCommands.h ├── ClutchCommands.m ├── ClutchPrint.h ├── ClutchPrint.m ├── Device.h ├── Device.m ├── Dumper.h ├── Dumper.m ├── Extension.h ├── Extension.m ├── FBApplicationInfo.h ├── FinalizeDumpOperation.h ├── FinalizeDumpOperation.m ├── Framework.h ├── Framework.m ├── Framework64Dumper.h ├── Framework64Dumper.m ├── FrameworkDumper.h ├── FrameworkDumper.m ├── FrameworkLoader.h ├── FrameworkLoader.m ├── Info.plist ├── Info.plist.in ├── KJApplicationManager.h ├── KJApplicationManager.m ├── LSApplicationWorkspace.h ├── MiniZip │ ├── CMakeLists.txt │ ├── crypt.h │ ├── ioapi.c │ ├── ioapi.h │ ├── unzip.c │ ├── unzip.h │ ├── zip.c │ └── zip.h ├── NSBundle+Clutch.h ├── NSBundle+Clutch.m ├── NSData+Reading.h ├── NSData+Reading.m ├── NSFileHandle+Private.h ├── NSFileHandle+Private.m ├── NSTask.h ├── SCInfoBuilder.h ├── SCInfoBuilder.m ├── Stub.storyboard ├── ZipArchive.h ├── ZipArchive.m ├── ZipOperation.h ├── ZipOperation.m ├── mach_vm.h ├── main.m ├── move_and_sign.sh ├── optool-defines.h ├── optool-headers.h ├── optool-headers.m ├── optool-operations.h ├── optool-operations.m ├── optool.h ├── scinfo.h ├── scinfo.m ├── sha1.c └── sha1.h ├── LSApplicationProxy.h ├── README.md └── cmake ├── AddFramework.cmake ├── DebugUtils.cmake └── iphoneos.toolchain.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: ObjC 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: None 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: true 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | BraceWrapping: 22 | AfterClass: false 23 | AfterControlStatement: false 24 | AfterEnum: false 25 | AfterFunction: false 26 | AfterNamespace: false 27 | AfterObjCDeclaration: false 28 | AfterStruct: false 29 | AfterUnion: false 30 | BeforeCatch: false 31 | BeforeElse: false 32 | IndentBraces: false 33 | SplitEmptyFunction: true 34 | SplitEmptyRecord: true 35 | SplitEmptyNamespace: true 36 | BreakBeforeBinaryOperators: None 37 | BreakBeforeBraces: Attach 38 | BreakBeforeInheritanceComma: false 39 | BreakBeforeTernaryOperators: false 40 | BreakConstructorInitializersBeforeComma: false 41 | BreakConstructorInitializers: BeforeColon 42 | BreakAfterJavaFieldAnnotations: false 43 | BreakStringLiterals: true 44 | ColumnLimit: 120 45 | CommentPragmas: '^ IWYU pragma:' 46 | CompactNamespaces: false 47 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 48 | ConstructorInitializerIndentWidth: 4 49 | ContinuationIndentWidth: 4 50 | Cpp11BracedListStyle: true 51 | DerivePointerAlignment: false 52 | DisableFormat: false 53 | ExperimentalAutoDetectBinPacking: false 54 | FixNamespaceComments: true 55 | ForEachMacros: 56 | - foreach 57 | - Q_FOREACH 58 | - BOOST_FOREACH 59 | IncludeCategories: 60 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 61 | Priority: 2 62 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 63 | Priority: 3 64 | - Regex: '.*' 65 | Priority: 1 66 | IncludeIsMainRegex: '(Test)?$' 67 | IndentCaseLabels: true 68 | IndentWidth: 4 69 | IndentWrappedFunctionNames: false 70 | JavaScriptQuotes: Leave 71 | JavaScriptWrapImports: true 72 | KeepEmptyLinesAtTheStartOfBlocks: true 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 1 76 | NamespaceIndentation: None 77 | ObjCBlockIndentWidth: 4 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: true 80 | PenaltyBreakAssignment: 2 81 | PenaltyBreakBeforeFirstCallParameter: 19 82 | PenaltyBreakComment: 300 83 | PenaltyBreakFirstLessLess: 120 84 | PenaltyBreakString: 1000 85 | PenaltyExcessCharacter: 1000000 86 | PenaltyReturnTypeOnItsOwnLine: 60 87 | PointerAlignment: Right 88 | ReflowComments: true 89 | SortIncludes: true 90 | SortUsingDeclarations: true 91 | SpaceAfterCStyleCast: false 92 | SpaceAfterTemplateKeyword: true 93 | SpaceBeforeAssignmentOperators: true 94 | SpaceBeforeParens: ControlStatements 95 | SpaceInEmptyParentheses: false 96 | SpacesBeforeTrailingComments: 1 97 | SpacesInAngles: false 98 | SpacesInContainerLiterals: true 99 | SpacesInCStyleCastParentheses: false 100 | SpacesInParentheses: false 101 | SpacesInSquareBrackets: false 102 | Standard: Cpp11 103 | TabWidth: 4 104 | UseTab: Never 105 | --- 106 | Language: Cpp 107 | AccessModifierOffset: -4 108 | AlignAfterOpenBracket: Align 109 | AlignConsecutiveAssignments: false 110 | AlignConsecutiveDeclarations: false 111 | AlignEscapedNewlines: Right 112 | AlignOperands: true 113 | AlignTrailingComments: true 114 | AllowAllParametersOfDeclarationOnNextLine: true 115 | AllowShortBlocksOnASingleLine: false 116 | AllowShortCaseLabelsOnASingleLine: false 117 | AllowShortFunctionsOnASingleLine: None 118 | AllowShortIfStatementsOnASingleLine: false 119 | AllowShortLoopsOnASingleLine: false 120 | AlwaysBreakAfterReturnType: None 121 | AlwaysBreakBeforeMultilineStrings: false 122 | AlwaysBreakTemplateDeclarations: true 123 | BinPackArguments: false 124 | BinPackParameters: false 125 | BraceWrapping: 126 | AfterClass: false 127 | AfterControlStatement: false 128 | AfterEnum: false 129 | AfterFunction: false 130 | AfterNamespace: false 131 | AfterObjCDeclaration: false 132 | AfterStruct: false 133 | AfterUnion: false 134 | BeforeCatch: false 135 | BeforeElse: false 136 | IndentBraces: false 137 | SplitEmptyFunction: true 138 | SplitEmptyRecord: true 139 | SplitEmptyNamespace: true 140 | BreakBeforeBinaryOperators: None 141 | BreakBeforeBraces: Attach 142 | BreakBeforeInheritanceComma: false 143 | BreakBeforeTernaryOperators: false 144 | BreakConstructorInitializersBeforeComma: false 145 | BreakConstructorInitializers: BeforeColon 146 | BreakAfterJavaFieldAnnotations: false 147 | BreakStringLiterals: true 148 | ColumnLimit: 120 149 | CommentPragmas: '^ IWYU pragma:' 150 | CompactNamespaces: false 151 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 152 | ConstructorInitializerIndentWidth: 4 153 | ContinuationIndentWidth: 4 154 | Cpp11BracedListStyle: true 155 | DerivePointerAlignment: false 156 | DisableFormat: false 157 | ExperimentalAutoDetectBinPacking: false 158 | FixNamespaceComments: true 159 | ForEachMacros: 160 | - foreach 161 | - Q_FOREACH 162 | - BOOST_FOREACH 163 | IncludeCategories: 164 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 165 | Priority: 2 166 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 167 | Priority: 3 168 | - Regex: '.*' 169 | Priority: 1 170 | IncludeIsMainRegex: '(Test)?$' 171 | IndentCaseLabels: false 172 | IndentWidth: 4 173 | IndentWrappedFunctionNames: false 174 | JavaScriptQuotes: Leave 175 | JavaScriptWrapImports: true 176 | KeepEmptyLinesAtTheStartOfBlocks: true 177 | MacroBlockBegin: '' 178 | MacroBlockEnd: '' 179 | MaxEmptyLinesToKeep: 1 180 | NamespaceIndentation: None 181 | ObjCBlockIndentWidth: 4 182 | ObjCSpaceAfterProperty: true 183 | ObjCSpaceBeforeProtocolList: true 184 | PenaltyBreakAssignment: 2 185 | PenaltyBreakBeforeFirstCallParameter: 19 186 | PenaltyBreakComment: 300 187 | PenaltyBreakFirstLessLess: 120 188 | PenaltyBreakString: 1000 189 | PenaltyExcessCharacter: 1000000 190 | PenaltyReturnTypeOnItsOwnLine: 60 191 | PointerAlignment: Right 192 | ReflowComments: true 193 | SortIncludes: true 194 | SortUsingDeclarations: true 195 | SpaceAfterCStyleCast: false 196 | SpaceAfterTemplateKeyword: true 197 | SpaceBeforeAssignmentOperators: true 198 | SpaceBeforeParens: ControlStatements 199 | SpaceInEmptyParentheses: false 200 | SpacesBeforeTrailingComments: 1 201 | SpacesInAngles: false 202 | SpacesInContainerLiterals: true 203 | SpacesInCStyleCastParentheses: false 204 | SpacesInParentheses: false 205 | SpacesInSquareBrackets: false 206 | Standard: Cpp11 207 | TabWidth: 4 208 | UseTab: Never 209 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | (下面有中文版) 2 | 3 | Before filing an issue, please ensure you are using the master branch at the latest version and [building correctly](../README.md#building). 4 | 5 | Please search issues to make sure your issue has not been reported before. 6 | 7 | We only support iOS 8 and later. We only support publicly known jailbreak methods. Please use the latest version of your jailbreak before filing an issue. 8 | 9 | If you are receiving an immediate crash with the message *Killed: 9*, ensure the build is being signed correctly. Try these steps before filing an issue: 10 | 11 | * Rename the binary to something else like `clutch2` 12 | * Re-sign the binary on the device by copying `Clutch.entitlements` onto the device and using LDID: `ldid -SClutch.entitlements` 13 | 14 | If you are seeing the message *Error: Could not obtain mach port, either the process is dead (codesign error?) or entitlements were not properly signed!*, try re-signing or [building](../README.md#building) again. 15 | 16 | If a framework will not dump and you are not seeing the *Error: Could not obtain mach port, either the process is dead (codesign error?) or entitlements were not properly signed!* message, please file an issue. 17 | 18 | # General information 19 | 20 | Please delete the example text and fill this in: 21 | 22 | * iOS version: e.g 10.2, 9.3.3 23 | * Commit hash: 24 | * App bundle ID: `example.app.name` 25 | * App name: *Example app name* 26 | * Command used: example: `clutch -d ...` 27 | 28 | # Log 29 | 30 | Please post the complete log from the terminal here surrounded with backticks as [described here](https://help.github.com/articles/creating-and-highlighting-code-blocks/). 31 | 32 | ------- 33 | 34 | 提交 issue 之前,请确认您在使用的是 master 分支的最新版并且[编译过程正确](../README.md#building)。. 35 | 36 | 请先搜索 issue 列表以确认之前您的问题并没有被报告过。 37 | 38 | 我们只支持 iOS 8 及以后版本,以及公开的越狱方法。提交问题之前请确认你使用的越狱软件是最新版。 39 | 40 | 如果程序显示出 *Killed: 9* 然后瞬间崩溃,请确认可执行文件有被正确地签名。试试以下步骤: 41 | 42 | * 把可执行文件改个名字,比如改成 `clutch2` 43 | * 把 `Clutch.entitlements` 复制到设备上,然后运行 `ldid -SClutch.entitlements` 44 | 45 | 如果你看到这条提示信息 *Error: Could not obtain mach port, either the process is dead (codesign error?) or entitlements were not properly signed!* ,试试重新签名或者重新[编译](../README.md#building)。 46 | 47 | 如果有 framework 无法 dump 出来,也没有显示出 *Error: Could not obtain mach port, either the process is dead (codesign error?) or entitlements were not properly signed!* 的错误信息,请提交 issue 。 48 | 49 | # 一般信息 50 | 51 | 请删掉示例文字并填写相关信息。 52 | 53 | * iOS 版本:例如 10.2, 9.3.3 54 | * Commit hash:<可使用 `git log` 看到> 55 | * App bundle ID:`example.app.name` 56 | * App 名称:*Example app name* 57 | * 使用的命令:例如 `clutch -d ...` 58 | 59 | # Log 60 | 61 | 请在此贴出来自终端的完整 log ,并且用反引号括住,就像[这里](https://help.github.com/articles/creating-and-highlighting-code-blocks/)所描述的一样。 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.xcuserstate 4 | project.xcworkspace/ 5 | xcuserdata/ 6 | *.xccheckout 7 | *~.nib 8 | DerivedData/ 9 | *.pbxuser 10 | *.mode1v3 11 | *.mode2v3 12 | *.perspectivev3 13 | !default.pbxuser 14 | !default.mode1v3 15 | !default.mode2v3 16 | !default.perspectivev3 17 | *.xccheckout 18 | *.moved-aside 19 | !Podfile.lock 20 | .idea 21 | cmake-build-debug 22 | 23 | # OS junk 24 | .DS_Store 25 | .DS_Store? 26 | .Trashes 27 | .swp 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | script: xcodebuild clean build 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(Clutch) 4 | 5 | set(PRODUCT_NAME Clutch) 6 | set(EXECUTABLE_NAME Clutch) 7 | set(PRODUCT_BUNDLE_IDENTIFIER kjc.Clutch) 8 | set(MACOSX_BUNDLE_SHORT_VERSION_STRING "2.0.4") 9 | set(MACOSX_BUNDLE_BUNDLE_VERSION "1") 10 | set(DEVICE_FAMILY "1,2") 11 | set(DEPLOYMENT_TARGET 8.0 CACHE STRING "") 12 | set(CODE_SIGN_IDENTITY "iPhone Developer") 13 | 14 | if(NOT CMAKE_BUILD_TYPE) 15 | set(CMAKE_BUILD_TYPE Release) 16 | endif(NOT CMAKE_BUILD_TYPE) 17 | 18 | if(CMAKE_BUILD_TYPE STREQUAL Debug) 19 | add_definitions(-DDEBUG) 20 | endif() 21 | 22 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 23 | 24 | include(cmake/DebugUtils.cmake) 25 | include(cmake/AddFramework.cmake) 26 | add_subdirectory(Clutch) 27 | -------------------------------------------------------------------------------- /Clutch.xcodeproj/xcshareddata/xcschemes/Clutch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 85 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Clutch/ARM64Dumper.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARM64Dumper.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "Dumper.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ARM64Dumper : Dumper 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Clutch/ARM64Dumper.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARM64Dumper.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "ARM64Dumper.h" 10 | #import "ClutchPrint.h" 11 | #import "Device.h" 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | @implementation ARM64Dumper 18 | 19 | - (cpu_type_t)supportedCPUType { 20 | return CPU_TYPE_ARM64; 21 | } 22 | 23 | - (BOOL)dumpBinary { 24 | __block BOOL dumpResult; 25 | NSString *binaryDumpPath = [self.originalBinary.workingPath 26 | stringByAppendingPathComponent:self.originalBinary.binaryPath.lastPathComponent]; 27 | 28 | NSFileHandle *newFileHandle = 29 | [[NSFileHandle alloc] initWithFileDescriptor:fileno(fopen(binaryDumpPath.UTF8String, "r+"))]; 30 | 31 | NSString *swappedBinaryPath = self.originalBinary.binaryPath, *newSinf = self.originalBinary.sinfPath, 32 | *newSupp = self.originalBinary.suppPath, 33 | *newSupf = self.originalBinary.supfPath; // default values if we dont need to swap archs 34 | 35 | // check if cpusubtype matches 36 | if ((self.thinHeader.header.cpusubtype != [Device cpu_subtype]) && 37 | (self.originalBinary.hasMultipleARMSlices || 38 | (self.originalBinary.hasARM64Slice && ([Device cpu_type] == CPU_TYPE_ARM64)))) { 39 | 40 | NSString *suffix = [NSString stringWithFormat:@"_%@", [Dumper readableArchFromHeader:self.thinHeader]]; 41 | 42 | swappedBinaryPath = [self.originalBinary.binaryPath stringByAppendingString:suffix]; 43 | newSinf = [self.originalBinary.sinfPath.stringByDeletingPathExtension 44 | stringByAppendingString:[suffix stringByAppendingPathExtension:self.originalBinary.sinfPath.pathExtension]]; 45 | newSupp = [self.originalBinary.suppPath.stringByDeletingPathExtension 46 | stringByAppendingString:[suffix stringByAppendingPathExtension:self.originalBinary.suppPath.pathExtension]]; 47 | 48 | [self swapArch]; 49 | } 50 | 51 | // actual dumping 52 | 53 | [newFileHandle seekToFileOffset:self.thinHeader.offset + self.thinHeader.size]; 54 | 55 | struct linkedit_data_command ldid; // LC_CODE_SIGNATURE load header (for resign) 56 | struct encryption_info_command_64 crypt; // LC_ENCRYPTION_INFO load header (for crypt*) 57 | struct segment_command_64 __text; // __TEXT segment 58 | 59 | struct super_blob *codesignblob; // codesign blob pointer 60 | struct code_directory directory; // codesign directory index 61 | 62 | directory.nCodeSlots = 0; 63 | BOOL foundCrypt = NO, foundSignature = NO, foundStartText = NO; 64 | crypt.cryptid = crypt.cryptoff = crypt.cryptsize = 0; 65 | 66 | uint64_t __text_start = 0; 67 | 68 | KJDebug( 69 | @"64bit dumping: arch %@ offset %u", [Dumper readableArchFromHeader:self.thinHeader], self.thinHeader.offset); 70 | 71 | for (unsigned int i = 0; i < self.thinHeader.header.ncmds; i++) { 72 | 73 | uint32_t cmd = [newFileHandle unsignedInt32Atoffset:newFileHandle.offsetInFile]; 74 | uint32_t size = [newFileHandle unsignedInt32Atoffset:newFileHandle.offsetInFile + sizeof(uint32_t)]; 75 | 76 | switch (cmd) { 77 | case LC_CODE_SIGNATURE: { 78 | [newFileHandle getBytes:&ldid 79 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), 80 | sizeof(struct linkedit_data_command))]; 81 | foundSignature = YES; 82 | 83 | KJDebug(@"FOUND CODE SIGNATURE: dataoff %u | datasize %u", ldid.dataoff, ldid.datasize); 84 | 85 | break; 86 | } 87 | case LC_ENCRYPTION_INFO_64: { 88 | [newFileHandle getBytes:&crypt 89 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), 90 | sizeof(struct encryption_info_command_64))]; 91 | foundCrypt = YES; 92 | 93 | KJDebug(@"FOUND ENCRYPTION INFO: cryptoff %u | cryptsize %u | cryptid %u", 94 | crypt.cryptoff, 95 | crypt.cryptsize, 96 | crypt.cryptid); 97 | 98 | break; 99 | } 100 | case LC_SEGMENT_64: { 101 | [newFileHandle 102 | getBytes:&__text 103 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), sizeof(struct segment_command_64))]; 104 | 105 | if (strncmp(__text.segname, "__TEXT", 6) == 0) { 106 | foundStartText = YES; 107 | KJDebug(@"FOUND %s SEGMENT", __text.segname); 108 | __text_start = __text.vmaddr; 109 | } 110 | break; 111 | } 112 | } 113 | 114 | [newFileHandle seekToFileOffset:newFileHandle.offsetInFile + size]; 115 | 116 | if (foundCrypt && foundSignature && foundStartText) 117 | break; 118 | } 119 | 120 | // we need to have all of these 121 | if (!foundCrypt || !foundSignature || !foundStartText) { 122 | KJDebug(@"dumping binary: some load commands were not found %@ %@ %@", 123 | foundCrypt ? @"YES" : @"NO", 124 | foundSignature ? @"YES" : @"NO", 125 | foundStartText ? @"YES" : @"NO"); 126 | return NO; 127 | } 128 | 129 | KJDebug(@"found all required load commands for %@ %@", 130 | self.originalBinary, 131 | [Dumper readableArchFromHeader:self.thinHeader]); 132 | 133 | pid_t pid; // store the process ID of the fork 134 | mach_port_t port; // mach port used for moving virtual memory 135 | kern_return_t err; // any kernel return codes 136 | NSUInteger begin = 0; 137 | 138 | pid = [self posix_spawn:swappedBinaryPath disableASLR:self.shouldDisableASLR]; 139 | 140 | if ((err = task_for_pid(mach_task_self(), pid, &port) != KERN_SUCCESS)) { 141 | KJPrint(@"Could not obtain mach port, either the process is dead (codesign " 142 | @"error?) or entitlements were not properly signed!"); 143 | goto gotofail; 144 | } 145 | 146 | codesignblob = malloc(ldid.datasize); 147 | 148 | // seek to ldid offset 149 | 150 | [newFileHandle seekToFileOffset:self.thinHeader.offset + ldid.dataoff]; 151 | [newFileHandle getBytes:codesignblob inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), ldid.datasize)]; 152 | 153 | uint32_t countBlobs = CFSwapInt32(codesignblob->count); // how many indexes? 154 | 155 | for (uint32_t index = 0; index < countBlobs; index++) { // is this the code directory? 156 | if (CFSwapInt32(codesignblob->index[index].type) == CSSLOT_CODEDIRECTORY) { 157 | // we'll find the hash metadata in here 158 | KJDebug(@"%u %u %u", self.thinHeader.offset, ldid.dataoff, codesignblob->index[index].offset); 159 | begin = self.thinHeader.offset + ldid.dataoff + 160 | CFSwapInt32(codesignblob->index[index].offset); // store the top of the codesign directory blob 161 | [newFileHandle 162 | getBytes:&directory 163 | inRange:NSMakeRange(begin, sizeof(struct code_directory))]; // read the blob from its beginning 164 | KJDebug(@"Found CSSLOT_CODEDIRECTORY"); 165 | break; // break (we don't need anything from this the superblob anymore) 166 | } 167 | } 168 | 169 | free(codesignblob); 170 | 171 | uint32_t pages = CFSwapInt32(directory.nCodeSlots); // get the amount of codeslots 172 | 173 | KJDebug(@"Codesign Pages %u", pages); 174 | 175 | if (pages == 0) { 176 | KJPrint(@"pages == 0"); 177 | goto gotofail; 178 | } 179 | 180 | [newFileHandle seekToFileOffset:self.thinHeader.offset]; 181 | 182 | if ((self.thinHeader.header.flags & MH_PIE) && !self.shouldDisableASLR) { 183 | NSError *error = nil; 184 | mach_vm_address_t main_address = [ASLRDisabler slideForPID:pid error:&error]; 185 | if (error) { 186 | KJPrint(@"Failed to find address of header!"); 187 | goto gotofail; 188 | } 189 | 190 | KJDebug(@"ASLR slide: 0x%llx", main_address); 191 | __text_start = main_address; 192 | } 193 | 194 | { 195 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); 196 | 197 | dispatch_sync(queue, ^{ 198 | dumpResult = [self _dumpToFileHandle:newFileHandle 199 | withDumpSize:(crypt.cryptsize + crypt.cryptoff) 200 | pages:pages 201 | fromPort:port 202 | pid:pid 203 | aslrSlide:__text_start 204 | codeSignature_hashOffset:CFSwapInt32(directory.hashOffset) 205 | codesign_begin:(uint32_t)begin]; 206 | }); 207 | } 208 | KJDebug(@"done dumping"); 209 | 210 | // done dumping, let's wait for pid 211 | 212 | _kill(pid); 213 | [newFileHandle closeFile]; 214 | if (![swappedBinaryPath isEqualToString:self.originalBinary.binaryPath]) 215 | [[NSFileManager defaultManager] removeItemAtPath:swappedBinaryPath error:nil]; 216 | if (![newSinf isEqualToString:self.originalBinary.sinfPath]) 217 | [[NSFileManager defaultManager] removeItemAtPath:newSinf error:nil]; 218 | if (![newSupp isEqualToString:self.originalBinary.suppPath]) 219 | [[NSFileManager defaultManager] removeItemAtPath:newSupp error:nil]; 220 | if (![newSupf isEqualToString:self.originalBinary.supfPath]) 221 | [[NSFileManager defaultManager] removeItemAtPath:newSupf error:nil]; 222 | 223 | return dumpResult; 224 | 225 | gotofail: 226 | 227 | _kill(pid); 228 | [newFileHandle closeFile]; 229 | if (![swappedBinaryPath isEqualToString:self.originalBinary.binaryPath]) 230 | [[NSFileManager defaultManager] removeItemAtPath:swappedBinaryPath error:nil]; 231 | if (![newSinf isEqualToString:self.originalBinary.sinfPath]) 232 | [[NSFileManager defaultManager] removeItemAtPath:newSinf error:nil]; 233 | if (![newSupp isEqualToString:self.originalBinary.suppPath]) 234 | [[NSFileManager defaultManager] removeItemAtPath:newSupp error:nil]; 235 | if (![newSupf isEqualToString:self.originalBinary.supfPath]) 236 | [[NSFileManager defaultManager] removeItemAtPath:newSupf error:nil]; 237 | 238 | return NO; 239 | } 240 | 241 | @end 242 | -------------------------------------------------------------------------------- /Clutch/ARMDumper.h: -------------------------------------------------------------------------------- 1 | // 2 | // ARMDumper.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "Dumper.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ARMDumper : Dumper 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Clutch/ARMDumper.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARMDumper.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "ARMDumper.h" 10 | #import "ClutchPrint.h" 11 | #import "Device.h" 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | 19 | @implementation ARMDumper 20 | 21 | - (cpu_type_t)supportedCPUType { 22 | return CPU_TYPE_ARM; 23 | } 24 | 25 | - (BOOL)dumpBinary { 26 | __block BOOL dumpResult; 27 | NSString *binaryDumpPath = [self.originalBinary.workingPath 28 | stringByAppendingPathComponent:self.originalBinary.binaryPath.lastPathComponent]; 29 | 30 | NSFileHandle *newFileHandle = 31 | [[NSFileHandle alloc] initWithFileDescriptor:fileno(fopen(binaryDumpPath.UTF8String, "r+"))]; 32 | 33 | NSString *swappedBinaryPath = self.originalBinary.binaryPath, *newSinf = self.originalBinary.sinfPath, 34 | *newSupp = self.originalBinary.suppPath; // default values if we dont need to swap archs 35 | 36 | // check if cpusubtype matches 37 | if ((self.thinHeader.header.cpusubtype != [Device cpu_subtype]) && 38 | (self.originalBinary.hasMultipleARMSlices || 39 | (self.originalBinary.hasARM64Slice && ([Device cpu_type] == CPU_TYPE_ARM64)))) { 40 | 41 | NSString *suffix = [NSString stringWithFormat:@"_%@", [Dumper readableArchFromHeader:self.thinHeader]]; 42 | 43 | swappedBinaryPath = [self.originalBinary.binaryPath stringByAppendingString:suffix]; 44 | newSinf = [self.originalBinary.sinfPath.stringByDeletingPathExtension 45 | stringByAppendingString:[suffix stringByAppendingPathExtension:self.originalBinary.sinfPath.pathExtension]]; 46 | newSupp = [self.originalBinary.suppPath.stringByDeletingPathExtension 47 | stringByAppendingString:[suffix stringByAppendingPathExtension:self.originalBinary.suppPath.pathExtension]]; 48 | 49 | [self swapArch]; 50 | } 51 | 52 | // actual dumping 53 | 54 | [newFileHandle seekToFileOffset:self.thinHeader.offset + self.thinHeader.size]; 55 | 56 | struct linkedit_data_command ldid; // LC_CODE_SIGNATURE load header (for resign) 57 | struct encryption_info_command crypt; // LC_ENCRYPTION_INFO load header (for crypt*) 58 | struct segment_command __text; // __TEXT segment 59 | crypt.cryptsize = crypt.cryptoff = crypt.cryptid = 0; 60 | 61 | struct super_blob *codesignblob; // codesign blob pointer 62 | struct code_directory directory; // codesign directory index 63 | directory.nCodeSlots = 0; 64 | 65 | BOOL foundCrypt = NO, foundSignature = NO, foundStartText = NO; 66 | 67 | uint64_t __text_start = 0; 68 | 69 | KJDebug( 70 | @"32bit Dumping: arch %@ offset %u", [Dumper readableArchFromHeader:self.thinHeader], self.thinHeader.offset); 71 | 72 | for (unsigned int i = 0; i < self.thinHeader.header.ncmds; i++) { 73 | 74 | uint32_t cmd = [newFileHandle unsignedInt32Atoffset:newFileHandle.offsetInFile]; 75 | uint32_t size = [newFileHandle unsignedInt32Atoffset:newFileHandle.offsetInFile + sizeof(uint32_t)]; 76 | 77 | switch (cmd) { 78 | case LC_CODE_SIGNATURE: { 79 | [newFileHandle getBytes:&ldid 80 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), 81 | sizeof(struct linkedit_data_command))]; 82 | foundSignature = YES; 83 | 84 | KJDebug(@"FOUND CODE SIGNATURE: dataoff %u | datasize %u", ldid.dataoff, ldid.datasize); 85 | 86 | break; 87 | } 88 | case LC_ENCRYPTION_INFO: { 89 | [newFileHandle getBytes:&crypt 90 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), 91 | sizeof(struct encryption_info_command))]; 92 | foundCrypt = YES; 93 | 94 | KJDebug(@"FOUND ENCRYPTION INFO: cryptoff %u | cryptsize %u | cryptid %u", 95 | crypt.cryptoff, 96 | crypt.cryptsize, 97 | crypt.cryptid); 98 | 99 | break; 100 | } 101 | case LC_SEGMENT: { 102 | [newFileHandle 103 | getBytes:&__text 104 | inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), sizeof(struct segment_command))]; 105 | 106 | if (strncmp(__text.segname, "__TEXT", 6) == 0) { 107 | foundStartText = YES; 108 | KJDebug(@"FOUND %s SEGMENT", __text.segname); 109 | __text_start = __text.vmaddr; 110 | } 111 | break; 112 | } 113 | } 114 | 115 | [newFileHandle seekToFileOffset:newFileHandle.offsetInFile + size]; 116 | 117 | if (foundCrypt && foundSignature && foundStartText) 118 | break; 119 | } 120 | 121 | KJDebug(@"binary path %@", swappedBinaryPath); 122 | 123 | // we need to have all of these 124 | if (!foundCrypt || !foundSignature || !foundStartText) { 125 | KJPrint(@"dumping binary: some load commands were not found %@ %@ %@", 126 | foundCrypt ? @"YES" : @"NO", 127 | foundSignature ? @"YES" : @"NO", 128 | foundStartText ? @"YES" : @"NO"); 129 | return NO; 130 | } 131 | 132 | KJDebug(@"found all required load commands for %@ %@", 133 | self.originalBinary, 134 | [Dumper readableArchFromHeader:self.thinHeader]); 135 | 136 | pid_t pid; // store the process ID of the fork 137 | mach_port_t port; // mach port used for moving virtual memory 138 | kern_return_t err; // any kernel return codes 139 | NSUInteger begin = 0; 140 | 141 | pid = [self posix_spawn:swappedBinaryPath disableASLR:self.shouldDisableASLR]; 142 | 143 | if ((err = task_for_pid(mach_task_self(), pid, &port) != KERN_SUCCESS)) { 144 | KJPrint(@"Could not obtain mach port, either the process is dead (codesign " 145 | @"error?) or entitlements were not properly signed!?"); 146 | goto gotofail; 147 | } 148 | 149 | [newFileHandle seekToFileOffset:self.thinHeader.offset + ldid.dataoff]; 150 | 151 | codesignblob = malloc(ldid.datasize); 152 | 153 | // seek to ldid offset 154 | 155 | [newFileHandle seekToFileOffset:self.thinHeader.offset + ldid.dataoff]; 156 | [newFileHandle getBytes:codesignblob inRange:NSMakeRange((NSUInteger)(newFileHandle.offsetInFile), ldid.datasize)]; 157 | 158 | uint32_t countBlobs = CFSwapInt32(codesignblob->count); // how many indexes? 159 | 160 | for (uint32_t index = 0; index < countBlobs; index++) { // is this the code directory? 161 | if (CFSwapInt32(codesignblob->index[index].type) == CSSLOT_CODEDIRECTORY) { 162 | // we'll find the hash metadata in here 163 | KJDebug(@"%u %u %u", self.thinHeader.offset, ldid.dataoff, codesignblob->index[index].offset); 164 | begin = self.thinHeader.offset + ldid.dataoff + 165 | CFSwapInt32(codesignblob->index[index].offset); // store the top of the codesign directory blob 166 | [newFileHandle 167 | getBytes:&directory 168 | inRange:NSMakeRange(begin, sizeof(struct code_directory))]; // read the blob from its beginning 169 | KJDebug(@"Found CSSLOT_CODEDIRECTORY"); 170 | break; // break (we don't need anything from this the superblob anymore) 171 | } 172 | } 173 | 174 | free(codesignblob); 175 | 176 | uint32_t pages = CFSwapInt32(directory.nCodeSlots); // get the amount of codeslots 177 | 178 | if (pages == 0) { 179 | KJPrint(@"pages == 0"); 180 | goto gotofail; 181 | } 182 | 183 | [newFileHandle seekToFileOffset:self.thinHeader.offset]; 184 | 185 | if ((self.thinHeader.header.flags & MH_PIE) && !self.shouldDisableASLR) { 186 | NSError *error = nil; 187 | mach_vm_address_t main_address = [ASLRDisabler slideForPID:pid error:&error]; 188 | if (error) { 189 | KJPrint(@"Failed to find address of header!"); 190 | goto gotofail; 191 | } 192 | 193 | KJDebug(@"ASLR slide: 0x%llx", main_address); 194 | __text_start = main_address; 195 | } 196 | 197 | { 198 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); 199 | 200 | dispatch_sync(queue, ^{ 201 | dumpResult = [self _dumpToFileHandle:newFileHandle 202 | withDumpSize:(crypt.cryptsize + crypt.cryptoff) 203 | pages:pages 204 | fromPort:port 205 | pid:pid 206 | aslrSlide:__text_start 207 | codeSignature_hashOffset:CFSwapInt32(directory.hashOffset) 208 | codesign_begin:(uint32_t)begin]; 209 | }); 210 | } 211 | 212 | KJDebug(@"done dumping"); 213 | if (![swappedBinaryPath isEqualToString:self.originalBinary.binaryPath]) 214 | [[NSFileManager defaultManager] removeItemAtPath:swappedBinaryPath error:nil]; 215 | if (![newSinf isEqualToString:self.originalBinary.sinfPath]) 216 | [[NSFileManager defaultManager] removeItemAtPath:newSinf error:nil]; 217 | if (![newSupp isEqualToString:self.originalBinary.suppPath]) 218 | [[NSFileManager defaultManager] removeItemAtPath:newSupp error:nil]; 219 | [newFileHandle closeFile]; 220 | _kill(pid); 221 | 222 | return dumpResult; 223 | 224 | gotofail: 225 | 226 | _kill(pid); 227 | [newFileHandle closeFile]; 228 | if (![swappedBinaryPath isEqualToString:self.originalBinary.binaryPath]) 229 | [[NSFileManager defaultManager] removeItemAtPath:swappedBinaryPath error:nil]; 230 | if (![newSinf isEqualToString:self.originalBinary.sinfPath]) 231 | [[NSFileManager defaultManager] removeItemAtPath:newSinf error:nil]; 232 | if (![newSupp isEqualToString:self.originalBinary.suppPath]) 233 | [[NSFileManager defaultManager] removeItemAtPath:newSupp error:nil]; 234 | 235 | return NO; 236 | } 237 | 238 | @end 239 | -------------------------------------------------------------------------------- /Clutch/ASLRDisabler.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASLR.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 14.02.15. 6 | // 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ASLRDisabler : NSObject 14 | 15 | + (mach_vm_address_t)slideForPID:(pid_t)pid error:(NSError *__autoreleasing *)error; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Clutch/ASLRDisabler.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASLR.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 14.02.15. 6 | // 7 | // 8 | 9 | #import "ASLRDisabler.h" 10 | #import "ClutchPrint.h" 11 | #import "mach_vm.h" 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | 18 | @import MachO.loader; 19 | 20 | @implementation ASLRDisabler 21 | 22 | + (mach_vm_address_t)slideForPID:(pid_t)pid error:(NSError *__autoreleasing *)error { 23 | vm_map_t targetTask = 0; 24 | kern_return_t kr = 0; 25 | if (task_for_pid(mach_task_self(), pid, &targetTask)) { 26 | KJPrint(@"Can't execute task_for_pid! Do you have the right permissions/entitlements?"); 27 | NSDictionary *userInfo = @{ 28 | NSLocalizedDescriptionKey : @"Failed to execute task_for_pid", 29 | }; 30 | if (error) { 31 | *error = [NSError errorWithDomain:NSMachErrorDomain code:-1 userInfo:userInfo]; 32 | } 33 | return 0; 34 | } 35 | 36 | vm_address_t iter = 0; 37 | while (1) { 38 | struct mach_header mh = {0}; 39 | vm_address_t addr = iter; 40 | vm_size_t lsize = 0; 41 | uint32_t depth; 42 | mach_vm_size_t bytes_read = 0; 43 | struct vm_region_submap_info_64 info; 44 | mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; 45 | 46 | if (vm_region_recurse_64(targetTask, &addr, &lsize, &depth, (vm_region_info_t)&info, &count)) { 47 | break; 48 | } 49 | 50 | kr = mach_vm_read_overwrite(targetTask, 51 | (mach_vm_address_t)addr, 52 | (mach_vm_size_t)sizeof(struct mach_header), 53 | (mach_vm_address_t)&mh, 54 | &bytes_read); 55 | 56 | if (kr == KERN_SUCCESS && bytes_read == sizeof(struct mach_header)) { 57 | /* only one image with MH_EXECUTE filetype */ 58 | if ((mh.magic == MH_MAGIC || mh.magic == MH_MAGIC_64) && mh.filetype == MH_EXECUTE) { 59 | KJDebug(@"Found main binary mach-o image @ %p!", (void *)addr); 60 | return addr; 61 | } 62 | } 63 | 64 | iter = addr + lsize; 65 | } 66 | 67 | NSDictionary *userInfo = @{ 68 | NSLocalizedDescriptionKey : @"Should not reach here", 69 | }; 70 | if (error) { 71 | *error = [NSError errorWithDomain:NSMachErrorDomain code:-1 userInfo:userInfo]; 72 | } 73 | return 0; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Clutch/Application.h: -------------------------------------------------------------------------------- 1 | // 2 | // Application.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 09.02.2015. 6 | // 7 | // 8 | 9 | #import "ClutchBundle.h" 10 | #import "Extension.h" 11 | #import "Framework.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface Application : ClutchBundle 16 | 17 | @property (nonatomic, readonly) BOOL hasAppleWatchApp; // YES if contains watchOS 2 compatible application 18 | @property (nonatomic, readonly) 19 | BOOL isAppleWatchApp; // only for Apple Watch apps that support watchOS 2 or newer (armv7k) 20 | 21 | @property (nonatomic, retain, readonly) NSArray *extensions; 22 | @property (nonatomic, retain, readonly) NSArray *frameworks; 23 | @property (nonatomic, retain, readonly) NSArray *watchOSApps; 24 | 25 | - (BOOL)dumpToDirectoryURL:(nullable NSURL *)directoryURL onlyBinaries:(BOOL)yrn; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /Clutch/Binary.h: -------------------------------------------------------------------------------- 1 | // 2 | // Binary.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "BundleDumpOperation.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class ClutchBundle; 14 | 15 | @interface Binary : NSObject 16 | 17 | @property (nonatomic, readonly) BOOL hasRestrictedSegment; 18 | @property (nonatomic, readonly) BundleDumpOperation *dumpOperation; 19 | @property (nonatomic, readonly) NSString *workingPath; 20 | @property (nonatomic, readonly) NSString *binaryPath; 21 | @property (nonatomic, readonly) NSString *sinfPath; 22 | @property (nonatomic, readonly) NSString *supfPath; 23 | @property (nonatomic, readonly) NSString *suppPath; 24 | @property (nonatomic, readonly) NSString *frameworksPath; 25 | @property (nonatomic, readonly) BOOL isFAT; 26 | @property (nonatomic, readonly) BOOL hasARMSlice; 27 | @property (nonatomic, readonly) BOOL hasARM64Slice; 28 | @property (nonatomic, readonly) BOOL hasMultipleARMSlices; 29 | @property (nonatomic, readonly) BOOL hasMultipleARM64Slices; 30 | 31 | - (nullable instancetype)initWithBundle:(nullable ClutchBundle *)bundle NS_DESIGNATED_INITIALIZER; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /Clutch/Binary.m: -------------------------------------------------------------------------------- 1 | // 2 | // Binary.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "Binary.h" 10 | #import "ClutchBundle.h" 11 | 12 | #import "ClutchPrint.h" 13 | #import "NSFileHandle+Private.h" 14 | #import "optool.h" 15 | #include 16 | 17 | @interface Binary () { 18 | ClutchBundle *_bundle; 19 | BOOL _isFAT; 20 | BOOL _m32; 21 | BOOL _m64; 22 | } 23 | @end 24 | 25 | @implementation Binary 26 | 27 | - (NSString *)workingPath { 28 | return [_bundle.workingPath stringByAppendingPathComponent:_bundle.bundleIdentifier]; 29 | } 30 | 31 | - (nullable instancetype)init { 32 | return [self initWithBundle:nil]; 33 | } 34 | 35 | - (nullable instancetype)initWithBundle:(nullable ClutchBundle *)path { 36 | if (!path) { 37 | return nil; 38 | } 39 | 40 | if (self = [super init]) { 41 | _bundle = path; 42 | 43 | KJDebug(@"######## bundle URL %@", _bundle.bundleContainerURL); 44 | if ([(_bundle.bundleContainerURL).path hasSuffix:@"Frameworks"]) { 45 | _frameworksPath = (_bundle.bundleContainerURL).path; 46 | } 47 | 48 | _sinfPath = [_bundle pathForResource:_bundle.executablePath.lastPathComponent 49 | ofType:@"sinf" 50 | inDirectory:@"SC_Info"]; 51 | _supfPath = [_bundle pathForResource:_bundle.executablePath.lastPathComponent 52 | ofType:@"supf" 53 | inDirectory:@"SC_Info"]; 54 | _suppPath = [_bundle pathForResource:_bundle.executablePath.lastPathComponent 55 | ofType:@"supp" 56 | inDirectory:@"SC_Info"]; 57 | 58 | _dumpOperation = [[BundleDumpOperation alloc] initWithBundle:_bundle]; 59 | 60 | NSFileHandle *tmpHandle = 61 | [[NSFileHandle alloc] initWithFileDescriptor:fileno(fopen(_bundle.executablePath.UTF8String, "r+")) 62 | closeOnDealloc:YES]; 63 | 64 | NSData *headersData = tmpHandle.availableData; 65 | 66 | thin_header headers[4]; 67 | uint32_t numHeaders = 0; 68 | 69 | headersFromBinary(headers, headersData, &numHeaders); 70 | 71 | int m32 = 0, m64 = 0; 72 | for (unsigned int i = 0; i < numHeaders; i++) { 73 | thin_header macho = headers[i]; 74 | 75 | switch (macho.header.cputype) { 76 | case CPU_TYPE_ARM: 77 | m32++; 78 | break; 79 | case CPU_TYPE_ARM64: 80 | m64++; 81 | break; 82 | } 83 | } 84 | 85 | _m32 = m32 > 1; 86 | _m64 = m64 > 1; 87 | _isFAT = numHeaders > 1; 88 | 89 | _hasRestrictedSegment = NO; 90 | 91 | struct thin_header macho = headers[0]; 92 | 93 | unsigned long long size = [tmpHandle seekToEndOfFile]; 94 | 95 | [tmpHandle seekToFileOffset:macho.offset + macho.size]; 96 | 97 | for (unsigned int i = 0; i < macho.header.ncmds; i++) { 98 | if (tmpHandle.offsetInFile >= size || 99 | tmpHandle.offsetInFile > macho.header.sizeofcmds + macho.size + macho.offset) 100 | break; 101 | 102 | uint32_t cmd = [tmpHandle unsignedInt32Atoffset:tmpHandle.offsetInFile]; 103 | uint32_t size_ = [tmpHandle unsignedInt32Atoffset:tmpHandle.offsetInFile + sizeof(uint32_t)]; 104 | 105 | struct segment_command *command; 106 | 107 | command = malloc(sizeof(struct segment_command)); 108 | 109 | [tmpHandle getBytes:command 110 | inRange:NSMakeRange((NSUInteger)(tmpHandle.offsetInFile), sizeof(struct segment_command))]; 111 | 112 | if (((cmd == LC_SEGMENT) || (cmd == LC_SEGMENT_64)) && (strcmp(command->segname, "__RESTRICT") == 0)) { 113 | _hasRestrictedSegment = YES; 114 | break; 115 | } else 116 | [tmpHandle seekToFileOffset:tmpHandle.offsetInFile + size_]; 117 | 118 | free(command); 119 | } 120 | 121 | [tmpHandle closeFile]; 122 | } 123 | 124 | return self; 125 | } 126 | 127 | - (NSString *)binaryPath { 128 | NSString *path = [_bundle.executablePath copy]; 129 | 130 | if ([path hasPrefix:@"/var/mobile"]) { 131 | path = [@"/private" stringByAppendingString:path]; 132 | } 133 | 134 | return path; 135 | } 136 | 137 | - (BOOL)isFAT { 138 | return _isFAT; 139 | } 140 | 141 | - (BOOL)hasARMSlice { 142 | return [_bundle.executableArchitectures containsObject:@CPU_TYPE_ARM]; 143 | } 144 | 145 | - (BOOL)hasARM64Slice { 146 | return [_bundle.executableArchitectures containsObject:@CPU_TYPE_ARM64]; 147 | } 148 | 149 | - (BOOL)hasMultipleARM64Slices { 150 | return _m64; 151 | } 152 | 153 | - (BOOL)hasMultipleARMSlices { 154 | return _m32; 155 | } 156 | 157 | - (NSString *)description { 158 | return [NSString stringWithFormat:@"<%@>", _bundle.executablePath.lastPathComponent]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /Clutch/BinaryDumpProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryDumpProtocol.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "NSFileHandle+Private.h" 10 | #import "optool.h" 11 | #import 12 | 13 | typedef NS_ENUM(NSUInteger, ArchCompatibility) { 14 | ArchCompatibilityCompatible, 15 | // ArchCompatibilityStrip, 16 | ArchCompatibilitySwap, 17 | ArchCompatibilityNotCompatible, 18 | }; 19 | 20 | typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data); 21 | void sha1(uint8_t *hash, uint8_t *data, size_t size); 22 | 23 | #define CSSLOT_CODEDIRECTORY 0 24 | 25 | #define PT_TRACE_ME 0 26 | 27 | struct blob_index { 28 | unsigned int type; 29 | unsigned int offset; 30 | }; 31 | 32 | struct super_blob { 33 | unsigned int magic; 34 | unsigned int length; 35 | unsigned int count; 36 | struct blob_index index[]; 37 | }; 38 | 39 | struct code_directory { 40 | unsigned int magic; 41 | unsigned int length; 42 | unsigned int version; 43 | unsigned int flags; 44 | unsigned int hashOffset; 45 | unsigned int identOffset; 46 | unsigned int nSpecialSlots; 47 | unsigned int nCodeSlots; /* number of ordinary (code) hash slots */ 48 | unsigned int codeLimit; 49 | unsigned char hashSize; 50 | unsigned char hashType; 51 | unsigned char spare1; 52 | unsigned char pageSize; 53 | unsigned int spare2; 54 | }; 55 | 56 | @protocol BinaryDumpProtocol 57 | 58 | @property (nonatomic, readonly) cpu_type_t supportedCPUType; 59 | @property (nonatomic, readonly) BOOL dumpBinary; 60 | 61 | @end 62 | 63 | @protocol FrameworkBinaryDumpProtocol 64 | 65 | @property (nonatomic, readonly) cpu_type_t supportedCPUType; 66 | @property (nonatomic, readonly) BOOL dumpBinary; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Clutch/BundleDumpOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // BundleDumpOperation.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 11.02.15. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @class ClutchBundle; 12 | 13 | @interface BundleDumpOperation : NSOperation 14 | 15 | @property (nonatomic, assign, readonly) BOOL failed; 16 | 17 | - (nullable instancetype)initWithBundle:(nullable ClutchBundle *)application NS_DESIGNATED_INITIALIZER; 18 | + (nullable instancetype)operationWithBundle:(nullable ClutchBundle *)application; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /Clutch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | set(SOURCE_COMMANDS ClutchCommands.h ClutchCommands.m) 4 | source_group("Commands" FILES ${SOURCE_COMMANDS}) 5 | list(APPEND SOURCE_FILES ${SOURCE_COMMANDS}) 6 | 7 | set(SOURCE_OPERATIONS 8 | ZipOperation.h 9 | ZipOperation.m 10 | BundleDumpOperation.h 11 | BundleDumpOperation.m 12 | FinalizeDumpOperation.h 13 | FinalizeDumpOperation.m) 14 | source_group("Operations" FILES ${SOURCE_OPERATIONS}) 15 | list(APPEND SOURCE_FILES ${SOURCE_OPERATIONS}) 16 | 17 | set(SOURCE_DUMPERS 18 | ASLRDisabler.h 19 | ASLRDisabler.m 20 | ARMDumper.h 21 | ARMDumper.m 22 | ARM64Dumper.h 23 | ARM64Dumper.m 24 | FrameworkDumper.h 25 | FrameworkDumper.m 26 | Framework64Dumper.h 27 | Framework64Dumper.m 28 | FrameworkLoader.h 29 | FrameworkLoader.m) 30 | source_group("Dumpers" FILES ${SOURCE_DUMPERS}) 31 | list(APPEND SOURCE_FILES ${SOURCE_DUMPERS}) 32 | 33 | set(SOURCE_PROTOCOL_PARENT_DUMPER BinaryDumpProtocol.h Dumper.h Dumper.m) 34 | source_group("Dumpers\\Protocol + Parent Dumper" FILES 35 | ${SOURCE_PROTOCOL_PARENT_DUMPER}) 36 | list(APPEND SOURCE_FILES ${SOURCE_PROTOCOL_PARENT_DUMPER}) 37 | 38 | set(SOURCE_APPLICATION 39 | Application.h 40 | Application.m 41 | Extension.h 42 | Extension.m 43 | Framework.h 44 | Framework.m) 45 | source_group("Application" FILES ${SOURCE_APPLICATION}) 46 | list(APPEND SOURCE_FILES ${SOURCE_APPLICATION}) 47 | 48 | set(SOURCE_CLUTCH_BUNDLE Binary.h Binary.m ClutchBundle.h ClutchBundle.m) 49 | source_group("Application\\ClutchBundle" FILES ${SOURCE_CLUTCH_BUNDLE}) 50 | list(APPEND SOURCE_FILES ${SOURCE_CLUTCH_BUNDLE}) 51 | 52 | set(SOURCE_MAIN 53 | KJApplicationManager.h 54 | KJApplicationManager.m 55 | Device.h 56 | Device.m 57 | main.m 58 | ClutchPrint.h 59 | ClutchPrint.m) 60 | source_group("" FILES ${SOURCE_MAIN}) 61 | list(APPEND SOURCE_FILES ${SOURCE_MAIN}) 62 | 63 | set(PRECOMPILED_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/Clutch-Prefix.pch) 64 | set(SOURCE_SUPPORTING_FILES 65 | ../LSApplicationProxy.h 66 | ${PRECOMPILED_HEADER} 67 | SCInfoBuilder.h 68 | SCInfoBuilder.m 69 | sha1.c 70 | sha1.h 71 | Clutch.entitlements 72 | mach_vm.h 73 | NSTask.h 74 | scinfo.h 75 | scinfo.m 76 | LSApplicationWorkspace.h 77 | FBApplicationInfo.h 78 | Info.plist 79 | NSFileHandle+Private.h 80 | NSFileHandle+Private.m 81 | NSBundle+Clutch.h 82 | NSBundle+Clutch.m) 83 | source_group("Supporting Files" FILES ${SOURCE_SUPPORTING_FILES}) 84 | list(APPEND SOURCE_FILES ${SOURCE_SUPPORTING_FILES}) 85 | 86 | set(SOURCE_OPTOOL 87 | optool.h 88 | optool-defines.h 89 | optool-headers.h 90 | optool-headers.m 91 | optool-operations.h 92 | optool-operations.m 93 | NSData+Reading.h 94 | NSData+Reading.m) 95 | source_group("Supporting Files\\optools" FILES ${SOURCE_OPTOOL}) 96 | list(APPEND SOURCE_FILES ${SOURCE_OPTOOL}) 97 | 98 | set(SOURCE_ZIP ZipArchive.h ZipArchive.m) 99 | source_group("Supporting Files\\Zip" FILES ${SOURCE_ZIP}) 100 | list(APPEND SOURCE_FILES ${SOURCE_ZIP}) 101 | 102 | set(RESOURCE_SCRIPTS move_and_sign.sh) 103 | source_group("scripts" FILES ${RESOURCE_SCRIPTS}) 104 | list(APPEND SOURCE_FILES ${RESOURCE_SCRIPTS}) 105 | 106 | configure_file(Clutch.entitlements Clutch.entitlements COPYONLY) 107 | configure_file(Info.plist.in Info.plist COPYONLY) 108 | 109 | add_subdirectory(MiniZip) 110 | 111 | add_executable(Clutch MACOSX_BUNDLE ${SOURCE_FILES} $) 112 | addframework(Clutch Foundation) 113 | addframework(Clutch UIKit) 114 | addframework(Clutch MobileCoreServices) 115 | target_include_directories(Clutch PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 116 | 117 | if(CMAKE_GENERATOR STREQUAL Xcode) 118 | set_target_properties( 119 | Clutch 120 | PROPERTIES MACOSX_BUNDLE_INFO_PLIST 121 | "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in" 122 | XCODE_ATTRIBUTE_SDKROOT 123 | iphoneos 124 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER 125 | "${PRODUCT_BUNDLE_IDENTIFIER}" 126 | XCODE_ATTRIBUTE_CODE_SIGN_STYLE 127 | "Manual" 128 | XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS 129 | YES 130 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 131 | ${DEPLOYMENT_TARGET} 132 | XCODE_ATTRIBUTE_ARCHS 133 | "$(ARCHS_STANDARD)" 134 | XCODE_ATTRIBUTE_ENABLE_BITCODE 135 | YES 136 | XCODE_ATTRIBUTE_SKIP_INSTALL 137 | YES 138 | XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES 139 | YES 140 | XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT 141 | dwarf-with-dsym 142 | XCODE_ATTRIBUTE_GCC_PREFIX_HEADER 143 | "${PRECOMPILED_HEADER}" 144 | XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER 145 | YES 146 | XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 147 | ${DEVICE_FAMILY} 148 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC 149 | YES 150 | XCODE_ATTRIBUTE_INSTALL_PATH 151 | "$(LOCAL_APPS_DIR)" 152 | XCODE_ATTRIBUTE_ENABLE_TESTABILITY 153 | YES 154 | XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN 155 | YES) 156 | else() 157 | target_compile_options(Clutch 158 | PRIVATE -include 159 | "${PRECOMPILED_HEADER}" 160 | -miphoneos-version-min=${DEPLOYMENT_TARGET} 161 | -fobjc-arc 162 | -fmodules) 163 | endif() 164 | 165 | add_custom_command( 166 | TARGET Clutch POST_BUILD 167 | COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/build" 168 | COMMAND ${CMAKE_COMMAND} -E copy $ 169 | "${CMAKE_BINARY_DIR}/build/" 170 | COMMAND codesign -fs- 171 | --entitlements "${CMAKE_CURRENT_BINARY_DIR}/Clutch.entitlements" 172 | "${CMAKE_BINARY_DIR}/build/Clutch") 173 | -------------------------------------------------------------------------------- /Clutch/Clutch-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Clutch' target in the 'Clutch' project 3 | // 4 | #include 5 | 6 | #ifdef __OBJC__ 7 | #import 8 | #endif 9 | 10 | /* Definition of `CGFLOAT_TYPE', `CGFLOAT_IS_DOUBLE', `CGFLOAT_MIN', and 11 | `CGFLOAT_MAX'. */ 12 | #if defined(__LP64__) && __LP64__ 13 | #define CGFLOAT_TYPE double 14 | #define CGFLOAT_IS_DOUBLE 1 15 | #define CGFLOAT_MIN DBL_MIN 16 | #define CGFLOAT_MAX DBL_MAX 17 | #else 18 | #define CGFLOAT_TYPE float 19 | #define CGFLOAT_IS_DOUBLE 0 20 | #define CGFLOAT_MIN FLT_MIN 21 | #define CGFLOAT_MAX FLT_MAX 22 | #endif 23 | 24 | /* Definition of the `CGFloat' type and `CGFLOAT_DEFINED'. */ 25 | typedef CGFLOAT_TYPE CGFloat; 26 | #define CGFLOAT_DEFINED 1 27 | 28 | /* Definition of `CLUTCH_VERSION'. */ 29 | #define CLUTCH_VERSION ((NSString *)(NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"])) 30 | 31 | /* Definition of `CLUTCH_UNUSED'. */ 32 | #define CLUTCH_UNUSED(x) ((void)x) 33 | 34 | /* Definition of `SYSTEM_VERSION_EQUAL_TO', `SYSTEM_VERSION_GREATER_THAN', 35 | `SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO', `SYSTEM_VERSION_LESS_THAN', 36 | and `SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO'. */ 37 | #define SYSTEM_VERSION_EQUAL_TO(_v) (floor(NSFoundationVersionNumber) == _v ? YES : NO) 38 | #define SYSTEM_VERSION_GREATER_THAN(_v) (floor(NSFoundationVersionNumber) > _v ? YES : NO) 39 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(_v) (floor(NSFoundationVersionNumber) >= _v ? YES : NO) 40 | #define SYSTEM_VERSION_LESS_THAN(_v) (floor(NSFoundationVersionNumber) < _v ? YES : NO) 41 | #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(_v) (floor(NSFoundationVersionNumber) <= _v ? YES : NO) 42 | -------------------------------------------------------------------------------- /Clutch/Clutch.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | get-task-allow 8 | 9 | task_for_pid-allow 10 | 11 | com.apple.backboardd.debugapplications 12 | 13 | com.apple.springboard.debugapplications 14 | 15 | run-unsigned-code 16 | 17 | com.apple.private.librarian.can-get-application-info 18 | 19 | com.apple.private.skip-library-validation 20 | 21 | com.apple.private.security.no-container 22 | 23 | com.apple.private.mobileinstall.allowedSPI 24 | 25 | Lookup 26 | CopyInstalledAppsForLaunchServices 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Clutch/ClutchBundle.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchBundle.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "Binary.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ClutchBundle : NSBundle 14 | 15 | @property (nonatomic, retain) ClutchBundle *parentBundle; 16 | @property (nonatomic, retain, readonly) NSString *workingPath; 17 | @property (nonatomic, retain, readonly) NSString *zipFilename; 18 | @property (nonatomic, retain, readonly) NSString *zipPrefix; 19 | @property (nonatomic, retain, readonly) NSURL *enumURL; 20 | @property (nonatomic, retain, readonly) NSURL *bundleContainerURL; 21 | @property (nonatomic, retain, readonly) Binary *executable; 22 | @property (nonatomic, retain) NSOperationQueue *dumpQueue; 23 | @property (nonatomic, retain, readonly) NSString *displayName; 24 | 25 | - (nullable instancetype)initWithBundleInfo:(NSDictionary *)info NS_DESIGNATED_INITIALIZER; 26 | - (void)dumpToDirectoryURL:(NSURL *)directoryURL; 27 | - (void)prepareForDump; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /Clutch/ClutchBundle.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchBundle.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "ClutchBundle.h" 10 | #import "ClutchPrint.h" 11 | #import "optool.h" 12 | 13 | @interface ClutchBundle () 14 | 15 | @end 16 | 17 | @implementation ClutchBundle 18 | 19 | - (nullable instancetype)initWithPath:(NSString *)path { 20 | return [self initWithBundleInfo:@{ 21 | @"BundleURL" : [NSURL fileURLWithPath:path], 22 | @"BundleContainer" : NSNull.null, 23 | @"DisplayName" : NSNull.null, 24 | }]; 25 | } 26 | 27 | - (nullable instancetype)initWithBundleInfo:(NSDictionary *)info { 28 | NSURL *url = info[@"BundleURL"]; 29 | if (!url || [NSNull isEqual:url]) { 30 | return nil; 31 | } 32 | 33 | if ((self = [super initWithPath:url.path])) { 34 | _bundleContainerURL = [info[@"BundleContainer"] copy]; 35 | if ([NSNull isEqual:_bundleContainerURL]) { 36 | return nil; 37 | } 38 | _displayName = [info[@"DisplayName"] copy]; 39 | if ([NSNull isEqual:_displayName]) { 40 | return nil; 41 | } 42 | _dumpQueue = [NSOperationQueue new]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)prepareForDump { 49 | _executable = [[Binary alloc] initWithBundle:self]; 50 | 51 | KJPrintVerbose(@"Preparing to dump %@", _executable); 52 | KJPrintVerbose(@"Path: %@", self.executable.binaryPath); 53 | } 54 | 55 | - (void)dumpToDirectoryURL:(NSURL *)directoryURL { 56 | CLUTCH_UNUSED(directoryURL); 57 | if (_dumpQueue.operationCount) 58 | [_dumpQueue cancelAllOperations]; 59 | } 60 | 61 | - (NSString *)debugDescription { 62 | return [NSString stringWithFormat:@"<%@: %p, bundleIdentifier: %@, bundleURL: %@>", 63 | NSStringFromClass([self class]), 64 | (void *)self, 65 | self.bundleIdentifier, 66 | self.bundleURL]; 67 | } 68 | 69 | - (NSString *)description { 70 | return [NSString stringWithFormat:@"<%@ bundleID: %@>", 71 | self.bundlePath.lastPathComponent.stringByDeletingPathExtension, 72 | self.bundleIdentifier]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Clutch/ClutchCommands.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchCommands.h 3 | // Clutch 4 | // 5 | // Created by dev on 10/01/2017. 6 | // 7 | // 8 | 9 | typedef NS_OPTIONS(NSInteger, ClutchCommandFlag) { 10 | ClutchCommandFlagNone = 0, 11 | ClutchCommandFlagInvisible = 1 << 0, // don't print to help 12 | ClutchCommandFlagArgumentRequired = 1 << 1, // requires args 13 | ClutchCommandFlagNoArguments = 1 << 2, // will not take args 14 | ClutchCommandFlagOptional = 1 << 3, // can be optionally added to any other command (i.e. --verbose) 15 | }; 16 | 17 | typedef NS_ENUM(NSUInteger, ClutchCommandOption) { 18 | ClutchCommandOptionNone, 19 | ClutchCommandOptionFrameworkDump, 20 | ClutchCommandOptionBinaryDump, 21 | ClutchCommandOptionDump, 22 | ClutchCommandOptionPrintInstalled, 23 | ClutchCommandOptionClean, 24 | ClutchCommandOptionVersion, 25 | ClutchCommandOptionHelp, 26 | ClutchCommandOptionNoColor, 27 | ClutchCommandOptionVerbose, 28 | ClutchCommandOptionDebug, 29 | }; 30 | 31 | @interface ClutchCommand : NSObject 32 | 33 | @property (nonatomic, assign, readonly) ClutchCommandOption option; 34 | @property (nonatomic, retain, readonly) NSString *shortOption; 35 | @property (nonatomic, retain, readonly) NSString *longOption; 36 | @property (nonatomic, retain, readonly) NSString *commandDescription; 37 | @property (nonatomic, assign, readonly) ClutchCommandFlag flag; 38 | 39 | @end 40 | 41 | @interface ClutchCommands : NSObject 42 | 43 | @property (nonatomic, retain, readonly) NSArray *allCommands; 44 | @property (nonatomic, retain, readonly) NSArray *commands; 45 | @property (nonatomic, retain, readonly) NSString *helpString; 46 | @property (nonatomic, retain, readonly) NSArray *values; 47 | 48 | - (instancetype)initWithArguments:(NSArray *)arguments; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Clutch/ClutchCommands.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchCommands.m 3 | // Clutch 4 | // 5 | // Created by dev on 10/01/2017. 6 | // 7 | // 8 | 9 | #import "ClutchCommands.h" 10 | #import "ClutchPrint.h" 11 | 12 | @interface ClutchCommand () 13 | 14 | - (instancetype)initWithCommandOption:(ClutchCommandOption)commandOption 15 | shortOption:(NSString *)shortOption 16 | longOption:(NSString *)longOption 17 | commandDescription:(NSString *)commandDescription 18 | flag:(ClutchCommandFlag)flag; 19 | + (instancetype)commandWithCommandOption:(ClutchCommandOption)commandOption 20 | shortOption:(NSString *)shortOption 21 | longOption:(NSString *)longOption 22 | commandDescription:(NSString *)commandDescription 23 | flag:(ClutchCommandFlag)flag; 24 | 25 | @end 26 | 27 | @implementation ClutchCommand 28 | 29 | - (instancetype)initWithCommandOption:(ClutchCommandOption)commandOption 30 | shortOption:(NSString *)shortOption 31 | longOption:(NSString *)longOption 32 | commandDescription:(NSString *)commandDescription 33 | flag:(ClutchCommandFlag)flag { 34 | if ((self = [super self])) { 35 | _option = commandOption; 36 | _shortOption = shortOption; 37 | _longOption = longOption; 38 | _commandDescription = commandDescription; 39 | _flag = flag; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | + (instancetype)commandWithCommandOption:(ClutchCommandOption)commandOption 46 | shortOption:(NSString *)shortOption 47 | longOption:(NSString *)longOption 48 | commandDescription:(NSString *)commandDescription 49 | flag:(ClutchCommandFlag)flag { 50 | return [[self alloc] initWithCommandOption:commandOption 51 | shortOption:shortOption 52 | longOption:longOption 53 | commandDescription:commandDescription 54 | flag:flag]; 55 | } 56 | 57 | @end 58 | 59 | @implementation ClutchCommands 60 | 61 | - (instancetype)initWithArguments:(NSArray *)arguments { 62 | if ((self = [super self])) { 63 | _allCommands = [self buildCommands]; 64 | _commands = [self parseCommandWithArguments:arguments]; 65 | _helpString = [self buildHelpString]; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | - (NSArray *)parseCommandWithArguments:(NSArray *)arguments { 72 | NSMutableArray *returnCommands = [NSMutableArray new]; 73 | NSMutableArray *returnValues = [NSMutableArray new]; 74 | BOOL commandFound = NO; 75 | 76 | for (NSString *argument in arguments) { 77 | if ([argument isEqualToString:arguments[0]]) { 78 | continue; 79 | } else if ([argument isEqualToString:@"--no-color"]) { 80 | // Optionals 81 | [returnCommands insertObject:self.allCommands[8] atIndex:0]; 82 | } else if ([argument isEqualToString:@"--verbose"]) { 83 | [returnCommands insertObject:self.allCommands[9] atIndex:0]; 84 | } else if ([argument isEqualToString:@"--debug"]) { 85 | [returnCommands insertObject:self.allCommands[10] atIndex:0]; 86 | } else if ([argument hasPrefix:@"-"]) { 87 | // is a flag 88 | for (ClutchCommand *command in self.allCommands) { 89 | if ([argument isEqualToString:command.shortOption] || [argument isEqualToString:command.longOption]) { 90 | if (!commandFound) { 91 | commandFound = YES; 92 | [returnCommands addObject:command]; 93 | break; 94 | } else { 95 | KJPrint(@"Ignoring incorrectly chained command and values: %@.", argument); 96 | // ignore 2nd command in chained commands like -b foo -d bar 97 | } 98 | } 99 | } 100 | } else { 101 | // is a value 102 | [returnValues addObject:argument]; 103 | } 104 | } 105 | 106 | if (returnCommands.count < 1) { 107 | return @[ self.allCommands[0] ]; 108 | } 109 | 110 | _values = returnValues; 111 | 112 | return returnCommands; 113 | } 114 | 115 | - (NSArray *)buildCommands { 116 | ClutchCommand *none = 117 | [ClutchCommand commandWithCommandOption:ClutchCommandOptionNone 118 | shortOption:nil 119 | longOption:nil 120 | commandDescription:@"None command" 121 | flag:ClutchCommandFlagInvisible | ClutchCommandFlagNoArguments]; 122 | ClutchCommand *framework = 123 | [ClutchCommand commandWithCommandOption:ClutchCommandOptionFrameworkDump 124 | shortOption:@"-f" 125 | longOption:@"--fmwk-dump" 126 | commandDescription:@"Only dump binary files from specified bundleID" 127 | flag:ClutchCommandFlagArgumentRequired | ClutchCommandFlagInvisible]; 128 | ClutchCommand *binary = [ClutchCommand commandWithCommandOption:ClutchCommandOptionBinaryDump 129 | shortOption:@"-b" 130 | longOption:@"--binary-dump" 131 | commandDescription:@"Only dump binary files from specified bundleID" 132 | flag:ClutchCommandFlagArgumentRequired]; 133 | ClutchCommand *dump = [ClutchCommand commandWithCommandOption:ClutchCommandOptionDump 134 | shortOption:@"-d" 135 | longOption:@"--dump" 136 | commandDescription:@"Dump specified bundleID into .ipa file" 137 | flag:ClutchCommandFlagArgumentRequired]; 138 | ClutchCommand *printInstalled = [ClutchCommand commandWithCommandOption:ClutchCommandOptionPrintInstalled 139 | shortOption:@"-i" 140 | longOption:@"--print-installed" 141 | commandDescription:@"Prints installed applications" 142 | flag:ClutchCommandFlagNoArguments]; 143 | ClutchCommand *clean = [ClutchCommand commandWithCommandOption:ClutchCommandOptionClean 144 | shortOption:nil 145 | longOption:@"--clean" 146 | commandDescription:@"Clean /var/tmp/clutch directory" 147 | flag:ClutchCommandFlagNoArguments]; 148 | ClutchCommand *version = [ClutchCommand commandWithCommandOption:ClutchCommandOptionVersion 149 | shortOption:nil 150 | longOption:@"--version" 151 | commandDescription:@"Display version and exit" 152 | flag:ClutchCommandFlagNoArguments]; 153 | ClutchCommand *help = [ClutchCommand commandWithCommandOption:ClutchCommandOptionHelp 154 | shortOption:@"-?" 155 | longOption:@"--help" 156 | commandDescription:@"Displays this help and exit" 157 | flag:ClutchCommandFlagNoArguments]; 158 | ClutchCommand *noColor = [ClutchCommand commandWithCommandOption:ClutchCommandOptionNoColor 159 | shortOption:@"-n" 160 | longOption:@"--no-color" 161 | commandDescription:@"Prints with colors disabled" 162 | flag:ClutchCommandFlagOptional]; 163 | ClutchCommand *verbose = [ClutchCommand commandWithCommandOption:ClutchCommandOptionVerbose 164 | shortOption:@"-v" 165 | longOption:@"--verbose" 166 | commandDescription:@"Print verbose messages" 167 | flag:ClutchCommandFlagOptional]; 168 | ClutchCommand *debug = 169 | [ClutchCommand commandWithCommandOption:ClutchCommandOptionDebug 170 | shortOption:nil 171 | longOption:@"--debug" 172 | commandDescription:@"Enable debug logging. Only available with a debug build." 173 | flag:ClutchCommandFlagNoArguments | ClutchCommandFlagInvisible]; 174 | 175 | return @[ none, framework, binary, dump, printInstalled, clean, version, help, noColor, verbose, debug ]; 176 | } 177 | 178 | - (ClutchCommand *)parseCommandString:(NSString *)commandString { 179 | for (ClutchCommand *command in self.commands) { 180 | if ([commandString isEqualToString:command.shortOption] || [commandString isEqualToString:command.longOption]) { 181 | return command; 182 | } 183 | } 184 | 185 | return self.commands[0]; // return ClutchCommand None 186 | } 187 | 188 | - (NSString *)buildHelpString { 189 | NSMutableString *helpString = 190 | [NSMutableString stringWithFormat:@"Usage: %@ [OPTIONS]\n", NSProcessInfo.processInfo.processName]; 191 | 192 | for (ClutchCommand *command in self.allCommands) { 193 | BOOL isInvisible = (command.flag & ClutchCommandFlagInvisible) ? YES : NO; 194 | 195 | if (!isInvisible) { 196 | [helpString appendFormat:@"%-2s %-30s%@\n", 197 | command.shortOption.UTF8String ? command.shortOption.UTF8String : " ", 198 | command.longOption.UTF8String, 199 | command.commandDescription]; 200 | } 201 | } 202 | 203 | return helpString; 204 | } 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /Clutch/ClutchPrint.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchPrint.h 3 | // Clutch 4 | // 5 | // Created by dev on 15/02/2016. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | FOUNDATION_EXTERN NSUInteger KJPrintCurrentLogLevel; 12 | typedef NS_ENUM(NSUInteger, KJPrintLogLevel) { 13 | KJPrintLogLevelNormal = 0, 14 | KJPrintLogLevelVerbose = 1, 15 | KJPrintLogLevelDebug = 2, 16 | }; 17 | 18 | NSInteger KJPrint(NSString *format, ...); 19 | NSInteger KJPrintVerbose(NSString *format, ...); 20 | #if defined(DEBUG) && DEBUG 21 | NSInteger KJDebug(NSString *format, ...); 22 | #else 23 | #define KJDebug(x...) 24 | #endif 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /Clutch/ClutchPrint.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClutchPrint.m 3 | // Clutch 4 | // 5 | // Created by dev on 15/02/2016. 6 | // 7 | // 8 | 9 | #import "ClutchPrint.h" 10 | 11 | NSUInteger KJPrintCurrentLogLevel = KJPrintLogLevelNormal; 12 | 13 | static NSInteger KJPrintv(NSString *format, va_list ap) { 14 | if (![format hasSuffix:@"\n"]) { 15 | format = [format stringByAppendingString:@"\n"]; 16 | } 17 | NSString *s = [[NSString alloc] initWithFormat:format arguments:ap]; 18 | return printf("%s", s.UTF8String); 19 | } 20 | 21 | NSInteger KJPrint(NSString *format, ...) { 22 | va_list ap; 23 | va_start(ap, format); 24 | NSInteger ret = KJPrintv(format, ap); 25 | va_end(ap); 26 | return ret; 27 | } 28 | 29 | NSInteger KJPrintVerbose(NSString *format, ...) { 30 | if (KJPrintCurrentLogLevel < KJPrintLogLevelVerbose) { 31 | return 0; 32 | } 33 | va_list ap; 34 | va_start(ap, format); 35 | NSInteger ret = KJPrintv(format, ap); 36 | va_end(ap); 37 | return ret; 38 | } 39 | 40 | #if defined(DEBUG) && DEBUG 41 | NSInteger KJDebug(NSString *format, ...) { 42 | if (KJPrintCurrentLogLevel < KJPrintLogLevelDebug) { 43 | return 0; 44 | } 45 | va_list ap; 46 | va_start(ap, format); 47 | NSInteger ret = KJPrintv(format, ap); 48 | va_end(ap); 49 | return ret; 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /Clutch/Device.h: -------------------------------------------------------------------------------- 1 | // 2 | // Device.h 3 | // Clutch 4 | // 5 | // Created by Zorro on 14/11/13. 6 | // Copyright (c) 2013 AppAddict. All rights reserved. 7 | // 8 | // Re-tailored for Clutch 9 | 10 | #import "BinaryDumpProtocol.h" 11 | #import 12 | #import 13 | #include 14 | #include 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @interface Device : NSObject 19 | 20 | + (cpu_type_t)cpu_type; 21 | + (cpu_subtype_t)cpu_subtype; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /Clutch/Device.m: -------------------------------------------------------------------------------- 1 | // 2 | // Device.m 3 | // Clutch 4 | // 5 | // Created by Zorro on 14/11/13. 6 | // Copyright (c) 2013 AppAddict. All rights reserved. 7 | // 8 | // Re-tailored for Clutch 9 | 10 | #import "Device.h" 11 | #import "NSData+Reading.h" 12 | #import 13 | #import 14 | 15 | @import MachO.loader; 16 | 17 | @implementation Device 18 | 19 | + (cpu_type_t)cpu_type { 20 | const struct mach_header *header = _dyld_get_image_header(0); 21 | cpu_type_t local_cpu_type = header->cputype; 22 | 23 | return local_cpu_type; 24 | } 25 | 26 | + (cpu_subtype_t)cpu_subtype { 27 | const struct mach_header *header = _dyld_get_image_header(0); 28 | cpu_subtype_t local_cpu_subtype = header->cpusubtype; 29 | 30 | return local_cpu_subtype; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Clutch/Dumper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Dumper.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 22.03.15. 6 | // 7 | // 8 | 9 | #import "ASLRDisabler.h" 10 | #import "Binary.h" 11 | #import "BinaryDumpProtocol.h" 12 | #import "ClutchBundle.h" 13 | #import "ClutchPrint.h" 14 | #import "mach_vm.h" 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | void *safe_trim(void *p, size_t n); 19 | void exit_with_errno(int err, const char *prefix); 20 | void _kill(pid_t pid); 21 | 22 | @interface Dumper : NSObject 23 | 24 | @property (nonatomic, readonly) BOOL isASLRProtected; 25 | @property (nonatomic, retain) NSFileHandle *originalFileHandle; 26 | @property (nonatomic, assign, readonly) BOOL shouldDisableASLR; 27 | @property (nonatomic, retain) Binary *originalBinary; 28 | @property (nonatomic, assign, readonly) thin_header thinHeader; 29 | @property (nonatomic, readonly) ArchCompatibility compatibilityMode; 30 | 31 | + (NSString *)readableArchFromHeader:(thin_header)macho; 32 | + (NSString *)readableArchFromMachHeader:(struct mach_header)header; 33 | - (pid_t)posix_spawn:(NSString *)binaryPath disableASLR:(BOOL)yrn; 34 | - (pid_t)posix_spawn:(NSString *)binaryPath disableASLR:(BOOL)yrn suspend:(BOOL)suspend; 35 | - (nullable instancetype)initWithHeader:(thin_header)macho 36 | originalBinary:(nullable Binary *)binary NS_DESIGNATED_INITIALIZER; 37 | - (void)swapArch; 38 | - (BOOL)_dumpToFileHandle:(NSFileHandle *)fileHandle 39 | withDumpSize:(uint32_t)togo 40 | pages:(uint32_t)pages 41 | fromPort:(mach_port_t)port 42 | pid:(pid_t)pid 43 | aslrSlide:(mach_vm_address_t)__text_start 44 | codeSignature_hashOffset:(uint32_t)hashOffset 45 | codesign_begin:(uint32_t)begin; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /Clutch/Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "ClutchBundle.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class Application; 14 | 15 | @interface Extension : ClutchBundle 16 | 17 | @property (readonly) BOOL isWatchKitExtension; 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /Clutch/Extension.m: -------------------------------------------------------------------------------- 1 | // 2 | // Extension.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "Extension.h" 10 | 11 | @implementation Extension 12 | 13 | - (instancetype)initWithBundleInfo:(NSDictionary *)info { 14 | if (self = [super initWithBundleInfo:info]) { 15 | } 16 | return self; 17 | } 18 | 19 | - (BOOL)isWatchKitExtension { 20 | return [self.infoDictionary[@"NSExtension"][@"NSExtensionPointIdentifier"] isEqualToString:@"com.apple.watchkit"]; 21 | } 22 | 23 | - (NSString *)zipFilename { 24 | return self.parentBundle.zipFilename; 25 | } 26 | 27 | - (NSString *)zipPrefix { 28 | return 29 | [@"Payload" stringByAppendingPathComponent:[self.bundleContainerURL.path 30 | stringByReplacingOccurrencesOfString:self.parentBundle 31 | .bundleContainerURL.path 32 | withString:@""]]; 33 | } 34 | 35 | - (NSURL *)enumURL { 36 | return self.bundleURL; 37 | } 38 | 39 | - (NSString *)workingPath { 40 | return self.parentBundle.workingPath; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Clutch/FBApplicationInfo.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FBBundleInfo : NSObject { 4 | id _proxy; 5 | NSString *_displayName; 6 | NSString *_bundleIdentifier; 7 | NSString *_bundleVersion; 8 | NSString *_bundleType; 9 | NSURL *_bundleURL; 10 | } 11 | 12 | @property (retain, nonatomic) NSURL *bundleURL; // @synthesize bundleURL=_bundleURL; 13 | @property (copy, nonatomic) NSString *bundleType; // @synthesize bundleType=_bundleType; 14 | @property (copy, nonatomic) NSString *bundleVersion; // @synthesize bundleVersion=_bundleVersion; 15 | @property (copy, nonatomic) NSString *bundleIdentifier; // @synthesize bundleIdentifier=_bundleIdentifier; 16 | @property (copy, nonatomic) NSString *displayName; // @synthesize displayName=_displayName; 17 | @property (readonly, retain, nonatomic, getter=_proxy) id proxy; // @synthesize proxy=_proxy; 18 | 19 | - (instancetype)initWithApplicationProxy:(id)arg1; 20 | 21 | @end 22 | 23 | @interface FBApplicationInfo : FBBundleInfo { 24 | NSURL *_executableURL; 25 | NSURL *_bundleContainerURL; 26 | NSURL *_dataContainerURL; 27 | NSURL *_sandboxURL; 28 | double _lastModifiedDate; 29 | NSString *_preferenceDomain; 30 | NSString *_signerIdentity; 31 | NSDictionary *_environmentVariables; 32 | NSDictionary *_entitlements; 33 | _Bool _provisioningProfileValidated; 34 | NSString *_sdkVersion; 35 | NSArray *_customMachServices; 36 | unsigned long long _type; 37 | NSArray *_requiredCapabilities; 38 | NSArray *_tags; 39 | NSArray *_deviceFamilies; 40 | _Bool _enabled; 41 | _Bool _newsstand; 42 | _Bool _restricted; 43 | _Bool _beta; 44 | NSSet *_backgroundModes; 45 | NSSet *_supportedInterfaceOrientations; 46 | _Bool _exitsOnSuspend; 47 | _Bool _requiresPersistentWiFi; 48 | float _minimumBrightnessLevel; 49 | NSArray *_externalAccessoryProtocols; 50 | long long _ratingRank; 51 | NSArray *_folderNames; 52 | NSString *_fallbackFolderName; 53 | _Bool _installing; 54 | _Bool _uninstalling; 55 | NSObject *_workQueue; 56 | } 57 | 58 | @property (nonatomic, getter=_isUninstalling, setter=_setUninstalling:) 59 | _Bool uninstalling; // @synthesize uninstalling=_uninstalling; 60 | @property (nonatomic, getter=_isInstalling, setter=_setInstalling:) 61 | _Bool installing; // @synthesize installing=_installing; 62 | @property (readonly, nonatomic) long long ratingRank; // @synthesize ratingRank=_ratingRank; 63 | @property (readonly, retain, nonatomic) 64 | NSArray *externalAccessoryProtocols; // @synthesize externalAccessoryProtocols=_externalAccessoryProtocols; 65 | @property (readonly, nonatomic) 66 | float minimumBrightnessLevel; // @synthesize minimumBrightnessLevel=_minimumBrightnessLevel; 67 | @property (readonly, nonatomic) 68 | _Bool requiresPersistentWiFi; // @synthesize requiresPersistentWiFi=_requiresPersistentWiFi; 69 | @property (readonly, nonatomic, getter=isExitsOnSuspend) 70 | _Bool exitsOnSuspend; // @synthesize exitsOnSuspend=_exitsOnSuspend; 71 | @property (readonly, nonatomic, getter=isBeta) _Bool beta; // @synthesize beta=_beta; 72 | @property (readonly, nonatomic, getter=isRestricted) _Bool restricted; // @synthesize restricted=_restricted; 73 | @property (readonly, nonatomic, getter=isNewsstand) _Bool newsstand; // @synthesize newsstand=_newsstand; 74 | @property (readonly, nonatomic, getter=isEnabled) _Bool enabled; // @synthesize enabled=_enabled; 75 | @property (readonly, retain, nonatomic) NSArray *tags; // @synthesize tags=_tags; 76 | @property (readonly, retain, nonatomic) NSArray *deviceFamilies; // @synthesize deviceFamilies=_deviceFamilies; 77 | @property (readonly, retain, nonatomic) 78 | NSArray *requiredCapabilities; // @synthesize requiredCapabilities=_requiredCapabilities; 79 | @property (readonly, nonatomic) unsigned long long type; // @synthesize type=_type; 80 | @property (readonly, retain, nonatomic) 81 | NSArray *customMachServices; // @synthesize customMachServices=_customMachServices; 82 | @property (readonly, copy, nonatomic) NSString *sdkVersion; // @synthesize sdkVersion=_sdkVersion; 83 | @property (readonly, nonatomic, getter=isProvisioningProfileValidated) 84 | _Bool provisioningProfileValidated; // @synthesize provisioningProfileValidated=_provisioningProfileValidated; 85 | @property (readonly, retain, nonatomic) NSDictionary *entitlements; // @synthesize entitlements=_entitlements; 86 | @property (readonly, retain, nonatomic) 87 | NSDictionary *environmentVariables; // @synthesize environmentVariables=_environmentVariables; 88 | @property (readonly, copy, nonatomic) NSString *signerIdentity; // @synthesize signerIdentity=_signerIdentity; 89 | @property (readonly, copy, nonatomic) NSString *preferenceDomain; // @synthesize preferenceDomain=_preferenceDomain; 90 | @property (readonly, nonatomic) double lastModifiedDate; // @synthesize lastModifiedDate=_lastModifiedDate; 91 | @property (readonly, retain, nonatomic) NSURL *sandboxURL; // @synthesize sandboxURL=_sandboxURL; 92 | @property (readonly, retain, nonatomic) NSURL *dataContainerURL; // @synthesize dataContainerURL=_dataContainerURL; 93 | @property (readonly, retain, nonatomic) 94 | NSURL *bundleContainerURL; // @synthesize bundleContainerURL=_bundleContainerURL; 95 | @property (readonly, retain, nonatomic) NSURL *executableURL; // @synthesize executableURL=_executableURL; 96 | - (id)_localizedGenreFromDictionary:(id)arg1; 97 | - (id)_localizedGenreNameForID:(long long)arg1; 98 | - (void)_cacheFolderNamesForSystemApp:(id)arg1; 99 | - (id)_configureEnvironment:(id)arg1; 100 | @property (nonatomic, readonly) long long _computeRatingRank; 101 | @property (nonatomic, readonly, strong) id _copyiTunesMetadata; 102 | - (void)_buildDefaultsFromInfoPlist:(id)arg1; 103 | - (id)_computeSupportedInterfaceOrientations:(id)arg1; 104 | - (void)_acceptApplicationSignatureIdentity; 105 | @property (nonatomic, readonly, strong) id _preferenceDomain; 106 | - (double)_lastModifiedDateForPath:(id)arg1; 107 | - (unsigned long long)_applicationType:(id)arg1; 108 | @property (nonatomic, readonly, strong) id description; 109 | - (_Bool)builtOnOrAfterSDKVersion:(id)arg1; 110 | - (void)acceptApplicationSignatureIdentity; 111 | - (_Bool)supportsInterfaceOrientation:(long long)arg1; 112 | - (_Bool)supportsBackgroundMode:(id)arg1; 113 | @property (readonly, retain, nonatomic) 114 | NSString *fallbackFolderName; // @synthesize fallbackFolderName=_fallbackFolderName; 115 | @property (readonly, retain, nonatomic) NSArray *folderNames; // @synthesize folderNames=_folderNames; 116 | @property (readonly, nonatomic) long long signatureState; // @dynamic signatureState; 117 | - (void)dealloc; 118 | - (instancetype)initWithApplicationProxy:(id)arg1; 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /Clutch/FinalizeDumpOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // FinalizeDumpOperation.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 12.02.15. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @class Application; 12 | 13 | @interface FinalizeDumpOperation : NSOperation 14 | 15 | @property (nonatomic, assign) BOOL onlyBinaries; 16 | @property (nonatomic, assign) NSUInteger expectedBinariesCount; 17 | 18 | - (nullable instancetype)initWithApplication:(nullable Application *)application NS_DESIGNATED_INITIALIZER; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /Clutch/FinalizeDumpOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // FinalizeDumpOperation.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 12.02.15. 6 | // 7 | // 8 | 9 | #import "FinalizeDumpOperation.h" 10 | #import "Application.h" 11 | #import "ClutchPrint.h" 12 | #import "ZipArchive.h" 13 | #import "ZipOperation.h" 14 | #import 15 | 16 | NSInteger diff_ms(struct timeval, struct timeval); 17 | extern struct timeval gStart; 18 | 19 | @interface FinalizeDumpOperation () { 20 | Application *_application; 21 | BOOL _executing, _finished; 22 | ZipArchive *_archive; 23 | } 24 | @end 25 | 26 | @implementation FinalizeDumpOperation 27 | 28 | - (nullable instancetype)init { 29 | return [self initWithApplication:nil]; 30 | } 31 | 32 | - (nullable instancetype)initWithApplication:(nullable Application *)application { 33 | if (!application) { 34 | return nil; 35 | } 36 | self = [super init]; 37 | if (self) { 38 | _executing = NO; 39 | _finished = NO; 40 | _application = application; 41 | } 42 | return self; 43 | } 44 | 45 | - (BOOL)isAsynchronous { 46 | return YES; 47 | } 48 | 49 | - (BOOL)isConcurrent { 50 | return YES; 51 | } 52 | 53 | - (BOOL)isExecuting { 54 | return _executing; 55 | } 56 | 57 | - (BOOL)isFinished { 58 | return _finished; 59 | } 60 | 61 | - (void)start { 62 | // Always check for cancellation before launching the task. 63 | if (self.cancelled) { 64 | // Must move the operation to the finished state if it is canceled. 65 | [self willChangeValueForKey:@"isFinished"]; 66 | _finished = YES; 67 | [self didChangeValueForKey:@"isFinished"]; 68 | return; 69 | } 70 | 71 | NSString __weak *bundleIdentifier = _application.bundleIdentifier; 72 | self.completionBlock = ^{ 73 | struct timeval end; 74 | gettimeofday(&end, NULL); 75 | NSInteger dif = diff_ms(end, gStart); 76 | CGFloat sec = ((dif + 500.0f) / 1000.0f); 77 | KJPrint(@"Finished dumping %@ in %0.1f seconds", bundleIdentifier, sec); 78 | }; 79 | 80 | // If the operation is not canceled, begin executing the task. 81 | [self willChangeValueForKey:@"isExecuting"]; 82 | [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; 83 | _executing = YES; 84 | [self didChangeValueForKey:@"isExecuting"]; 85 | } 86 | 87 | - (void)main { 88 | @try { 89 | 90 | if (_onlyBinaries) { 91 | 92 | NSDirectoryEnumerator *dirEnumerator = 93 | [NSFileManager.defaultManager enumeratorAtURL:[NSURL fileURLWithPath:_application.workingPath] 94 | includingPropertiesForKeys:@[ NSURLNameKey, NSURLIsDirectoryKey ] 95 | options:0 96 | errorHandler:^BOOL(NSURL *url, NSError *error) { 97 | CLUTCH_UNUSED(url); 98 | CLUTCH_UNUSED(error); 99 | return YES; 100 | }]; 101 | 102 | NSMutableArray *plists = [NSMutableArray new]; 103 | 104 | for (NSURL *theURL in dirEnumerator) { 105 | NSNumber *isDirectory; 106 | [theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; 107 | 108 | if ([theURL.lastPathComponent isEqualToString:@"filesToAdd.plist"]) { 109 | 110 | NSDictionary *dict = [NSDictionary dictionaryWithContentsOfURL:theURL]; 111 | 112 | if (dict) 113 | [plists addObject:theURL.path]; 114 | 115 | [[NSFileManager defaultManager] removeItemAtURL:theURL error:nil]; 116 | } 117 | } 118 | 119 | __block BOOL status = plists.count == self.expectedBinariesCount; 120 | 121 | if (status) { 122 | KJPrint(@"Finished dumping %@ to %@", _application.bundleIdentifier, _application.workingPath); 123 | } else { 124 | KJPrint(@"Failed to dump %@ :(", _application.bundleIdentifier); 125 | return; 126 | } 127 | 128 | [self completeOperation]; 129 | 130 | return; 131 | } 132 | 133 | NSString *_zipFilename = _application.zipFilename; 134 | 135 | if (_application.parentBundle) { 136 | KJDebug(@"Zipping %@", _application.bundleURL.lastPathComponent); 137 | } 138 | 139 | if (_archive == nil) { 140 | _archive = [[ZipArchive alloc] init]; 141 | [_archive CreateZipFile2:[_application.workingPath stringByAppendingPathComponent:_zipFilename] append:YES]; 142 | } 143 | 144 | NSDirectoryEnumerator *dirEnumerator = 145 | [NSFileManager.defaultManager enumeratorAtURL:[NSURL fileURLWithPath:_application.workingPath] 146 | includingPropertiesForKeys:@[ NSURLNameKey, NSURLIsDirectoryKey ] 147 | options:0 148 | errorHandler:^BOOL(NSURL *url, NSError *error) { 149 | CLUTCH_UNUSED(url); 150 | CLUTCH_UNUSED(error); 151 | return YES; 152 | }]; 153 | 154 | NSMutableArray *plists = [NSMutableArray new]; 155 | 156 | for (NSURL *theURL in dirEnumerator) { 157 | NSNumber *isDirectory; 158 | [theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; 159 | 160 | if ([theURL.lastPathComponent isEqualToString:@"filesToAdd.plist"]) { 161 | 162 | NSDictionary *dict = [NSDictionary dictionaryWithContentsOfURL:theURL]; 163 | 164 | if (dict) { 165 | for (NSString *key in dict.allKeys) { 166 | NSString *zipPath = dict[key]; 167 | [_archive addFileToZip:key newname:zipPath]; 168 | KJDebug(@"Added %@", zipPath); 169 | } 170 | 171 | [plists addObject:theURL.path]; 172 | } 173 | } 174 | } 175 | 176 | [_archive CloseZipFile2]; 177 | 178 | // cleanup 179 | 180 | #ifndef DEBUG 181 | for (NSString *path in plists) 182 | [[NSFileManager defaultManager] removeItemAtPath:path.stringByDeletingLastPathComponent error:nil]; 183 | #endif 184 | 185 | __block BOOL status = plists.count == self.expectedBinariesCount; 186 | 187 | NSString *_ipaPath = [@"/private/var/mobile/Documents/Dumped" stringByAppendingPathComponent:_zipFilename]; 188 | 189 | if (!status) { 190 | // remove .ipa if failed 191 | [[NSFileManager defaultManager] 192 | removeItemAtPath:[_application.workingPath stringByAppendingPathComponent:_zipFilename] 193 | error:nil]; 194 | } else { 195 | [[NSFileManager defaultManager] createDirectoryAtPath:@"/private/var/mobile/Documents/Dumped" 196 | withIntermediateDirectories:YES 197 | attributes:nil 198 | error:nil]; 199 | 200 | NSURL *ipaSrcURL = 201 | [NSURL fileURLWithPath:[_application.workingPath stringByAppendingPathComponent:_zipFilename]]; 202 | NSError *anError; 203 | if ([[NSFileManager defaultManager] fileExistsAtPath:_ipaPath]) { 204 | for (int i = 2; i < 999; ++i) { 205 | NSFileManager *fileMgr = [NSFileManager defaultManager]; 206 | NSString *newName = [_ipaPath.lastPathComponent.stringByDeletingPathExtension 207 | stringByAppendingFormat:@"-%i.%@", i, _ipaPath.pathExtension]; 208 | NSString *currentFile = 209 | [_ipaPath.stringByDeletingLastPathComponent stringByAppendingPathComponent:newName]; 210 | BOOL fileExists = [fileMgr fileExistsAtPath:currentFile]; 211 | if (!fileExists) { 212 | _ipaPath = currentFile; 213 | if (![[NSFileManager defaultManager] moveItemAtURL:ipaSrcURL 214 | toURL:[NSURL fileURLWithPath:currentFile] 215 | error:&anError]) { 216 | KJDebug(@"Failed to move from %@ to %@ with error %@", 217 | ipaSrcURL, 218 | [NSURL fileURLWithPath:currentFile], 219 | anError); 220 | } 221 | break; 222 | } 223 | } 224 | } else { 225 | if (![[NSFileManager defaultManager] moveItemAtURL:ipaSrcURL 226 | toURL:[NSURL fileURLWithPath:_ipaPath] 227 | error:&anError]) { 228 | KJDebug(@"Failed to move from %@ to %@ with error %@", 229 | ipaSrcURL, 230 | [NSURL fileURLWithPath:_ipaPath], 231 | anError); 232 | } 233 | } 234 | } 235 | 236 | KJPrint(@"%@: %@", status ? @"DONE" : @"FAILED", status ? _ipaPath : _application); 237 | 238 | // Do the main work of the operation here. 239 | [self completeOperation]; 240 | } @catch (...) { 241 | // Do not rethrow exceptions. 242 | } 243 | } 244 | 245 | - (void)completeOperation { 246 | [self willChangeValueForKey:@"isFinished"]; 247 | [self willChangeValueForKey:@"isExecuting"]; 248 | _executing = NO; 249 | _finished = YES; 250 | [self didChangeValueForKey:@"isExecuting"]; 251 | [self didChangeValueForKey:@"isFinished"]; 252 | } 253 | 254 | - (NSUInteger)hash { 255 | return 4201234; 256 | } 257 | 258 | @end 259 | -------------------------------------------------------------------------------- /Clutch/Framework.h: -------------------------------------------------------------------------------- 1 | // 2 | // Framework.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "ClutchBundle.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class Application; 14 | 15 | @interface Framework : ClutchBundle 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Clutch/Framework.m: -------------------------------------------------------------------------------- 1 | // 2 | // Framework.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 10.02.15. 6 | // 7 | // 8 | 9 | #import "Framework.h" 10 | #import "Device.h" 11 | 12 | @implementation Framework 13 | 14 | - (instancetype)initWithBundleInfo:(NSDictionary *)info { 15 | if (self = [super initWithBundleInfo:info]) { 16 | } 17 | return self; 18 | } 19 | 20 | - (void)prepareForDump { 21 | [super prepareForDump]; 22 | } 23 | 24 | - (NSString *)zipFilename { 25 | return self.parentBundle.zipFilename; 26 | } 27 | 28 | - (NSString *)zipPrefix { 29 | return 30 | [@"Payload" stringByAppendingPathComponent:[self.bundleContainerURL.path 31 | stringByReplacingOccurrencesOfString:self.parentBundle 32 | .bundleContainerURL.path 33 | withString:@""]]; 34 | } 35 | 36 | - (NSURL *)enumURL { 37 | return self.bundleURL; 38 | } 39 | 40 | - (NSString *)workingPath { 41 | return self.parentBundle.workingPath; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Clutch/Framework64Dumper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Framework64Dumper.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 02.04.15. 6 | // 7 | // 8 | 9 | #import "Dumper.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface Framework64Dumper : Dumper 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Clutch/FrameworkDumper.h: -------------------------------------------------------------------------------- 1 | // 2 | // FrameworkDumper.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 02.04.15. 6 | // 7 | // 8 | 9 | #import "Dumper.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FrameworkDumper : Dumper 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Clutch/FrameworkLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // FrameworkLoader.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 06.04.15. 6 | // 7 | // 8 | 9 | #import "Dumper.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface FrameworkLoader : Dumper 14 | 15 | @property (nonatomic, assign) uint32_t ncmds; 16 | @property (nonatomic, assign) uint32_t offset; 17 | @property (nonatomic, assign) uint32_t pages; 18 | @property (nonatomic, assign) uint32_t dumpSize; 19 | @property (nonatomic, assign) uint32_t hashOffset; 20 | @property (nonatomic, assign) uint32_t cryptoff; 21 | @property (nonatomic, assign) uint32_t cryptsize; 22 | @property (nonatomic, assign) uint32_t cryptlc_offset; 23 | @property (nonatomic, assign) uint32_t codesign_begin; 24 | @property (nonatomic, assign) BOOL arm64; 25 | @property (nonatomic, retain) NSString *binPath; 26 | @property (nonatomic, retain) NSString *dumpPath; 27 | @property (nonatomic, retain) NSString *bID; 28 | @property (nonatomic, readonly) cpu_type_t supportedCPUType; 29 | @property (nonatomic, readonly) BOOL dumpBinary; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /Clutch/FrameworkLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // FrameworkLoader.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 06.04.15. 6 | // 7 | // 8 | 9 | #import "FrameworkLoader.h" 10 | #import "ClutchPrint.h" 11 | #import "Device.h" 12 | #import "NSBundle+Clutch.h" 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | 22 | @import ObjectiveC.runtime; 23 | 24 | @interface FrameworkLoader () { 25 | uint32_t _dyldImageIndex; 26 | } 27 | @end 28 | 29 | @implementation FrameworkLoader 30 | 31 | - (cpu_type_t)supportedCPUType { 32 | return CPU_TYPE_ARM | CPU_TYPE_ARM64; 33 | } 34 | 35 | - (BOOL)dumpBinary { 36 | 37 | NSString *binaryDumpPath = self.dumpPath; 38 | 39 | NSString *swappedBinaryPath = self.binPath; // default values if we dont need to swap archs 40 | 41 | NSDictionary *_infoPlist = 42 | [NSDictionary dictionaryWithContentsOfFile:[self.binPath.stringByDeletingLastPathComponent 43 | stringByAppendingPathComponent:@"Info.plist"]]; 44 | 45 | [NSBundle mainBundle].clutchBID = self.bID; //_infoPlist[@"CFBundleIdentifier"]; 46 | 47 | self.originalBinary = (Binary *)[NSString stringWithFormat:@"<%@>", _infoPlist[@"CFBundleExecutable"]]; 48 | 49 | NSFileHandle *newFileHandle = 50 | [[NSFileHandle alloc] initWithFileDescriptor:fileno(fopen(binaryDumpPath.UTF8String, "r+"))]; 51 | 52 | [newFileHandle seekToFileOffset:self.offset]; 53 | 54 | void *handle = dlopen(swappedBinaryPath.UTF8String, RTLD_LAZY); 55 | 56 | if (!handle) { 57 | KJPrint(@"Failed to dlopen %@ %s", swappedBinaryPath, dlerror()); 58 | return NO; 59 | } 60 | 61 | uint32_t imageCount = _dyld_image_count(); 62 | uint32_t dyldIndex = 0; 63 | BOOL modifiedDyldIndex = NO; 64 | for (uint32_t idx = 0; idx < imageCount; idx++) { 65 | NSString *dyldPath = @(_dyld_get_image_name(idx)); 66 | if ([swappedBinaryPath.lastPathComponent isEqualToString:dyldPath.lastPathComponent]) { 67 | dyldIndex = idx; 68 | modifiedDyldIndex = YES; 69 | break; 70 | } 71 | } 72 | 73 | if (!modifiedDyldIndex) { 74 | dlclose(handle); 75 | return NO; 76 | } 77 | 78 | _dyldImageIndex = dyldIndex; 79 | 80 | intptr_t dyldPointer = _dyld_get_image_vmaddr_slide(dyldIndex); 81 | 82 | KJDebug(@"dyld offset %u", dyldPointer); 83 | 84 | BOOL dumpResult; 85 | 86 | //[self _dumpToFileHandle:newFileHandle withEncryptionInfoCommand:self.encryptionInfoCommand pages:self.pages 87 | // fromPort:mach_task_self() pid:[NSProcessInfo processInfo].processIdentifier aslrSlide:dyldPointer]; 88 | 89 | dumpResult = [self _dumpToFileHandle:newFileHandle 90 | withDumpSize:self.dumpSize 91 | pages:self.pages 92 | fromPort:mach_task_self() 93 | pid:[NSProcessInfo processInfo].processIdentifier 94 | aslrSlide:(mach_vm_address_t)dyldPointer 95 | codeSignature_hashOffset:self.hashOffset 96 | codesign_begin:self.codesign_begin]; 97 | 98 | dlclose(handle); 99 | 100 | return dumpResult; 101 | } 102 | 103 | - (BOOL)_dumpToFileHandle:(NSFileHandle *)fileHandle 104 | withDumpSize:(uint32_t)togo 105 | pages:(uint32_t)pages 106 | fromPort:(mach_port_t)port 107 | pid:(pid_t)pid 108 | aslrSlide:(mach_vm_address_t)__text_start 109 | codeSignature_hashOffset:(uint32_t)hashOffset 110 | codesign_begin:(uint32_t)begin { 111 | CLUTCH_UNUSED(port); 112 | CLUTCH_UNUSED(pid); 113 | CLUTCH_UNUSED(__text_start); 114 | 115 | KJDebug(@"Using Framework Dumper, pages %u", pages); 116 | void *checksum = malloc(pages * 20); // 160 bits for each hash (SHA1) 117 | 118 | const struct mach_header *image_header = _dyld_get_image_header(_dyldImageIndex); 119 | 120 | KJPrint(@"Dumping %@ %@", self.originalBinary, [Dumper readableArchFromMachHeader:*image_header]); 121 | 122 | uint32_t pages_d = 0; 123 | 124 | uint8_t *buf = malloc(0x1000); 125 | 126 | [fileHandle seekToFileOffset:self.offset]; 127 | 128 | while (togo > 0) { 129 | memcpy(buf, (unsigned char *)image_header + (pages_d * 0x1000), 0x1000); 130 | [fileHandle writeData:[NSData dataWithBytes:buf length:0x1000]]; 131 | // https://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html 132 | sha1((uint8_t *)checksum + (20 * pages_d), buf, 0x1000); // perform checksum on the page 133 | togo -= 0x1000; // remove a page from the togo 134 | pages_d += 1; // increase the amount of completed pages 135 | } 136 | free(buf); 137 | 138 | // nice! now let's write the new checksum data 139 | KJDebug(@"Writing new checksum"); 140 | 141 | [fileHandle seekToFileOffset:(begin + hashOffset)]; 142 | 143 | NSData *trimmed_checksum = [[NSData dataWithBytes:checksum 144 | length:pages * 20] subdataWithRange:NSMakeRange(0, 20 * pages_d)]; 145 | free(checksum); 146 | [fileHandle writeData:trimmed_checksum]; 147 | 148 | KJDebug(@"Done writing checksum"); 149 | 150 | KJDebug(@"Patching cryptid"); 151 | 152 | NSData *data; 153 | 154 | if (image_header->cputype == CPU_TYPE_ARM64) { 155 | struct encryption_info_command_64 crypt; 156 | 157 | [fileHandle getBytes:&crypt atOffset:self.cryptlc_offset length:sizeof(struct encryption_info_command_64)]; 158 | 159 | KJDebug(@"current cryptid %u", crypt.cryptid); 160 | crypt.cryptid = 0; 161 | [fileHandle seekToFileOffset:self.cryptlc_offset]; 162 | 163 | data = [NSData dataWithBytes:&crypt length:sizeof(struct encryption_info_command_64)]; 164 | 165 | } else { 166 | struct encryption_info_command crypt; 167 | [fileHandle getBytes:&crypt atOffset:self.cryptlc_offset length:sizeof(struct encryption_info_command)]; 168 | KJDebug(@"current cryptid %u", crypt.cryptid); 169 | crypt.cryptid = 0; 170 | [fileHandle seekToFileOffset:self.cryptlc_offset]; 171 | data = [NSData dataWithBytes:&crypt length:sizeof(struct encryption_info_command)]; 172 | } 173 | 174 | [fileHandle writeData:data]; 175 | return YES; 176 | } 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /Clutch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.4 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | Stub 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationPortraitUpsideDown 33 | UIInterfaceOrientationLandscapeLeft 34 | UIInterfaceOrientationLandscapeRight 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Clutch/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 2.0.4 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | Stub 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationPortraitUpsideDown 33 | UIInterfaceOrientationLandscapeLeft 34 | UIInterfaceOrientationLandscapeRight 35 | 36 | 37 | -------------------------------------------------------------------------------- /Clutch/KJApplicationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationsManager.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 09.02.15. 6 | // 7 | // 8 | 9 | #import "Application.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface KJApplicationManager : NSObject 14 | 15 | - (instancetype)init; 16 | 17 | @property (nonatomic, readonly, copy) NSDictionary *installedApps; 18 | @property (nonatomic, readonly, copy) NSDictionary *cachedApplications; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /Clutch/KJApplicationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationsManager.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 09.02.15. 6 | // 7 | // 8 | 9 | #define applistCachePath @"applist-cache.plist" 10 | #define dumpedAppPath @"/etc/dumped.clutch" 11 | 12 | #import "KJApplicationManager.h" 13 | #import "FBApplicationInfo.h" 14 | #import "LSApplicationProxy.h" 15 | #import "LSApplicationWorkspace.h" 16 | #import 17 | 18 | typedef NSDictionary *(*MobileInstallationLookup)(NSDictionary *options); 19 | 20 | @interface KJApplicationManager () 21 | @property (nonatomic, retain) NSMutableArray *cachedApps; 22 | @end 23 | 24 | @implementation KJApplicationManager 25 | 26 | - (instancetype)init { 27 | if ((self = [super init])) { 28 | if ([[NSFileManager defaultManager] fileExistsAtPath:applistCachePath]) { 29 | _cachedApps = [[NSMutableArray alloc] initWithContentsOfFile:applistCachePath]; 30 | } else { 31 | _cachedApps = [NSMutableArray new]; 32 | } 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (NSDictionary *)listApplicationsForiOS7AndLower { 39 | MobileInstallationLookup mobileInstallationLookup; 40 | void *MIHandle; 41 | 42 | NSMutableDictionary *returnValue = [NSMutableDictionary new]; 43 | MIHandle = dlopen("/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation", RTLD_NOW); 44 | mobileInstallationLookup = NULL; 45 | 46 | if (MIHandle) { 47 | mobileInstallationLookup = (MobileInstallationLookup)dlsym(MIHandle, "MobileInstallationLookup"); 48 | if (mobileInstallationLookup) { 49 | 50 | NSDictionary *installedApps; 51 | NSDictionary *options = @{ 52 | @"ApplicationType" : @"User", 53 | @"ReturnAttributes" : @[ 54 | @"CFBundleShortVersionString", 55 | @"CFBundleVersion", 56 | @"Path", 57 | @"CFBundleDisplayName", 58 | @"CFBundleExecutable", 59 | @"MinimumOSVersion" 60 | ] 61 | }; 62 | 63 | installedApps = mobileInstallationLookup(options); 64 | 65 | for (NSString *bundleID in installedApps.allKeys) { 66 | NSDictionary *appI = installedApps[bundleID]; 67 | NSURL *bundleURL = [NSURL fileURLWithPath:appI[@"Path"]]; 68 | NSString *scinfo = [bundleURL.path stringByAppendingPathComponent:@"SC_Info"]; 69 | 70 | BOOL isDirectory; 71 | BOOL purchased = [[NSFileManager defaultManager] fileExistsAtPath:scinfo isDirectory:&isDirectory]; 72 | 73 | if (purchased && isDirectory) { 74 | NSString *name = appI[@"CFBundleDisplayName"]; 75 | if (name == nil) { 76 | name = appI[@"CFBundleExecutable"]; 77 | } 78 | 79 | NSDictionary *bundleInfo = @{ 80 | @"BundleContainer" : bundleURL.URLByDeletingLastPathComponent, 81 | @"BundleURL" : bundleURL, 82 | @"DisplayName" : name, 83 | @"BundleIdentifier" : bundleID 84 | }; 85 | Application *app = [[Application alloc] initWithBundleInfo:bundleInfo]; 86 | returnValue[bundleID] = app; 87 | 88 | [self cacheBundle:bundleInfo]; 89 | } 90 | } 91 | } 92 | } 93 | 94 | [self writeToCache]; 95 | 96 | return returnValue; 97 | } 98 | 99 | - (NSDictionary *)listApplicationsForiOS8AndHigher { 100 | NSMutableDictionary *returnValue = [NSMutableDictionary new]; 101 | LSApplicationWorkspace *applicationWorkspace = [LSApplicationWorkspace defaultWorkspace]; 102 | 103 | NSArray *proxies = [applicationWorkspace allApplications]; 104 | NSDictionary *bundleInfo = nil; 105 | 106 | for (FBApplicationInfo *proxy in proxies) { 107 | NSString *appType = [proxy performSelector:@selector(applicationType)]; 108 | 109 | if ([appType isEqualToString:@"User"] && proxy.bundleContainerURL && proxy.bundleURL) { 110 | NSString *scinfo = [proxy.bundleURL.path stringByAppendingPathComponent:@"SC_Info"]; 111 | 112 | BOOL isDirectory; 113 | BOOL purchased = [[NSFileManager defaultManager] fileExistsAtPath:scinfo isDirectory:&isDirectory]; 114 | 115 | if (purchased && isDirectory) { 116 | NSString *itemName = ((LSApplicationProxy *)proxy).itemName; 117 | 118 | if (!itemName) { 119 | itemName = ((LSApplicationProxy *)proxy).localizedName; 120 | } 121 | 122 | bundleInfo = @{ 123 | @"BundleContainer" : proxy.bundleContainerURL, 124 | @"BundleURL" : proxy.bundleURL, 125 | @"DisplayName" : itemName, 126 | @"BundleIdentifier" : proxy.bundleIdentifier 127 | }; 128 | 129 | Application *app = [[Application alloc] initWithBundleInfo:bundleInfo]; 130 | returnValue[proxy.bundleIdentifier] = app; 131 | 132 | [self cacheBundle:bundleInfo]; 133 | } 134 | } 135 | } 136 | 137 | [self writeToCache]; 138 | 139 | return returnValue.copy; 140 | } 141 | 142 | - (void)writeToCache { 143 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); 144 | dispatch_async(queue, ^{ 145 | [self.cachedApps writeToFile:applistCachePath atomically:YES]; 146 | }); 147 | } 148 | 149 | - (NSDictionary *)_allApplications { 150 | NSDictionary *returnValue; 151 | if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(NSFoundationVersionNumber_iOS_7_0)) { 152 | returnValue = [self listApplicationsForiOS7AndLower]; 153 | } else { 154 | returnValue = [self listApplicationsForiOS8AndHigher]; 155 | } 156 | 157 | return returnValue.copy; 158 | } 159 | 160 | - (NSDictionary *)installedApps { 161 | return [self _allApplications]; 162 | } 163 | 164 | - (NSDictionary *)cachedApplications { 165 | if (_cachedApps.count < 1) { 166 | return [self _allApplications]; 167 | } 168 | 169 | NSMutableDictionary *returnValue = [NSMutableDictionary new]; 170 | for (NSDictionary *bundleInfo in _cachedApps) { 171 | Application *app = [[Application alloc] initWithBundleInfo:bundleInfo]; 172 | returnValue[bundleInfo[@"BundleIdentifier"]] = app; 173 | } 174 | 175 | return returnValue; 176 | } 177 | 178 | - (void)cacheBundle:(NSDictionary *)bundle { 179 | [_cachedApps addObject:bundle]; 180 | } 181 | 182 | - (NSArray *)dumpedApps { 183 | NSString *dumpedPath = @""; 184 | NSArray *array = [[NSArray alloc] initWithArray:[[NSFileManager defaultManager] contentsOfDirectoryAtPath:dumpedPath 185 | error:nil]]; 186 | 187 | NSMutableArray *paths = [NSMutableArray new]; 188 | 189 | for (NSUInteger i = 0; i < array.count; i++) { 190 | if (![[array[i] pathExtension] caseInsensitiveCompare:@"ipa"]) { 191 | [paths addObject:array[i]]; 192 | } 193 | } 194 | 195 | return paths; 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /Clutch/LSApplicationWorkspace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This header is generated by classdump-dyld 0.7 3 | * on Sunday, November 22, 2015 at 10:24:36 PM Eastern Standard Time 4 | * Operating System: Version 9.0.2 (Build 13A452) 5 | * Image Source: /System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices 6 | * classdump-dyld is licensed under GPLv3, Copyright © 2013 by Elias Limneos. 7 | */ 8 | 9 | @interface LSApplicationWorkspace : NSObject 10 | + (LSApplicationWorkspace *)defaultWorkspace; 11 | - (void)_sf_openURL:(id)arg1 withOptions:(id)arg2 completionHandler:(/*^block*/ id)arg3; 12 | - (id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 userInfo:(id)arg3; 13 | @property (nonatomic, readonly) BOOL establishConnection; 14 | @property (nonatomic, readonly, strong) id remoteObserver; 15 | - (id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3 applyFilter:(/*^block*/ id)arg4; 16 | - (id)operationToOpenResource:(id)arg1 usingApplication:(id)arg2 uniqueDocumentIdentifier:(id)arg3 userInfo:(id)arg4; 17 | - (BOOL)installApplication:(id)arg1 withOptions:(id)arg2 error:(id *)arg3 usingBlock:(/*^block*/ id)arg4; 18 | - (id)installProgressForApplication:(id)arg1 withPhase:(unsigned long long)arg2; 19 | - (BOOL)registerApplicationDictionary:(id)arg1 withObserverNotification:(int)arg2; 20 | - (BOOL)installPhaseFinishedForProgress:(id)arg1; 21 | - (unsigned long long)getInstallTypeForOptions:(id)arg1 andApp:(id)arg2; 22 | - (id)installBundle:(id)arg1 23 | withOptions:(id)arg2 24 | usingBlock:(/*^block*/ id)arg3 25 | forApp:(id)arg4 26 | withError:(id *)arg5 27 | outInstallProgress:(id *)arg6; 28 | - (BOOL)uninstallApplication:(id)arg1 withOptions:(id)arg2 usingBlock:(/*^block*/ id)arg3; 29 | - (BOOL)registerBundleWithInfo:(id)arg1 options:(id)arg2 type:(unsigned long long)arg3 progress:(id)arg4; 30 | - (void)clearCreatedProgressForBundleID:(id)arg1; 31 | - (void)removeInstallProgressForBundleID:(id)arg1; 32 | - (void)getKnowledgeUUID:(id *)arg1 andSequenceNumber:(id *)arg2; 33 | @property (nonatomic, readonly, assign) id delegateProxy; 34 | @property (nonatomic, readonly, strong) id directionsApplications; 35 | @property (nonatomic, readonly, strong) id applicationsWithAudioComponents; 36 | @property (nonatomic, readonly, strong) id applicationsWithSettingsBundle; 37 | @property (nonatomic, readonly, strong) id applicationsWithVPNPlugins; 38 | @property (nonatomic, readonly, strong) id applicationsWithExternalAccessoryProtocols; 39 | - (id)applicationForUserActivityType:(id)arg1; 40 | - (id)applicationForUserActivityDomainName:(id)arg1; 41 | - (id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3 withFilter:(/*^block*/ id)arg4; 42 | - (id)pluginsWithIdentifiers:(id)arg1 protocols:(id)arg2 version:(id)arg3; 43 | - (void)openUserActivity:(id)arg1 withApplicationProxy:(id)arg2 completionHandler:(/*^block*/ id)arg3; 44 | @property (nonatomic, readonly, strong) id installedVPNPlugins; 45 | @property (nonatomic, readonly, strong) id unrestrictedApplications; 46 | @property (nonatomic, readonly, strong) id publicURLSchemes; 47 | - (BOOL)getClaimedActivityTypes:(id *)arg1 domains:(id *)arg2; 48 | - (BOOL)installApplication:(id)arg1 withOptions:(id)arg2; 49 | - (BOOL)installApplication:(id)arg1 withOptions:(id)arg2 error:(id *)arg3; 50 | - (BOOL)downgradeApplicationToPlaceholder:(id)arg1 withOptions:(id)arg2 error:(id *)arg3; 51 | - (BOOL)registerApplicationDictionary:(id)arg1; 52 | - (BOOL)registerApplication:(id)arg1; 53 | - (BOOL)unregisterApplication:(id)arg1; 54 | - (BOOL)registerPlugin:(id)arg1; 55 | - (BOOL)unregisterPlugin:(id)arg1; 56 | - (BOOL)updateSINFWithData:(id)arg1 forApplication:(id)arg2 options:(id)arg3 error:(id *)arg4; 57 | - (BOOL)invalidateIconCache:(id)arg1; 58 | - (void)_clearCachedAdvertisingIdentifier; 59 | @property (nonatomic, readonly, strong) id deviceIdentifierForAdvertising; 60 | - (id)installProgressForBundleID:(id)arg1 makeSynchronous:(unsigned char)arg2; 61 | - (BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3; 62 | - (id)applicationForOpeningResource:(id)arg1; 63 | - (void)addObserver:(id)arg1; 64 | - (BOOL)isApplicationAvailableToOpenURL:(id)arg1 error:(id *)arg2; 65 | - (id)applicationsAvailableForHandlingURLScheme:(id)arg1; 66 | @property (nonatomic, readonly, strong) id privateURLSchemes; 67 | - (id)URLOverrideForURL:(id)arg1; 68 | - (BOOL)openURL:(id)arg1; 69 | - (void)removeObserver:(id)arg1; 70 | - (id)operationToOpenResource:(id)arg1 71 | usingApplication:(id)arg2 72 | uniqueDocumentIdentifier:(id)arg3 73 | userInfo:(id)arg4 74 | delegate:(id)arg5; 75 | @property (nonatomic, readonly, strong) id deviceIdentifierForVendor; 76 | - (id)applicationsAvailableForOpeningDocument:(id)arg1; 77 | - (id)operationToOpenResource:(id)arg1 78 | usingApplication:(id)arg2 79 | uniqueDocumentIdentifier:(id)arg3 80 | sourceIsManaged:(BOOL)arg4 81 | userInfo:(id)arg5 82 | delegate:(id)arg6; 83 | - (BOOL)openURL:(id)arg1 withOptions:(id)arg2; 84 | - (BOOL)openSensitiveURL:(id)arg1 withOptions:(id)arg2; 85 | @property (nonatomic, readonly, strong) id allInstalledApplications; 86 | - (id)applicationsOfType:(unsigned long long)arg1; 87 | - (void)enumerateBundlesOfType:(unsigned long long)arg1 usingBlock:(/*^block*/ id)arg2; 88 | - (BOOL)uninstallApplication:(id)arg1 withOptions:(id)arg2; 89 | @property (nonatomic, readonly, strong) id placeholderApplications; 90 | - (BOOL)applicationIsInstalled:(id)arg1; 91 | @property (nonatomic, readonly, strong) id installedPlugins; 92 | - (void)_LSClearSchemaCaches; 93 | - (void)clearAdvertisingIdentifier; 94 | @property (nonatomic, readonly, strong) id applicationsWithUIBackgroundModes; 95 | @property (nonatomic, readonly, copy) NSArray *allApplications; 96 | - (BOOL)openApplicationWithBundleID:(id)arg1; 97 | - (BOOL)openSensitiveURL:(id)arg1 withOptions:(id)arg2 error:(id *)arg3; 98 | - (BOOL)openURL:(id)arg1 withOptions:(id)arg2 error:(id *)arg3; 99 | @end 100 | -------------------------------------------------------------------------------- /Clutch/MiniZip/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(MiniZip LANGUAGES C) 4 | 5 | set(HEADERS_FILES crypt.h ioapi.h unzip.h zip.h) 6 | 7 | set(SOURCE_FILES ioapi.c unzip.c zip.c) 8 | source_group("" FILES ${HEADERS_FILES} ${SOURCE_FILES}) 9 | 10 | add_library(MiniZip OBJECT ${HEADERS_FILES} ${SOURCE_FILES}) 11 | target_include_directories(MiniZip PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 12 | 13 | if(CMAKE_GENERATOR STREQUAL Xcode) 14 | set_target_properties(MiniZip 15 | PROPERTIES XCODE_ATTRIBUTE_SDKROOT 16 | iphoneos 17 | XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 18 | ${DEPLOYMENT_TARGET} 19 | XCODE_ATTIRBUTE_ENABLE_BITCODE 20 | YES 21 | XCODE_ATTIRBUTE_SKIP_INSTALL 22 | YES 23 | XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT 24 | dwarf-with-dsym 25 | XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 26 | ${DEVICE_FAMILY} 27 | XCODE_ATTRIBUTE_INSTALL_PATH 28 | "$(LOCAL_APPS_DIR)" 29 | XCODE_ATTRIBUTE_ENABLE_TESTABILITY 30 | YES 31 | XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN 32 | YES) 33 | else() 34 | target_compile_options(MiniZip 35 | PRIVATE -miphoneos-version-min=${DEPLOYMENT_TARGET}) 36 | endif() 37 | -------------------------------------------------------------------------------- /Clutch/MiniZip/crypt.h: -------------------------------------------------------------------------------- 1 | /* crypt.h -- base code for crypt/uncrypt ZIPfile 2 | 3 | 4 | Version 1.01e, February 12th, 2005 5 | 6 | Copyright (C) 1998-2005 Gilles Vollant 7 | 8 | This code is a modified version of crypting code in Infozip distribution 9 | 10 | The encryption/decryption parts of this source code (as opposed to the 11 | non-echoing password parts) were originally written in Europe. The 12 | whole source package can be freely distributed, including from the USA. 13 | (Prior to January 2000, re-export from the US was a violation of US law.) 14 | 15 | This encryption code is a direct transcription of the algorithm from 16 | Roger Schlafly, described by Phil Katz in the file appnote.txt. This 17 | file (appnote.txt) is distributed with the PKZIP program (even in the 18 | version without encryption capabilities). 19 | 20 | If you don't need crypting in your application, just define symbols 21 | NOCRYPT and NOUNCRYPT. 22 | 23 | This code support the "Traditional PKWARE Encryption". 24 | 25 | The new AES encryption added on Zip format by Winzip (see the page 26 | http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong 27 | Encryption is not supported. 28 | */ 29 | 30 | #define CRC32(c, b) ((*(pcrc_32_tab + (((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) 31 | 32 | /*********************************************************************** 33 | * Return the next byte in the pseudo-random sequence 34 | */ 35 | static int decrypt_byte(unsigned long *pkeys, const unsigned long *pcrc_32_tab) { 36 | (void)pcrc_32_tab; 37 | unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an 38 | * unpredictable manner on 16-bit systems; not a problem 39 | * with any known compiler so far, though */ 40 | 41 | temp = ((unsigned)(*(pkeys + 2)) & 0xffff) | 2; 42 | return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); 43 | } 44 | 45 | /*********************************************************************** 46 | * Update the encryption keys with the next byte of plain text 47 | */ 48 | static int update_keys(unsigned long *pkeys, const unsigned long *pcrc_32_tab, int c) { 49 | (*(pkeys + 0)) = CRC32((*(pkeys + 0)), c); 50 | (*(pkeys + 1)) += (*(pkeys + 0)) & 0xff; 51 | (*(pkeys + 1)) = (*(pkeys + 1)) * 134775813L + 1; 52 | { 53 | register int keyshift = (int)((*(pkeys + 1)) >> 24); 54 | (*(pkeys + 2)) = CRC32((*(pkeys + 2)), keyshift); 55 | } 56 | return c; 57 | } 58 | 59 | /*********************************************************************** 60 | * Initialize the encryption keys and the random header according to 61 | * the given password. 62 | */ 63 | static void init_keys(const char *passwd, unsigned long *pkeys, const unsigned long *pcrc_32_tab) { 64 | *(pkeys + 0) = 305419896L; 65 | *(pkeys + 1) = 591751049L; 66 | *(pkeys + 2) = 878082192L; 67 | while (*passwd != '\0') { 68 | update_keys(pkeys, pcrc_32_tab, (int)*passwd); 69 | passwd++; 70 | } 71 | } 72 | 73 | #define zdecode(pkeys, pcrc_32_tab, c) (update_keys(pkeys, pcrc_32_tab, c ^= decrypt_byte(pkeys, pcrc_32_tab))) 74 | 75 | #define zencode(pkeys, pcrc_32_tab, c, t) \ 76 | (t = decrypt_byte(pkeys, pcrc_32_tab), update_keys(pkeys, pcrc_32_tab, c), t ^ (c)) 77 | 78 | #ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED 79 | 80 | #define RAND_HEAD_LEN 12 81 | /* "last resort" source for second part of crypt seed pattern */ 82 | #ifndef ZCR_SEED2 83 | #define ZCR_SEED2 3141592654UL /* use PI as default pattern */ 84 | #endif 85 | 86 | static int crypthead(const char *passwd, /* password string */ 87 | unsigned char *buf, /* where to write header */ 88 | int bufSize, 89 | unsigned long *pkeys, 90 | const unsigned long *pcrc_32_tab, 91 | unsigned long crcForCrypting) { 92 | int n; /* index in random header */ 93 | int t; /* temporary */ 94 | int c; /* random byte */ 95 | unsigned char header[RAND_HEAD_LEN - 2]; /* random header */ 96 | static unsigned calls = 0; /* ensure different random header each time */ 97 | 98 | if (bufSize < RAND_HEAD_LEN) 99 | return 0; 100 | 101 | /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the 102 | * output of rand() to get less predictability, since rand() is 103 | * often poorly implemented. 104 | */ 105 | if (++calls == 1) { 106 | srand((unsigned)((unsigned long)time(NULL) ^ ZCR_SEED2)); 107 | } 108 | init_keys(passwd, pkeys, pcrc_32_tab); 109 | for (n = 0; n < RAND_HEAD_LEN - 2; n++) { 110 | c = (arc4random() >> 7) & 0xff; 111 | header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); 112 | } 113 | /* Encrypt random header (last two bytes is high word of crc) */ 114 | init_keys(passwd, pkeys, pcrc_32_tab); 115 | for (n = 0; n < RAND_HEAD_LEN - 2; n++) { 116 | buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); 117 | } 118 | buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); 119 | buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); 120 | return n; 121 | } 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /Clutch/MiniZip/ioapi.h: -------------------------------------------------------------------------------- 1 | /* ioapi.h -- IO base function header for compress/uncompress .zip 2 | part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) 3 | 4 | Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) 5 | 6 | Modifications for Zip64 support 7 | Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) 8 | 9 | For more info read MiniZip_info.txt 10 | 11 | Changes 12 | 13 | Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) 14 | Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. 15 | More if/def section may be needed to support other platforms 16 | Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. 17 | (but you should use iowin32.c for windows instead) 18 | 19 | */ 20 | 21 | #ifndef _ZLIBIOAPI64_H 22 | #define _ZLIBIOAPI64_H 23 | 24 | #if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) 25 | /* Linux needs this to support file operation on files larger then 4+GB 26 | But might need better if/def to select just the platforms that needs them. */ 27 | #ifndef __USE_FILE_OFFSET64 28 | #define __USE_FILE_OFFSET64 29 | #endif 30 | #ifndef __USE_LARGEFILE64 31 | #define __USE_LARGEFILE64 32 | #endif 33 | #ifndef _LARGEFILE64_SOURCE 34 | #define _LARGEFILE64_SOURCE 35 | #endif 36 | #ifndef _FILE_OFFSET_BIT 37 | #define _FILE_OFFSET_BIT 64 38 | #endif 39 | #endif 40 | 41 | #include "zlib.h" 42 | #include 43 | #include 44 | 45 | #if defined(USE_FILE32API) 46 | #define fopen64 fopen 47 | #define ftello64 ftell 48 | #define fseeko64 fseek 49 | #else 50 | #ifdef __FreeBSD__ 51 | #define fopen64 fopen 52 | #define ftello64 ftello 53 | #define fseeko64 fseeko 54 | #endif 55 | #ifdef _MSC_VER 56 | #define fopen64 fopen 57 | #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) 58 | #define ftello64 _ftelli64 59 | #define fseeko64 _fseeki64 60 | #else // old MSC 61 | #define ftello64 ftell 62 | #define fseeko64 fseek 63 | #endif 64 | #endif 65 | #endif 66 | 67 | /* a type choosen by DEFINE */ 68 | #ifdef HAVE_64BIT_INT_CUSTOM 69 | typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; 70 | #else 71 | #ifdef HAS_STDINT_H 72 | #import "stdint.h" 73 | typedef uint64_t ZPOS64_T; 74 | #else 75 | 76 | #if defined(_MSC_VER) || defined(__BORLANDC__) 77 | typedef unsigned __int64 ZPOS64_T; 78 | #else 79 | typedef unsigned long long int ZPOS64_T; 80 | #endif 81 | #endif 82 | #endif 83 | 84 | #ifdef __cplusplus 85 | extern "C" { 86 | #endif 87 | 88 | #define ZLIB_FILEFUNC_SEEK_CUR (1) 89 | #define ZLIB_FILEFUNC_SEEK_END (2) 90 | #define ZLIB_FILEFUNC_SEEK_SET (0) 91 | 92 | #define ZLIB_FILEFUNC_MODE_READ (1) 93 | #define ZLIB_FILEFUNC_MODE_WRITE (2) 94 | #define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) 95 | 96 | #define ZLIB_FILEFUNC_MODE_EXISTING (4) 97 | #define ZLIB_FILEFUNC_MODE_CREATE (8) 98 | 99 | #ifndef ZCALLBACK 100 | #if (defined(WIN32) || defined(_WIN32) || defined(WINDOWS) || defined(_WINDOWS)) && defined(CALLBACK) && \ 101 | defined(USEWINDOWS_CALLBACK) 102 | #define ZCALLBACK CALLBACK 103 | #else 104 | #define ZCALLBACK 105 | #endif 106 | #endif 107 | 108 | typedef voidpf(ZCALLBACK *open_file_func) OF((voidpf opaque, const char *filename, int mode)); 109 | typedef voidpf(ZCALLBACK *opendisk_file_func) OF((voidpf opaque, voidpf stream, int number_disk, int mode)); 110 | typedef uLong(ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void *buf, uLong size)); 111 | typedef uLong(ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void *buf, uLong size)); 112 | typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); 113 | typedef int(ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); 114 | 115 | typedef long(ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); 116 | typedef long(ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); 117 | 118 | /* here is the "old" 32 bits structure structure */ 119 | typedef struct zlib_filefunc_def_s { 120 | open_file_func zopen_file; 121 | opendisk_file_func zopendisk_file; 122 | read_file_func zread_file; 123 | write_file_func zwrite_file; 124 | tell_file_func ztell_file; 125 | seek_file_func zseek_file; 126 | close_file_func zclose_file; 127 | testerror_file_func zerror_file; 128 | voidpf opaque; 129 | } zlib_filefunc_def; 130 | 131 | typedef ZPOS64_T(ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); 132 | typedef long(ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); 133 | typedef voidpf(ZCALLBACK *open64_file_func) OF((voidpf opaque, const void *filename, int mode)); 134 | typedef voidpf(ZCALLBACK *opendisk64_file_func) OF((voidpf opaque, voidpf stream, int number_disk, int mode)); 135 | 136 | typedef struct zlib_filefunc64_def_s { 137 | open64_file_func zopen64_file; 138 | opendisk64_file_func zopendisk64_file; 139 | read_file_func zread_file; 140 | write_file_func zwrite_file; 141 | tell64_file_func ztell64_file; 142 | seek64_file_func zseek64_file; 143 | close_file_func zclose_file; 144 | testerror_file_func zerror_file; 145 | voidpf opaque; 146 | } zlib_filefunc64_def; 147 | 148 | void fill_fopen_filefunc OF((zlib_filefunc_def * pzlib_filefunc_def)); 149 | void fill_fopen64_filefunc OF((zlib_filefunc64_def * pzlib_filefunc_def)); 150 | 151 | /* now internal definition, only for zip.c and unzip.h */ 152 | typedef struct zlib_filefunc64_32_def_s { 153 | zlib_filefunc64_def zfile_func64; 154 | open_file_func zopen32_file; 155 | opendisk_file_func zopendisk32_file; 156 | tell_file_func ztell32_file; 157 | seek_file_func zseek32_file; 158 | } zlib_filefunc64_32_def; 159 | 160 | #define ZREAD64(filefunc, filestream, buf, size) \ 161 | ((*((filefunc).zfile_func64.zread_file))((filefunc).zfile_func64.opaque, filestream, buf, size)) 162 | #define ZWRITE64(filefunc, filestream, buf, size) \ 163 | ((*((filefunc).zfile_func64.zwrite_file))((filefunc).zfile_func64.opaque, filestream, buf, size)) 164 | //#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) 165 | //#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) 166 | //((filefunc).opaque,filestream,pos,mode)) 167 | #define ZCLOSE64(filefunc, filestream) \ 168 | ((*((filefunc).zfile_func64.zclose_file))((filefunc).zfile_func64.opaque, filestream)) 169 | #define ZERROR64(filefunc, filestream) \ 170 | ((*((filefunc).zfile_func64.zerror_file))((filefunc).zfile_func64.opaque, filestream)) 171 | 172 | voidpf call_zopen64 OF((const zlib_filefunc64_32_def *pfilefunc, const void *filename, int mode)); 173 | voidpf call_zopendisk64 OF((const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, int number_disk, int mode)); 174 | long call_zseek64 OF((const zlib_filefunc64_32_def *pfilefunc, voidpf filestream, ZPOS64_T offset, int origin)); 175 | ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def *pfilefunc, voidpf filestream)); 176 | 177 | void fill_zlib_filefunc64_32_def_from_filefunc32 OF((zlib_filefunc64_32_def * p_filefunc64_32, 178 | const zlib_filefunc_def *p_filefunc32)); 179 | 180 | #define ZOPEN64(filefunc, filename, mode) (call_zopen64((&(filefunc)), (filename), (mode))) 181 | #define ZOPENDISK64(filefunc, filestream, diskn, mode) (call_zopendisk64((&(filefunc)), (filestream), (diskn), (mode))) 182 | #define ZTELL64(filefunc, filestream) (call_ztell64((&(filefunc)), (filestream))) 183 | #define ZSEEK64(filefunc, filestream, pos, mode) (call_zseek64((&(filefunc)), (filestream), (pos), (mode))) 184 | 185 | #ifdef __cplusplus 186 | } 187 | #endif 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /Clutch/NSBundle+Clutch.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+Clutch.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 20.04.15. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @interface NSBundle (Clutch) 12 | 13 | @property (nonatomic, retain, nullable) NSString *clutchBID; 14 | @property (nonatomic, readonly, copy) NSString *bundleIdentifier; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Clutch/NSBundle+Clutch.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+Clutch.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 20.04.15. 6 | // 7 | // 8 | @import ObjectiveC.runtime; 9 | 10 | #import "NSBundle+Clutch.h" 11 | 12 | static NSString *_bID; 13 | 14 | @implementation NSBundle (Clutch) 15 | 16 | - (NSString *)clutchBID { 17 | return objc_getAssociatedObject(self, &_bID); 18 | } 19 | 20 | - (void)setClutchBID:(NSString *)clutchBID { 21 | [self willChangeValueForKey:@"clutchBID"]; 22 | objc_setAssociatedObject(self, &_bID, clutchBID, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 23 | [self didChangeValueForKey:@"clutchBID"]; 24 | } 25 | 26 | - (NSString *)bundleIdentifier { 27 | if ([self.bundlePath isEqualToString:NSBundle.mainBundle.bundlePath]) { 28 | return self.clutchBID; 29 | } 30 | 31 | return self.infoDictionary[(__bridge NSString *)kCFBundleIdentifierKey]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Clutch/NSData+Reading.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Reading.h 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @interface NSData (Reading) 31 | 32 | @property (nonatomic) NSUInteger currentOffset; 33 | 34 | @property (nonatomic, readonly) uint8_t nextByte; 35 | - (uint8_t)byteAtOffset:(NSUInteger)offset; 36 | 37 | @property (nonatomic, readonly) uint16_t nextShort; 38 | - (uint16_t)shortAtOffset:(NSUInteger)offset; 39 | 40 | @property (nonatomic, readonly) uint32_t nextInt; 41 | - (uint32_t)intAtOffset:(NSUInteger)offset; 42 | 43 | @property (nonatomic, readonly) uint64_t nextLong; 44 | - (uint64_t)longAtOffset:(NSUInteger)offset; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /Clutch/NSData+Reading.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Reading.m 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #import "NSData+Reading.h" 29 | #import 30 | 31 | @implementation NSData (Reading) 32 | 33 | static char OFFSET; 34 | - (NSUInteger)currentOffset { 35 | NSNumber *value = objc_getAssociatedObject(self, &OFFSET); 36 | return value.unsignedIntegerValue; 37 | } 38 | 39 | - (void)setCurrentOffset:(NSUInteger)offset { 40 | [self willChangeValueForKey:@"currentOffset"]; 41 | objc_setAssociatedObject(self, &OFFSET, @(offset), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 42 | [self didChangeValueForKey:@"currentOffset"]; 43 | } 44 | 45 | - (uint8_t)nextByte { 46 | uint8_t nextByte = [self byteAtOffset:self.currentOffset]; 47 | self.currentOffset += sizeof(uint8_t); 48 | return nextByte; 49 | } 50 | 51 | - (uint8_t)byteAtOffset:(NSUInteger)offset { 52 | uint8_t result; 53 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 54 | return result; 55 | } 56 | 57 | - (uint16_t)nextShort { 58 | uint16_t nextShort = [self shortAtOffset:self.currentOffset]; 59 | self.currentOffset += sizeof(uint16_t); 60 | return nextShort; 61 | } 62 | 63 | - (uint16_t)shortAtOffset:(NSUInteger)offset { 64 | uint16_t result; 65 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 66 | return result; 67 | } 68 | 69 | - (uint32_t)nextInt { 70 | uint32_t nextInt = [self intAtOffset:self.currentOffset]; 71 | self.currentOffset += sizeof(uint32_t); 72 | return nextInt; 73 | } 74 | 75 | - (uint32_t)intAtOffset:(NSUInteger)offset { 76 | uint32_t result; 77 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 78 | return result; 79 | } 80 | 81 | - (uint64_t)nextLong { 82 | uint64_t nextLong = [self longAtOffset:self.currentOffset]; 83 | self.currentOffset += sizeof(uint64_t); 84 | return nextLong; 85 | } 86 | 87 | - (uint64_t)longAtOffset:(NSUInteger)offset; 88 | { 89 | uint64_t result; 90 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 91 | return result; 92 | } 93 | 94 | @end 95 | 96 | @implementation NSMutableData (ByteAdditions) 97 | 98 | - (void)appendByte:(uint8_t)value { 99 | [self appendBytes:&value length:sizeof(value)]; 100 | } 101 | 102 | - (void)appendShort:(uint16_t)value { 103 | uint16_t swap = CFSwapInt16HostToLittle(value); 104 | [self appendBytes:&swap length:sizeof(swap)]; 105 | } 106 | 107 | - (void)appendInt:(uint32_t)value { 108 | uint32_t swap = CFSwapInt32HostToLittle(value); 109 | [self appendBytes:&swap length:sizeof(swap)]; 110 | } 111 | 112 | - (void)appendLong:(uint64_t)value; 113 | { 114 | uint64_t swap = CFSwapInt64HostToLittle(value); 115 | [self appendBytes:&swap length:sizeof(swap)]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /Clutch/NSFileHandle+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileHandle+Private.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 01.04.15. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @interface NSFileHandle (Private) 12 | 13 | /** 14 | Gets an unsigned integer at the given offset. 15 | @param offset File offset. 16 | @return Unsigned integer value. 17 | */ 18 | - (uint32_t)unsignedInt32Atoffset:(const unsigned long long)offset; 19 | /** 20 | Replaces bytes given a range with new bytes. 21 | @param range Range. 22 | @param bytes Replacement data. 23 | */ 24 | - (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)bytes; 25 | /** 26 | Gets a fixed number of bytes at a given offset. 27 | @param result Buffer to write to. 28 | @param offset File offset. 29 | @param length Length of the buffer. 30 | */ 31 | - (void)getBytes:(void *)result atOffset:(const unsigned long long)offset length:(NSUInteger)length; 32 | /** 33 | Gets a fixed number of a bytes at a given offset using a range structure. 34 | @param result Buffer to write to. 35 | @param range Range structure. 36 | */ 37 | - (void)getBytes:(void *)result inRange:(NSRange)range; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /Clutch/NSFileHandle+Private.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSFileHandle+Private.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 01.04.15. 6 | // 7 | // 8 | 9 | #import "NSFileHandle+Private.h" 10 | 11 | @implementation NSFileHandle (Private) 12 | 13 | #pragma mark - Public methods 14 | 15 | - (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)bytes { 16 | [self performWithFileOffsetResetOffset:range.location 17 | block:^(NSFileHandle *__weak fh) { 18 | [fh writeData:[NSData dataWithBytes:bytes length:range.length]]; 19 | }]; 20 | } 21 | 22 | - (void)getBytes:(void *)result inRange:(NSRange)range { 23 | [self performWithFileOffsetResetOffset:range.location 24 | block:^(NSFileHandle *__weak fh) { 25 | NSData *data = [fh readDataOfLength:range.length]; 26 | [data getBytes:result length:range.length]; 27 | }]; 28 | } 29 | 30 | - (void)getBytes:(void *)result atOffset:(const unsigned long long)offset length:(NSUInteger)length { 31 | [self performWithFileOffsetResetOffset:offset 32 | block:^(NSFileHandle *__weak fh) { 33 | NSData *data = [fh readDataOfLength:length]; 34 | [data getBytes:result length:length]; 35 | }]; 36 | } 37 | 38 | - (uint32_t)unsignedInt32Atoffset:(const unsigned long long)offset { 39 | __block uint32_t result; 40 | [self performWithFileOffsetResetOffset:offset 41 | block:^(NSFileHandle *__weak fh) { 42 | NSData *data = [fh readDataOfLength:sizeof(uint32_t)]; 43 | [data getBytes:&result length:sizeof(result)]; 44 | }]; 45 | return result; 46 | } 47 | 48 | #pragma mark - Private methods 49 | 50 | - (void)performWithFileOffsetResetOffset:(const unsigned long long)offset 51 | block:(void (^)(NSFileHandle *__weak fh))block { 52 | NSFileHandle *__weak weakSelf = self; 53 | unsigned long long oldOffset = self.offsetInFile; 54 | [self seekToFileOffset:offset]; 55 | block(weakSelf); 56 | [self seekToFileOffset:oldOffset]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Clutch/NSTask.h: -------------------------------------------------------------------------------- 1 | /* NSTask.h 2 | Copyright (c) 1996-2007, Apple Inc. All rights reserved. 3 | */ 4 | 5 | #import 6 | 7 | @class NSString, NSArray, NSDictionary; 8 | 9 | @interface NSTask : NSObject 10 | 11 | // Create an NSTask which can be run at a later time 12 | // An NSTask can only be run once. Subsequent attempts to 13 | // run an NSTask will raise. 14 | // Upon task death a notification will be sent 15 | // { Name = NSTaskDidTerminateNotification; object = task; } 16 | // 17 | 18 | - (instancetype)init; 19 | 20 | // set parameters 21 | // these methods can only be done before a launch 22 | // if not set, use current 23 | // if not set, use current 24 | 25 | // set standard I/O channels; may be either an NSFileHandle or an NSPipe 26 | - (void)setStandardInput:(id)input; 27 | - (void)setStandardOutput:(id)output; 28 | - (void)setStandardError:(id)error; 29 | 30 | // get parameters 31 | @property (NS_NONATOMIC_IOSONLY, copy) NSString *launchPath; 32 | @property (NS_NONATOMIC_IOSONLY, copy) NSArray *arguments; 33 | @property (NS_NONATOMIC_IOSONLY, copy) NSDictionary *environment; 34 | @property (NS_NONATOMIC_IOSONLY, copy) NSString *currentDirectoryPath; 35 | 36 | // get standard I/O channels; could be either an NSFileHandle or an NSPipe 37 | - (id)standardInput; 38 | - (id)standardOutput; 39 | - (id)standardError; 40 | 41 | // actions 42 | - (void)launch; 43 | 44 | - (void)interrupt; // Not always possible. Sends SIGINT. 45 | - (void)terminate; // Not always possible. Sends SIGTERM. 46 | 47 | @property (NS_NONATOMIC_IOSONLY, readonly) BOOL suspend; 48 | @property (NS_NONATOMIC_IOSONLY, readonly) BOOL resume; 49 | 50 | // status 51 | @property (NS_NONATOMIC_IOSONLY, readonly) int processIdentifier; 52 | @property (NS_NONATOMIC_IOSONLY, getter=isRunning, readonly) BOOL running; 53 | 54 | @property (NS_NONATOMIC_IOSONLY, readonly) int terminationStatus; 55 | 56 | @end 57 | 58 | @interface NSTask (NSTaskConveniences) 59 | 60 | + (NSTask *)launchedTaskWithLaunchPath:(NSString *)path arguments:(NSArray *)arguments; 61 | // convenience; create and launch 62 | 63 | - (void)waitUntilExit; 64 | // poll the runLoop in defaultMode until task completes 65 | 66 | @end 67 | 68 | FOUNDATION_EXPORT NSString *const NSTaskDidTerminateNotification; 69 | -------------------------------------------------------------------------------- /Clutch/SCInfoBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCInfoBuilder.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 03.04.15. 6 | // 7 | // 8 | 9 | #import "ClutchBundle.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | struct sinf_atom { 14 | uint32_t size; // size of entire atom structure 15 | uint32_t name; // name (usually an ASCII value) 16 | uint8_t blob[]; // (size - 8) byte long data blob 17 | }; 18 | 19 | struct sinf_kval { 20 | uint32_t name; // name of structure 21 | uint32_t val; // value of structure 22 | }; 23 | 24 | @interface SCInfoBuilder : NSObject 25 | 26 | + (nullable NSData *)sinfForBundle:(ClutchBundle *)bundle; 27 | + (NSDictionary *)parseOriginaleSinfForBundle:(ClutchBundle *)bundle; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /Clutch/SCInfoBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCInfoBuilder.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 03.04.15. 6 | // 7 | // 8 | 9 | #import "SCInfoBuilder.h" 10 | #import "NSData+Reading.h" 11 | #import "scinfo.h" 12 | 13 | @implementation SCInfoBuilder 14 | 15 | + (nullable NSData *)sinfForBundle:(ClutchBundle *)bundle { 16 | CLUTCH_UNUSED(bundle); 17 | return nil; 18 | } 19 | 20 | + (NSDictionary *)parseBlob:(NSData *)blobData { 21 | 22 | NSMutableDictionary *_blob = [NSMutableDictionary new]; 23 | 24 | NSDictionary *kvalues = @{ 25 | @"frma" : @"string", 26 | @"schm" : @"data", 27 | @"user" : @"number", 28 | @"key " : @"data", 29 | @"iviv" : @"data", 30 | @"veID" : @"number", 31 | @"plat" : @"number", 32 | @"aver" : @"number", 33 | @"tran" : @"number", 34 | @"song" : @"number", 35 | @"tool" : @"number", 36 | @"medi" : @"number", 37 | @"mode" : @"number", 38 | @"name" : @"string", 39 | @"priv" : @"data", 40 | @"sign" : @"data" 41 | }; 42 | 43 | blobData.currentOffset = 0; 44 | 45 | while (1) { 46 | 47 | if (blobData.currentOffset >= blobData.length) { 48 | break; 49 | } 50 | 51 | uint32_t _realSize = CFSwapInt32(blobData.nextInt); 52 | uint32_t _blobSize = _realSize - sizeof(struct sinf_kval); 53 | uint32_t _blobName = blobData.nextInt; 54 | 55 | NSString *name = @((const char *)&_blobName); 56 | 57 | if (name.length > 4) { 58 | name = [name substringWithRange:NSMakeRange(0, 4)]; 59 | } 60 | 61 | if ([kvalues.allKeys containsObject:name] && name.length) { 62 | 63 | NSData *valData = [blobData subdataWithRange:NSMakeRange(blobData.currentOffset, _blobSize)]; 64 | 65 | if ([kvalues[name] isEqualToString:@"string"]) { 66 | NSString *val = [[NSString alloc] initWithData:valData encoding:NSASCIIStringEncoding]; 67 | _blob[name] = val; 68 | } else if ([kvalues[name] isEqualToString:@"data"]) { 69 | _blob[name] = valData; 70 | } else if ([kvalues[name] isEqualToString:@"number"]) { 71 | uint32_t integer; 72 | [valData getBytes:&integer length:sizeof(integer)]; 73 | _blob[name] = @(CFSwapInt32(integer)); 74 | } 75 | 76 | } else if (name.length && [name isEqualToString:@"schi"]) { 77 | _blob[name] = [self parseBlob:[blobData subdataWithRange:NSMakeRange(blobData.currentOffset, _blobSize)]]; 78 | } else if (name.length && [name isEqualToString:@"righ"]) { 79 | 80 | NSMutableDictionary *_righ = [NSMutableDictionary new]; 81 | 82 | int count = _blobSize / sizeof(struct sinf_kval); 83 | 84 | for (int i = 0; i < count; i++) { 85 | 86 | uint32_t kName = blobData.nextInt; 87 | uint32_t kValue = blobData.nextInt; 88 | 89 | NSString *name_ = @((const char *)&kName); 90 | 91 | if (name_.length > 4) { 92 | name_ = [name_ substringWithRange:NSMakeRange(0, 4)]; 93 | } 94 | 95 | if ([kvalues[name_] isEqualToString:@"number"] && name_.length) { 96 | 97 | _righ[name_] = @(CFSwapInt32(kValue)); 98 | } 99 | } 100 | 101 | _blob[name] = _righ.copy; 102 | continue; 103 | } 104 | 105 | blobData.currentOffset += _blobSize; 106 | } 107 | 108 | return _blob.copy; 109 | } 110 | 111 | + (NSDictionary *)parseOriginaleSinfForBundle:(ClutchBundle *)bundle { 112 | 113 | NSMutableDictionary *_sinfDict = [NSMutableDictionary new]; 114 | 115 | Binary *executable = bundle.executable; 116 | 117 | NSData *sinfData = [NSData dataWithContentsOfFile:executable.sinfPath]; 118 | 119 | uint32_t realSize = CFSwapInt32([sinfData intAtOffset:0]); 120 | 121 | uint32_t blobSize = realSize - sizeof(struct sinf_kval); 122 | 123 | struct sinf_atom sinf; 124 | 125 | [sinfData getBytes:&sinf length:sizeof(sinf)]; 126 | 127 | _sinfDict[@"size"] = @(realSize); 128 | _sinfDict[@"name"] = @((const char *)&sinf.name); 129 | _sinfDict[@"blob"] = [self parseBlob:[sinfData subdataWithRange:NSMakeRange(sizeof(struct sinf_kval), blobSize)]]; 130 | 131 | return _sinfDict.copy; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /Clutch/Stub.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Clutch/ZipArchive.h: -------------------------------------------------------------------------------- 1 | /** 2 | // @header ZipArchive.h 3 | // 4 | // An objective C wrapper for minizip and libz for creating and exanding ZIP files. 5 | // 6 | // @author Created by aish on 08-9-11. 7 | // acsolu@gmail.com 8 | // @copyright Copyright 2008 Inc. All rights reserved. 9 | // 10 | */ 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | typedef NS_ENUM(NSInteger, ZipArchiveCompression) { 15 | ZipArchiveCompressionDefault = -1, 16 | ZipArchiveCompressionNone = 0, 17 | ZipArchiveCompressionSpeed = 1, 18 | ZipArchiveCompressionBest = 9, 19 | }; 20 | 21 | /** 22 | a block that is called from UnzipFileTo:overwrite:withProgressBlock: where the percentage of 23 | files processed (as an integer from 0 to 100), the number of files processed so far and the 24 | total number of files in the archive is called after each file is processed. 25 | */ 26 | typedef void (^ZipArchiveProgressUpdateBlock)(int percentage, int filesProcessed, unsigned long numFiles); 27 | 28 | /** 29 | @protocol 30 | @discussion methods for a delegate to receive error notifications and control overwriting of files 31 | */ 32 | 33 | @protocol ZipArchiveDelegate 34 | @optional 35 | 36 | /** 37 | @brief Delegate method to be notified of errors 38 | 39 | ZipArchive calls this selector on the delegate when errors are encountered. 40 | 41 | @param msg a string describing the error. 42 | */ 43 | 44 | - (void)ErrorMessage:(NSString *)msg; 45 | 46 | /** 47 | @brief Delegate method to determine if a file should be replaced 48 | 49 | When an zip file is being expanded and a file is about to be replaced, this selector 50 | is called on the delegate to notify that file is about to be replaced. The delegate method 51 | should return YES to overwrite the file, or NO to skip it. 52 | 53 | @param file - path to the file to be overwritten. 54 | @result a BOOL - YES to replace, NO to skip 55 | */ 56 | 57 | - (BOOL)OverWriteOperation:(NSString *)file; 58 | 59 | @end 60 | 61 | /** 62 | @class 63 | @brief An object that can create zip files and expand existing ones. 64 | This class provides methods to create a zip file (optionally with a password) and 65 | add files to that zip archive. 66 | 67 | It also provides methods to expand an existing archive file (optionally with a password), 68 | and extract the files. 69 | */ 70 | 71 | @interface ZipArchive : NSObject { 72 | @private 73 | void *_zipFile; 74 | void *_unzFile; 75 | 76 | unsigned long _numFiles; 77 | NSString *_password; 78 | id _delegate; 79 | ZipArchiveProgressUpdateBlock _progressBlock; 80 | 81 | NSArray *_unzippedFiles; 82 | 83 | NSFileManager *_fileManager; 84 | NSStringEncoding _stringEncoding; 85 | } 86 | 87 | /** a delegate object conforming to ZipArchiveDelegate protocol */ 88 | @property (nonatomic, retain) id delegate; 89 | @property (nonatomic, readonly) unsigned long numFiles; 90 | @property (nonatomic, copy) ZipArchiveProgressUpdateBlock progressBlock; 91 | 92 | @property (nonatomic, assign) ZipArchiveCompression compression; 93 | 94 | /** 95 | @brief String encoding to be used when interpreting file names in the zip file. 96 | */ 97 | @property (nonatomic, assign) NSStringEncoding stringEncoding; 98 | 99 | /** an array of files that were successfully expanded. Available after calling UnzipFileTo:overWrite: */ 100 | @property (nonatomic, readonly) NSArray *unzippedFiles; 101 | 102 | - (instancetype)initWithFileManager:(NSFileManager *)fileManager NS_DESIGNATED_INITIALIZER; 103 | 104 | - (BOOL)CreateZipFile2:(NSString *)zipFile; 105 | - (BOOL)CreateZipFile2:(NSString *)zipFile append:(BOOL)isAppend; 106 | - (BOOL)CreateZipFile2:(NSString *)zipFile Password:(NSString *)password; 107 | - (BOOL)CreateZipFile2:(NSString *)zipFile Password:(NSString *)password append:(BOOL)isAppend; 108 | - (BOOL)addFileToZip:(NSString *)file newname:(NSString *)newname; 109 | - (BOOL)addDataToZip:(NSData *)data fileAttributes:(NSDictionary *)attr newname:(NSString *)newname; 110 | @property (nonatomic, readonly) BOOL CloseZipFile2; 111 | 112 | - (BOOL)UnzipOpenFile:(NSString *)zipFile; 113 | - (BOOL)UnzipOpenFile:(NSString *)zipFile Password:(NSString *)password; 114 | - (BOOL)UnzipFileTo:(NSString *)path overWrite:(BOOL)overwrite; 115 | @property (nonatomic, readonly, copy) 116 | NSDictionary *UnzipFileToMemory; // To avoid memory issue, only use this method for small zip files. 117 | @property (nonatomic, readonly) BOOL UnzipCloseFile; 118 | 119 | // List the contents of the zip archive. must be called after UnzipOpenFile. 120 | // If zip file was appended with `CreateZipFile2:append:` or ``CreateZipFile2:Password:append:`, 121 | // `getZipFileContents` result won't be updated until re-unzip-open after close write handle (`CloseZipFile2` then 122 | // `UnzipCloseFile` then (`UnzipOpenFile:` or `UnzipOpenFile:Password`) get called). 123 | @property (nonatomic, getter=getZipFileContents, readonly, copy) NSArray *zipFileContents; 124 | 125 | @end 126 | 127 | NS_ASSUME_NONNULL_END 128 | -------------------------------------------------------------------------------- /Clutch/ZipOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZipOperation.h 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 11.02.15. 6 | // 7 | // 8 | 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @class ClutchBundle; 12 | 13 | @interface ZipOperation : NSOperation 14 | 15 | - (nullable instancetype)initWithApplication:(nullable ClutchBundle *)clutchBundle NS_DESIGNATED_INITIALIZER; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Clutch/ZipOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZipOperation.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 11.02.15. 6 | // 7 | // 8 | 9 | #import "ZipOperation.h" 10 | #import "ClutchBundle.h" 11 | #import "ClutchPrint.h" 12 | #import "ZipArchive.h" 13 | 14 | @interface ZipOperation () { 15 | ClutchBundle *_application; 16 | BOOL _executing, _finished; 17 | ZipArchive *_archive; 18 | } 19 | @end 20 | 21 | @implementation ZipOperation 22 | 23 | - (nullable instancetype)init { 24 | return [self initWithApplication:nil]; 25 | } 26 | 27 | - (nullable instancetype)initWithApplication:(nullable ClutchBundle *)application { 28 | if (!application) { 29 | return nil; 30 | } 31 | 32 | if ((self = [super init])) { 33 | _executing = NO; 34 | _finished = NO; 35 | _application = application; 36 | } 37 | return self; 38 | } 39 | 40 | - (BOOL)isAsynchronous { 41 | return YES; 42 | } 43 | 44 | - (BOOL)isConcurrent { 45 | return YES; 46 | } 47 | 48 | - (BOOL)isExecuting { 49 | return _executing; 50 | } 51 | 52 | - (BOOL)isFinished { 53 | return _finished; 54 | } 55 | 56 | - (void)start { 57 | // Always check for cancellation before launching the task. 58 | if (self.isCancelled) { 59 | // Must move the operation to the finished state if it is canceled. 60 | [self willChangeValueForKey:@"isFinished"]; 61 | _finished = YES; 62 | [self didChangeValueForKey:@"isFinished"]; 63 | return; 64 | } 65 | 66 | self.completionBlock = ^{ 67 | }; 68 | 69 | // If the operation is not canceled, begin executing the task. 70 | [self willChangeValueForKey:@"isExecuting"]; 71 | [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; 72 | _executing = YES; 73 | [self didChangeValueForKey:@"isExecuting"]; 74 | } 75 | 76 | - (void)main { 77 | NSFileManager *fm = [NSFileManager defaultManager]; 78 | 79 | @try { 80 | NSString *_zipFilename = _application.zipFilename; 81 | NSString *_localPrefix = _application.zipPrefix; 82 | 83 | KJPrint(@"Zipping %@", _application.bundleURL.lastPathComponent); 84 | 85 | if (!_archive) { 86 | _archive = [[ZipArchive alloc] init]; 87 | [_archive CreateZipFile2:[_application.workingPath stringByAppendingPathComponent:_zipFilename] 88 | append:_application.parentBundle != nil]; 89 | } 90 | 91 | if (!_application.parentBundle && 92 | [fm fileExistsAtPath:[_application.bundleContainerURL URLByAppendingPathComponent:@"iTunesArtwork" 93 | isDirectory:NO] 94 | .path]) { 95 | [_archive addFileToZip:[_application.bundleContainerURL URLByAppendingPathComponent:@"iTunesArtwork" 96 | isDirectory:NO] 97 | .path 98 | newname:@"iTunesArtwork"]; 99 | } 100 | 101 | if (!_application.parentBundle && 102 | [fm fileExistsAtPath:[_application.bundleContainerURL URLByAppendingPathComponent:@"iTunesMetadata.plist" 103 | isDirectory:NO] 104 | .path]) { 105 | 106 | // skip iTunesMetadata 107 | // [_archive addFileToZip:[_application.bundleContainerURL 108 | // URLByAppendingPathComponent:@"iTunesMetadata.plist" isDirectory:NO].path 109 | // newname:@"iTunesMetadata.plist"]; 110 | } 111 | 112 | NSDirectoryEnumerator *dirEnumerator = [fm enumeratorAtURL:_application.bundleURL 113 | includingPropertiesForKeys:@[ NSURLNameKey, NSURLIsDirectoryKey ] 114 | options:0 115 | errorHandler:^BOOL(NSURL *url, NSError *error) { 116 | CLUTCH_UNUSED(url); 117 | CLUTCH_UNUSED(error); 118 | return YES; 119 | }]; 120 | 121 | for (NSURL *theURL in dirEnumerator) { 122 | NSNumber *isDirectory; 123 | [theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; 124 | 125 | NSString *_localPath = 126 | [theURL.path stringByReplacingOccurrencesOfString:_application.bundleContainerURL.path withString:@""]; 127 | 128 | NSArray *_pathComponents = _localPath.pathComponents; 129 | 130 | if (_pathComponents.count > 2) { 131 | if ([_pathComponents[2] isEqualToString:@"SC_Info"] || [_pathComponents[2] isEqualToString:@"Watch"] || 132 | [_pathComponents[2] isEqualToString:@"Frameworks"] || 133 | [_pathComponents[2] isEqualToString:@"PlugIns"]) { 134 | if ([_localPath.lastPathComponent hasPrefix:@"libswift"] && 135 | ![_localPath.pathExtension caseInsensitiveCompare:@"dylib"]) { 136 | [_archive addFileToZip:theURL.path 137 | newname:[_localPrefix stringByAppendingPathComponent:_localPath]]; 138 | KJDebug(@"Added %@", [_localPrefix stringByAppendingPathComponent:_localPath]); 139 | } else { 140 | KJDebug(@"Skipping %@", [_localPrefix stringByAppendingPathComponent:_localPath]); 141 | } 142 | } else if (!isDirectory.boolValue && 143 | ![_pathComponents[2] isEqualToString:_application.executablePath.lastPathComponent]) { 144 | [_archive addFileToZip:theURL.path 145 | newname:[_localPrefix stringByAppendingPathComponent:_localPath]]; 146 | KJDebug(@"Added %@", [_localPrefix stringByAppendingPathComponent:_localPath]); 147 | } else { 148 | KJDebug(@"Skipping %@", [_localPrefix stringByAppendingPathComponent:_localPath]); 149 | } 150 | } else { 151 | KJDebug(@"Skipping %@", _localPath); 152 | } 153 | } 154 | 155 | [_archive CloseZipFile2]; 156 | 157 | // Do the main work of the operation here. 158 | [self completeOperation]; 159 | } @catch (...) { 160 | // Do not rethrow exceptions. 161 | } 162 | } 163 | 164 | - (void)completeOperation { 165 | [self willChangeValueForKey:@"isFinished"]; 166 | [self willChangeValueForKey:@"isExecuting"]; 167 | _executing = NO; 168 | _finished = YES; 169 | [self didChangeValueForKey:@"isExecuting"]; 170 | [self didChangeValueForKey:@"isFinished"]; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /Clutch/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Clutch 4 | // 5 | // Created by Anton Titkov on 09.02.15. 6 | // Copyright (c) 2015 AppAddict. All rights reserved. 7 | // 8 | 9 | #import "ClutchCommands.h" 10 | #import "ClutchPrint.h" 11 | #import "FrameworkLoader.h" 12 | #import "KJApplicationManager.h" 13 | #import "NSTask.h" 14 | #import "sha1.h" 15 | #import 16 | #import 17 | #include 18 | 19 | struct timeval gStart; 20 | 21 | NSInteger diff_ms(struct timeval t1, struct timeval t2); 22 | NSInteger diff_ms(struct timeval t1, struct timeval t2) { 23 | return (((t1.tv_sec - t2.tv_sec) * 1000000) + (t1.tv_usec - t2.tv_usec)) / 1000; 24 | } 25 | 26 | void listApps(void); 27 | void listApps(void) { 28 | KJApplicationManager *_manager = [[KJApplicationManager alloc] init]; 29 | 30 | NSArray *installedApps = _manager.installedApps.allValues; 31 | KJPrint(@"Installed apps:"); 32 | 33 | NSUInteger count; 34 | NSString *space; 35 | for (Application *_app in installedApps) { 36 | count = [installedApps indexOfObject:_app] + 1; 37 | if (count < 10) { 38 | space = @" "; 39 | } else if (count < 100) { 40 | space = @" "; 41 | } 42 | 43 | KJPrint(@"%d: %@%@ <%@>", count, space, _app.displayName, _app.bundleIdentifier); 44 | } 45 | } 46 | 47 | int main(int argc, const char *argv[]) { 48 | CLUTCH_UNUSED(argc); 49 | CLUTCH_UNUSED(argv); 50 | 51 | @autoreleasepool { 52 | if (getuid() != 0) { // Clutch needs to be root user to run 53 | KJPrint(@"Clutch needs to be run as the root user, please change user and rerun."); 54 | return 0; 55 | } 56 | 57 | if (SYSTEM_VERSION_LESS_THAN(NSFoundationVersionNumber_iOS_8_0)) { 58 | KJPrint(@"You need iOS 8.0+ to use Clutch %@", CLUTCH_VERSION); 59 | return 0; 60 | } 61 | 62 | BOOL dumpedFramework = NO; 63 | BOOL successfullyDumpedFramework = NO; 64 | NSString *_selectedOption = @""; 65 | NSString *_selectedBundleID; 66 | 67 | NSArray *arguments = [NSProcessInfo processInfo].arguments; 68 | 69 | ClutchCommands *commands = [[ClutchCommands alloc] initWithArguments:arguments]; 70 | 71 | NSArray *values; 72 | 73 | if (commands.commands) { 74 | for (ClutchCommand *command in commands.commands) { 75 | NSLog(@"command: %@", command.commandDescription); 76 | // Switch flags 77 | switch (command.flag) { 78 | case ClutchCommandFlagArgumentRequired: { 79 | values = commands.values; 80 | } 81 | default: 82 | break; 83 | } 84 | 85 | // Switch optionals 86 | switch (command.option) { 87 | case ClutchCommandOptionVerbose: 88 | KJPrintCurrentLogLevel = KJPrintLogLevelVerbose; 89 | break; 90 | case ClutchCommandOptionDebug: 91 | KJPrintCurrentLogLevel = KJPrintLogLevelDebug; 92 | default: 93 | break; 94 | } 95 | 96 | switch (command.option) { 97 | case ClutchCommandOptionNone: { 98 | KJPrint(@"%@", commands.helpString); 99 | break; 100 | } 101 | case ClutchCommandOptionFrameworkDump: { 102 | NSArray *args = [NSProcessInfo processInfo].arguments; 103 | 104 | if (([args[1] isEqualToString:@"--fmwk-dump"] || [args[1] isEqualToString:@"-f"]) && 105 | (args.count == 13)) { 106 | FrameworkLoader *fmwk = [FrameworkLoader new]; 107 | 108 | fmwk.binPath = args[2]; 109 | fmwk.dumpPath = args[3]; 110 | fmwk.pages = (uint32_t)[args[4] intValue]; 111 | fmwk.ncmds = (uint32_t)[args[5] intValue]; 112 | fmwk.offset = (uint32_t)[args[6] intValue]; 113 | fmwk.bID = args[7]; 114 | fmwk.hashOffset = (uint32_t)[args[8] intValue]; 115 | fmwk.codesign_begin = (uint32_t)[args[9] intValue]; 116 | fmwk.cryptsize = (uint32_t)[args[10] intValue]; 117 | fmwk.cryptoff = (uint32_t)[args[11] intValue]; 118 | fmwk.cryptlc_offset = (uint32_t)[args[12] intValue]; 119 | fmwk.dumpSize = fmwk.cryptoff + fmwk.cryptsize; 120 | 121 | BOOL result = successfullyDumpedFramework = [fmwk dumpBinary]; 122 | 123 | if (result) { 124 | KJPrint(@"Successfully dumped framework %@!", fmwk.binPath.lastPathComponent); 125 | 126 | return 0; 127 | } else { 128 | KJPrint(@"Failed to dump framework %@ :(", fmwk.binPath.lastPathComponent); 129 | return 1; 130 | } 131 | 132 | } else if (args.count != 13) { 133 | KJPrint(@"Incorrect amount of arguments - see source if you're using this."); 134 | } 135 | 136 | break; 137 | } 138 | case ClutchCommandOptionBinaryDump: 139 | case ClutchCommandOptionDump: { 140 | NSDictionary *_installedApps = [[[KJApplicationManager alloc] init] cachedApplications]; 141 | NSArray *_installedArray = _installedApps.allValues; 142 | 143 | for (NSString *selection in values) { 144 | NSUInteger key; 145 | Application *_selectedApp; 146 | 147 | if (!(key = (NSUInteger)selection.integerValue)) { 148 | KJDebug(@"using bundle identifier"); 149 | if (_installedApps[selection] == nil) { 150 | KJPrint(@"Couldn't find installed app with bundle identifier: %@", 151 | _selectedBundleID); 152 | return 1; 153 | } else { 154 | _selectedApp = _installedApps[selection]; 155 | } 156 | } else { 157 | KJDebug(@"using number"); 158 | key = key - 1; 159 | 160 | if (key > _installedArray.count) { 161 | KJPrint(@"Couldn't find app with corresponding number!?!"); 162 | return 1; 163 | } 164 | _selectedApp = _installedArray[key]; 165 | } 166 | 167 | if (!_selectedApp) { 168 | KJPrint(@"Couldn't find installed app"); 169 | return 1; 170 | } 171 | 172 | KJPrintVerbose(@"Now dumping %@", _selectedApp.bundleIdentifier); 173 | 174 | if (_selectedApp.hasAppleWatchApp) { 175 | KJPrint(@"%@ contains watchOS 2 compatible application. It's not possible to dump " 176 | @"watchOS 2 apps with Clutch %@ at this moment.", 177 | _selectedApp.bundleIdentifier, 178 | CLUTCH_VERSION); 179 | } 180 | 181 | gettimeofday(&gStart, NULL); 182 | if (![_selectedApp dumpToDirectoryURL:nil 183 | onlyBinaries:[_selectedOption isEqualToString:@"binary-dump"]]) { 184 | return 1; 185 | } 186 | } 187 | break; 188 | } 189 | case ClutchCommandOptionPrintInstalled: { 190 | listApps(); 191 | break; 192 | } 193 | case ClutchCommandOptionClean: { 194 | [[NSFileManager defaultManager] removeItemAtPath:@"/var/tmp/clutch" error:nil]; 195 | [[NSFileManager defaultManager] createDirectoryAtPath:@"/var/tmp/clutch" 196 | withIntermediateDirectories:YES 197 | attributes:nil 198 | error:nil]; 199 | break; 200 | } 201 | case ClutchCommandOptionVersion: { 202 | KJPrint(CLUTCH_VERSION); 203 | break; 204 | } 205 | case ClutchCommandOptionHelp: { 206 | KJPrint(@"%@", commands.helpString); 207 | break; 208 | } 209 | default: 210 | // no command found. 211 | break; 212 | } 213 | } 214 | } 215 | 216 | if (dumpedFramework) { 217 | fclose(stdin); 218 | fclose(stdout); 219 | fclose(stderr); 220 | 221 | if (successfullyDumpedFramework) { 222 | return 0; 223 | } 224 | return 1; 225 | } 226 | } 227 | 228 | return 0; 229 | } 230 | 231 | void sha1(uint8_t *hash, uint8_t *data, size_t size); 232 | 233 | void sha1(uint8_t *hash, uint8_t *data, size_t size) { 234 | SHA1Context context; 235 | SHA1Reset(&context); 236 | SHA1Input(&context, data, (unsigned)size); 237 | SHA1Result(&context, hash); 238 | } 239 | 240 | void exit_with_errno(int err, const char *prefix); 241 | void exit_with_errno(int err, const char *prefix) { 242 | if (err) { 243 | fprintf(stderr, "%s%s", prefix ? prefix : "", strerror(err)); 244 | fclose(stdout); 245 | fclose(stderr); 246 | exit(err); 247 | } 248 | } 249 | 250 | void _kill(pid_t pid); 251 | void _kill(pid_t pid) { 252 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { 253 | int result; 254 | waitpid(pid, &result, 0); 255 | waitpid(pid, &result, 0); 256 | kill(pid, SIGKILL); // just in case; 257 | }); 258 | 259 | kill(pid, SIGCONT); 260 | kill(pid, SIGKILL); 261 | } 262 | -------------------------------------------------------------------------------- /Clutch/move_and_sign.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$PROJECT_DIR" 4 | ! [ -d build ] && mkdir build 5 | cp "$BUILT_PRODUCTS_DIR/$EXECUTABLE_PATH" "build/" 6 | codesign -fs- --entitlements 'Clutch/Clutch.entitlements' "build/$EXECUTABLE_NAME" 7 | -------------------------------------------------------------------------------- /Clutch/optool-defines.h: -------------------------------------------------------------------------------- 1 | // 2 | // defines.h 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #import 29 | 30 | #ifndef CPU_TYPE_ARM64 31 | #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) 32 | #endif 33 | 34 | #define CPU(CPUTYPE) \ 35 | ({ \ 36 | const char *c = ""; \ 37 | if (CPUTYPE == CPU_TYPE_I386) \ 38 | c = "x86"; \ 39 | if (CPUTYPE == CPU_TYPE_X86_64) \ 40 | c = "x86_64"; \ 41 | if (CPUTYPE == CPU_TYPE_ARM) \ 42 | c = "arm"; \ 43 | if (CPUTYPE == CPU_TYPE_ARM64) \ 44 | c = "arm64"; \ 45 | c; \ 46 | }) 47 | 48 | #define LC(LOADCOMMAND) \ 49 | ({ \ 50 | const char *c = ""; \ 51 | if (LOADCOMMAND == LC_REEXPORT_DYLIB) \ 52 | c = "LC_REEXPORT_DYLIB"; \ 53 | else if (LOADCOMMAND == LC_LOAD_WEAK_DYLIB) \ 54 | c = "LC_LOAD_WEAK_DYLIB"; \ 55 | else if (LOADCOMMAND == LC_LOAD_UPWARD_DYLIB) \ 56 | c = "LC_LOAD_UPWARD_DYLIB"; \ 57 | else if (LOADCOMMAND == LC_LOAD_DYLIB) \ 58 | c = "LC_LOAD_DYLIB"; \ 59 | c; \ 60 | }) 61 | 62 | #define COMMAND(str) \ 63 | ({ \ 64 | uint32_t cmd = -1; \ 65 | if ([str isEqualToString:@"reexport"]) \ 66 | cmd = LC_REEXPORT_DYLIB; \ 67 | else if ([str isEqualToString:@"weak"]) \ 68 | cmd = LC_LOAD_WEAK_DYLIB; \ 69 | else if ([str isEqualToString:@"upward"]) \ 70 | cmd = LC_LOAD_UPWARD_DYLIB; \ 71 | else if ([str isEqualToString:@"load"]) \ 72 | cmd = LC_LOAD_DYLIB; \ 73 | cmd; \ 74 | }) 75 | 76 | // we pass around this header which includes some extra information 77 | // and a 32-bit header which we used for both 32-bit and 64-bit files 78 | // since the 64-bit just adds an extra field to the end which we don't need 79 | 80 | typedef struct thin_header { 81 | uint32_t offset; 82 | uint32_t size; 83 | struct mach_header header; 84 | } thin_header; 85 | 86 | typedef NS_ENUM(int, OPError) { 87 | OPErrorNone = 0, 88 | OPErrorRead = 1, // failed to read target path 89 | OPErrorIncompatibleBinary = 2, // couldn't find x86 or x86_64 architecture in binary 90 | OPErrorStripFailure = 3, // failed to strip codesignature 91 | OPErrorWriteFailure = 4, // failed to write data to final output path 92 | OPErrorNoBackup = 5, // no backup to restore 93 | OPErrorRemovalFailure = 6, // failed to remove executable during restore 94 | OPErrorMoveFailure = 7, // failed to move backup to correct location 95 | OPErrorNoEntries = 8, // cant remove dylib entries because they dont exist 96 | OPErrorInsertFailure = 9, // failed to insert load command 97 | OPErrorInvalidLoadCommand = 10, // user provided an unnacceptable load command string 98 | OPErrorResignFailure = 11, // codesign failed for some reason 99 | OPErrorBackupFailure = 12, // failed to write backup 100 | OPErrorInvalidArguments = 13 // bad arguments 101 | }; 102 | -------------------------------------------------------------------------------- /Clutch/optool-headers.h: -------------------------------------------------------------------------------- 1 | // 2 | // headers.h 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #import "optool-defines.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | thin_header headerAtOffset(NSData *binary, uint32_t offset); 33 | thin_header *headersFromBinary(thin_header *headers, NSData *binary, uint32_t *amount); 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /Clutch/optool-headers.m: -------------------------------------------------------------------------------- 1 | // 2 | // headers.m 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #import "optool-headers.h" 29 | #import "NSData+Reading.h" 30 | #import 31 | #import 32 | 33 | thin_header headerAtOffset(NSData *binary, uint32_t offset) { 34 | thin_header macho; 35 | macho.offset = offset; 36 | macho.header = *(struct mach_header *)((char *)binary.bytes + offset); 37 | if (macho.header.magic == MH_MAGIC || macho.header.magic == MH_CIGAM) { 38 | macho.size = sizeof(struct mach_header); 39 | } else { 40 | macho.size = sizeof(struct mach_header_64); 41 | } 42 | if (macho.header.cputype != CPU_TYPE_X86_64 && macho.header.cputype != CPU_TYPE_I386 && 43 | macho.header.cputype != CPU_TYPE_ARM && macho.header.cputype != CPU_TYPE_ARM64) { 44 | macho.size = 0; 45 | } 46 | 47 | return macho; 48 | } 49 | 50 | thin_header *headersFromBinary(thin_header *headers, NSData *binary, uint32_t *amount) { 51 | // In a MachO/FAT binary the first 4 bytes is a magic number 52 | // which gives details about the type of binary it is 53 | // CIGAM and co. mean the target binary has a byte order 54 | // in reverse relation to the host machine so we have to swap the bytes 55 | uint32_t magic = [binary intAtOffset:0]; 56 | bool shouldSwap = magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; 57 | #define SWAP(NUM) (shouldSwap ? CFSwapInt32((uint32_t)NUM) : (uint32_t)NUM) 58 | 59 | uint32_t numArchs = 0; 60 | 61 | // a FAT file is basically a collection of thin MachO binaries 62 | if (magic == FAT_CIGAM || magic == FAT_MAGIC) { 63 | 64 | // WE GOT A FAT ONE 65 | struct fat_header fat = *(struct fat_header *)binary.bytes; 66 | fat.nfat_arch = SWAP(fat.nfat_arch); 67 | int offset = sizeof(struct fat_header); 68 | 69 | // Loop through the architectures within the FAT binary to find 70 | // a thin macho header that we can work with (x86 or x86_64) 71 | for (unsigned int i = 0; i < fat.nfat_arch; i++) { 72 | struct fat_arch arch; 73 | arch = *(struct fat_arch *)((char *)binary.bytes + offset); 74 | arch.cputype = (int)SWAP(arch.cputype); 75 | arch.offset = (uint32_t)SWAP(arch.offset); 76 | 77 | thin_header macho = headerAtOffset(binary, arch.offset); 78 | if (macho.size > 0) { 79 | 80 | headers[numArchs] = macho; 81 | numArchs++; 82 | } 83 | 84 | offset += sizeof(struct fat_arch); 85 | } 86 | // The binary is thin, meaning it contains only one architecture 87 | } else if (magic == MH_MAGIC || magic == MH_MAGIC_64) { 88 | thin_header macho = headerAtOffset(binary, 0); 89 | if (macho.size > 0) { 90 | 91 | numArchs++; 92 | headers[0] = macho; 93 | } 94 | } 95 | 96 | *amount = numArchs; 97 | 98 | return headers; 99 | } 100 | -------------------------------------------------------------------------------- /Clutch/optool-operations.h: -------------------------------------------------------------------------------- 1 | // 2 | // operations.h 3 | // optool 4 | // Copyright (c) 2014, Alex Zielenski 5 | // All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are met: 9 | // 10 | // * Redistributions of source code must retain the above copyright notice, this 11 | // list of conditions and the following disclaimer. 12 | // 13 | // * Redistributions in binary form must reproduce the above copyright notice, 14 | // this list of conditions and the following disclaimer in the documentation 15 | // and/or other materials provided with the distribution. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | #import "optool-defines.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | BOOL stripCodeSignatureFromBinary(NSMutableData *binary, thin_header macho, BOOL soft); 33 | BOOL removeLoadEntryFromBinary(NSMutableData *binary, thin_header macho, NSString *payload); 34 | BOOL binaryHasLoadCommandForDylib(NSMutableData *binary, NSString *dylib, uint32_t *lastOffset, thin_header macho); 35 | BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, thin_header macho, uint32_t type); 36 | BOOL removeASLRFromBinary(NSMutableData *binary, thin_header macho); 37 | 38 | // Clutch 2 39 | BOOL insertRPATHIntoBinary(NSString *dylibPath, NSMutableData *binary, thin_header macho); 40 | BOOL removeRPATHFromBinary(NSMutableData *binary, thin_header macho); 41 | BOOL binaryHasRPATH(NSMutableData *binary, NSString *dylib, uint32_t *lastOffset, thin_header macho); 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /Clutch/optool.h: -------------------------------------------------------------------------------- 1 | #import "optool-headers.h" 2 | #import "optool-operations.h" 3 | -------------------------------------------------------------------------------- /Clutch/scinfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | Generates SC_Info keys (.sinf and .supp) 3 | see http://hackulo.us/wiki/SC_Info 4 | */ 5 | 6 | void *create_atom(char *name, uint32_t len, void *content); 7 | void *coalesced_atom(uint32_t amount, uint32_t name, ...); 8 | void *combine_atoms(char *name, int amount, ...); 9 | void *generate_sinf(int appid, char *person_name, int vendorID); 10 | void *generate_supp(uint32_t *suppsize); 11 | -------------------------------------------------------------------------------- /Clutch/scinfo.m: -------------------------------------------------------------------------------- 1 | /* 2 | Generates SC_Info keys (.sinf and .supp) 3 | see https://archive.fo/PNfJ5 4 | */ 5 | 6 | #import "scinfo.h" 7 | 8 | // create a SINF atom 9 | void *create_atom(char *name, uint32_t len, void *content) { 10 | uint32_t atomsize = len + 8; 11 | void *buf = malloc(atomsize); 12 | 13 | atomsize = CFSwapInt32(atomsize); 14 | 15 | memcpy(buf, &atomsize, 4); // copy atomsize 16 | memcpy((char *)buf + 4, name, 4); // copy atom name 17 | memcpy((char *)buf + 8, content, len); // copy atom content 18 | 19 | return buf; 20 | } 21 | 22 | void *coalesced_atom(uint32_t amount, uint32_t name, ...) { 23 | va_list vl; 24 | va_start(vl, name); 25 | uint32_t atomsize = 8 + amount * 8; // 8 bytes per field, 8 bytes for atom header 26 | 27 | amount = amount * 2; 28 | 29 | void *buf = malloc(atomsize); 30 | 31 | atomsize = CFSwapInt32(atomsize); 32 | 33 | memcpy(buf, &atomsize, 4); // copy atom size 34 | memcpy((char *)buf + 4, &name, 4); // copy name 35 | 36 | uint32_t *curpos = (uint32_t *)buf + 2; 37 | 38 | for (uint32_t i = 0; i < amount; i++) { 39 | uint32_t arg = va_arg(vl, uint32_t); 40 | if (i % 2) { 41 | arg = CFSwapInt32(arg); 42 | } 43 | 44 | memcpy(curpos, &arg, 4); 45 | curpos += 1; 46 | } 47 | 48 | va_end(vl); 49 | return buf; 50 | } 51 | 52 | void *combine_atoms(char *name, int amount, ...) { 53 | // combine all of the given atoms 54 | va_list vl; 55 | va_start(vl, amount); 56 | 57 | uint32_t atomsize = 8; 58 | 59 | for (int i = 0; i < amount; i++) { 60 | void *atom = va_arg(vl, void *); 61 | atomsize += CFSwapInt32(*(uint32_t *)atom); 62 | } 63 | 64 | void *buf = malloc(atomsize + 8); 65 | atomsize = CFSwapInt32(atomsize); 66 | 67 | memcpy(buf, &atomsize, 4); // atom size 68 | memcpy((char *)buf + 4, name, 4); // atom name 69 | 70 | va_start(vl, amount); 71 | 72 | char *curloc = (char *)buf + 8; 73 | 74 | for (int i = 0; i < amount; i++) { 75 | void *atom = va_arg(vl, void *); 76 | uint32_t content = CFSwapInt32(*(uint32_t *)atom); 77 | 78 | memcpy(curloc, atom, content); 79 | curloc += content; 80 | } 81 | 82 | va_end(vl); 83 | 84 | return buf; 85 | } 86 | 87 | void *generate_supp(uint32_t *suppsize) { 88 | // create a random 100612 byte file 89 | uint32_t *supp = malloc(100612); 90 | *suppsize = 100612; 91 | 92 | for (int i = 0; i < 25153; i++) { 93 | supp[i] = arc4random(); 94 | } 95 | 96 | return supp; 97 | } 98 | 99 | // generate a fake .sinf file 100 | void *generate_sinf(int appid, char *person_name, int vendorID) { 101 | // sinf.schi.righ is an atom of several misc. fields related to the application 102 | void *fakerigh = coalesced_atom(10, 103 | *(uint32_t *)"righ", 104 | *(uint32_t *)"veID", 105 | vendorID, // ? 106 | *(uint32_t *)"plat", 107 | 0, // platform? 108 | *(uint32_t *)"aver", 109 | 0x1010100, // app version? 110 | *(uint32_t *)"tran", 111 | arc4random(), // transaction ID 112 | *(uint32_t *)"song", 113 | appid, // appid 114 | *(uint32_t *)"tool", 115 | 1345598006, // itunes build? 116 | *(uint32_t *)"medi", 117 | 0x80, // ? 118 | *(uint32_t *)"mode", 119 | 0x0, // ? 120 | 0x0, 121 | 0x0, // padding 122 | 0x0, 123 | 0x0 // padding 124 | ); 125 | 126 | // sinf.schi.user is a userid. make it random 127 | uint32_t fakeuserid = arc4random(); 128 | void *user = create_atom("user", 4, &fakeuserid); 129 | 130 | // sinf.schi.key is a key index in itunes, a small integer. let's pick 1 131 | uint32_t fakekeyindex = CFSwapInt32(1); 132 | void *key = create_atom("key ", 4, &fakekeyindex); 133 | 134 | // sinf.schi.iviv is a 128bit IV for the private key 135 | uint32_t *iviv = malloc(16); 136 | for (int i = 0; i < 4; i++) { 137 | iviv[i] = arc4random(); 138 | } 139 | 140 | void *fakeiviv = create_atom("iviv", 16, iviv); 141 | free(iviv); 142 | 143 | // sinf.schi.name is a 256 byte name of the iTunes account owner 144 | char *name = malloc(256); 145 | 146 | memset(name, 0x0, 256); 147 | memcpy(name, person_name, strlen(person_name)); 148 | 149 | void *fakename = create_atom("name", 256, name); 150 | free(name); 151 | 152 | // sinf.schi.priv is a 440 byte private key 153 | uint32_t *fakepriv = malloc(440); 154 | 155 | for (int i = 0; i < 110; i++) { 156 | if (i > 107) { 157 | fakepriv[i] = 0; 158 | } else { 159 | fakepriv[i] = arc4random(); 160 | } 161 | } 162 | 163 | void *priv = create_atom("priv", 440, fakepriv); 164 | free(fakepriv); 165 | 166 | void *schi = combine_atoms("schi", 6, user, key, fakeiviv, fakerigh, fakename, priv); 167 | free(user); 168 | free(key); 169 | free(fakeiviv); 170 | free(fakerigh); 171 | free(fakename); 172 | free(priv); 173 | 174 | // sinf.frma = "game" 175 | void *frma = create_atom("frma", 4, "game"); 176 | 177 | // sinf.schm = "\x00000000itun\x00000000" 178 | void *schm = create_atom("schm", 12, "\x00\x00\x00\x00itun\x00\x00\x00\x00"); 179 | 180 | uint32_t *sign = malloc(128); 181 | 182 | for (int i = 0; i < 32; i++) { 183 | sign[i] = arc4random(); 184 | } 185 | 186 | void *fakesign = create_atom("sign", 128, sign); 187 | free(sign); 188 | 189 | void *sinf = combine_atoms("sinf", 4, frma, schm, schi, fakesign); 190 | free(frma); 191 | free(schm); 192 | free(schi); 193 | free(fakesign); 194 | 195 | return sinf; 196 | } 197 | -------------------------------------------------------------------------------- /Clutch/sha1.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sha1.h 3 | * 4 | * Description: 5 | * This is the header file for code which implements the Secure 6 | * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published 7 | * April 17, 1995. 8 | * 9 | * Many of the variable names in this code, especially the 10 | * single character names, were used because those were the names 11 | * used in the publication. 12 | * 13 | * Please read the file sha1.c for more information. 14 | * 15 | */ 16 | 17 | #ifndef _SHA1_H_ 18 | #define _SHA1_H_ 19 | 20 | #include 21 | 22 | #ifndef _SHA_enum_ 23 | #define _SHA_enum_ 24 | enum { 25 | shaSuccess = 0, 26 | shaNull, /* Null pointer parameter */ 27 | shaInputTooLong, /* input data too long */ 28 | shaStateError /* called Input after Result */ 29 | }; 30 | #endif 31 | #define SHA1HashSize 20 32 | 33 | /* 34 | * This structure will hold context information for the SHA-1 35 | * hashing operation 36 | */ 37 | typedef struct SHA1Context { 38 | uint32_t Intermediate_Hash[SHA1HashSize / 4]; /* Message Digest */ 39 | 40 | uint32_t Length_Low; /* Message length in bits */ 41 | uint32_t Length_High; /* Message length in bits */ 42 | 43 | /* Index into message block array */ 44 | int_least16_t Message_Block_Index; 45 | uint8_t Message_Block[64]; /* 512-bit message blocks */ 46 | 47 | int Computed; /* Is the digest computed? */ 48 | int Corrupted; /* Is the message digest corrupted? */ 49 | } SHA1Context; 50 | 51 | /* 52 | * Function Prototypes 53 | */ 54 | 55 | int SHA1Reset(SHA1Context *); 56 | int SHA1Input(SHA1Context *, const uint8_t *, unsigned int); 57 | int SHA1Result(SHA1Context *, uint8_t Message_Digest[SHA1HashSize]); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /LSApplicationProxy.h: -------------------------------------------------------------------------------- 1 | @interface LSApplicationProxy : NSObject 2 | 3 | @property (nonatomic, readonly) NSString *itemName; 4 | @property (nonatomic, readonly) NSString *localizedName; 5 | @property (nonatomic, readonly) NSString *applicationType; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clutch 2 | 3 | *Clutch* is a high-speed iOS decryption tool. Clutch supports the iPhone, iPod Touch, and iPad as well as all iOS version, architecture types, and most binaries. **Clutch is meant only for educational purposes and security research.** 4 | 5 | Clutch requires a jailbroken iOS device with version 8.0 or greater. 6 | 7 | # Usage 8 | 9 | ``` 10 | Clutch [OPTIONS] 11 | -b --binary-dump Only dump binary files from specified bundleID 12 | -d --dump Dump specified bundleID into .ipa file 13 | -i --print-installed Print installed application 14 | --clean Clean /var/tmp/clutch directory 15 | --version Display version and exit 16 | -? --help Display this help and exit 17 | ``` 18 | 19 | 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 with `ulimit -n 512` (default is 256). 20 | 21 | 22 | # Building 23 | 24 | ## Requirements 25 | 26 | * Xcode (install from [App Store](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) or from [Apple's developer site](http://adcdownload.apple.com/Developer_Tools/Xcode_8.2.1/Xcode_8.2.1.xip)) 27 | * Xcode command line tools: `xcode-select --install` (or from [Apple's developer site](http://adcdownload.apple.com/Developer_Tools/Command_Line_Tools_macOS_10.12_for_Xcode_8.2/Command_Line_Tools_macOS_10.12_for_Xcode_8.2.dmg)) 28 | 29 | ## Disable SDK code signing requirement 30 | 31 | ```sh 32 | killall Xcode 33 | cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist ~/ 34 | sudo /usr/libexec/PlistBuddy -c "Set :DefaultProperties:CODE_SIGNING_REQUIRED NO" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist 35 | sudo /usr/libexec/PlistBuddy -c "Set :DefaultProperties:AD_HOC_CODE_SIGNING_ALLOWED YES" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist 36 | ``` 37 | 38 | Note that if you update Xcode you may need to run these commands again. 39 | 40 | ## Compiling 41 | 42 | ### Xcode 43 | 44 | ```sh 45 | xcodebuild clean build 46 | ``` 47 | 48 | ### CMake 49 | 50 | ```sh 51 | mkdir build 52 | cd build 53 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../cmake/iphoneos.toolchain.cmake .. 54 | make -j$(sysctl -n hw.logicalcpu) 55 | ``` 56 | 57 | ## Installation 58 | 59 | After building, a copy of the binary named `Clutch` is placed in the build directory. Copy this to your device: 60 | 61 | ```sh 62 | scp ./build/Clutch root@:/usr/bin/Clutch 63 | ``` 64 | 65 | If you are using [iproxy](http://iphonedevwiki.net/index.php/SSH_Over_USB), use this line (replace `2222` with a different port if necessary): 66 | 67 | ```sh 68 | scp -P 2222 ./build/Clutch root@localhost:/usr/bin/Clutch 69 | ``` 70 | 71 | When you SSH into your device, run `Clutch`. 72 | 73 | If you are using the [unc0ver jailbreak](https://www.theiphonewiki.com/wiki/Unc0ver), you may need to run the following: 74 | 75 | ```sh 76 | inject /usr/bin/Clutch 77 | ``` 78 | 79 | # Licenses 80 | 81 | Clutch uses the following libraries under their respective licenses. 82 | 83 | * [optool](https://github.com/alexzielenski/optool) by Alex Zielenski 84 | * [ZipArchive](https://github.com/mattconnolly/ZipArchive/) by Matt Connolly, Edward Patel, et al. 85 | * [MiniZip](http://www.winimage.com/zLibDll/minizip.html) by Gilles Vollant and Mathias Svensson. 86 | 87 | # Thanks 88 | 89 | Clutch would not be what it is without these people: 90 | 91 | * dissident - The original creator of Clutch (pre 1.2.6) 92 | * Nighthawk - Code contributor (pre 1.2.6) 93 | * Rastignac - Inspiration and genius 94 | * TheSexyPenguin - Inspiration 95 | 96 | # Contributors 97 | 98 | * [iT0ny](https://github.com/iT0ny) 99 | * [ttwj](https://github.com/ttwj) 100 | * [NinjaLikesCheez](https://github.com/NinjaLikesCheez) 101 | * [Tatsh](https://github.com/Tatsh) 102 | * [C0deH4cker](https://github.com/C0deH4cker) 103 | * [DoubleDoughnut](https://github.com/DoubleDoughnut) 104 | * [iD70my](https://github.com/iD70my) 105 | * [OdNairy](https://github.com/OdNairy) 106 | * [palmerc](https://github.com/palmerc) 107 | * [jack980517](https://github.com/jack980517) 108 | 109 | # Copyright 110 | 111 | © [Kim Jong-Cracks](http://cracksby.kim) 1819-2017 112 | -------------------------------------------------------------------------------- /cmake/AddFramework.cmake: -------------------------------------------------------------------------------- 1 | macro(AddFramework TARGET NAME) 2 | find_library(FRAMEWORK_${NAME} 3 | NAMES ${NAME} 4 | PATHS ${CMAKE_OSX_SYSROOT}/System/Library 5 | PATH_SUFFIXES Frameworks CMAKE_FIND_FRAMEWORK only 6 | NO_DEFAULT_PATH) 7 | if(${FRAMEWORK_${NAME}} STREQUAL FRAMEWORK_${NAME}-NOTFOUND) 8 | message(ERROR ": Framework ${NAME} not found") 9 | else() 10 | target_link_libraries(${TARGET} ${FRAMEWORK_${NAME}}) 11 | message(STATUS "Framework ${NAME} found at ${FRAMEWORK_${NAME}}") 12 | endif() 13 | endmacro(AddFramework) 14 | -------------------------------------------------------------------------------- /cmake/DebugUtils.cmake: -------------------------------------------------------------------------------- 1 | function(DumpVariables) 2 | set(options MARK HCF) 3 | set(oneValueArgs) 4 | set(multiValueArgs VARS) 5 | cmake_parse_arguments(DumpVariables 6 | "${options}" 7 | "${oneValueArgs}" 8 | "${multiValueArgs}" 9 | ${ARGN}) 10 | 11 | get_cmake_property(_CMAKE_VARIABLES VARIABLES) 12 | set(MARK ${DumpVariables_MARK}) 13 | set(HCF ${DumpVariables_HCF}) 14 | if(DumpVariables_VARS) 15 | set(VARS "${DumpVariables_VARS}") 16 | else() 17 | set(VARS ${_CMAKE_VARIABLES}) 18 | endif() 19 | 20 | if(MARK) 21 | message("") 22 | message("#####################") 23 | message("DumpVariables - BEGIN") 24 | endif() 25 | 26 | foreach(_CMAKE_VARIABLE ${_CMAKE_VARIABLES}) 27 | if(_CMAKE_VARIABLE IN_LIST VARS) 28 | message(STATUS "${_CMAKE_VARIABLE}=${${_CMAKE_VARIABLE}}") 29 | endif() 30 | endforeach(_CMAKE_VARIABLE) 31 | 32 | if(MARK) 33 | message("DumpVariables - END") 34 | message("#####################") 35 | message("") 36 | endif() 37 | 38 | if(HCF) 39 | message(FATAL_ERROR "HCF - DumpVariables") 40 | endif() 41 | endfunction(DumpVariables) 42 | -------------------------------------------------------------------------------- /cmake/iphoneos.toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Darwin) 2 | set(SDK iphoneos CACHE STRING "") 3 | set(CMAKE_OSX_ARCHITECTURES armv7 armv7s arm64 CACHE STRING "") 4 | 5 | execute_process(COMMAND xcrun --sdk ${SDK} --find clang 6 | OUTPUT_VARIABLE CMAKE_C_COMPILER 7 | OUTPUT_STRIP_TRAILING_WHITESPACE) 8 | set(CMAKE_C_COMPILER ${CMAKE_C_COMPILER} CACHE PATH "clang executable") 9 | 10 | execute_process(COMMAND xcrun --sdk ${SDK} --find clang++ 11 | OUTPUT_VARIABLE CMAKE_CXX_COMPILER 12 | OUTPUT_STRIP_TRAILING_WHITESPACE) 13 | set(CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} CACHE PATH "clang++ executable") 14 | 15 | execute_process(COMMAND xcrun --sdk ${SDK} --find swiftc 16 | OUTPUT_VARIABLE CMAKE_Swift_COMPILER 17 | OUTPUT_STRIP_TRAILING_WHITESPACE) 18 | set(CMAKE_Swift_COMPILER ${CMAKE_Swift_COMPILER} CACHE PATH "swiftc executable") 19 | 20 | execute_process(COMMAND xcrun --sdk ${SDK} --find libtool 21 | OUTPUT_VARIABLE CMAKE_LIBTOOL 22 | OUTPUT_STRIP_TRAILING_WHITESPACE) 23 | set(CMAKE_LIBTOOL ${CMAKE_LIBTOOL} CACHE PATH "libtool executable") 24 | 25 | execute_process(COMMAND xcrun --sdk ${SDK} --find strip 26 | OUTPUT_VARIABLE CMAKE_STRIP 27 | OUTPUT_STRIP_TRAILING_WHITESPACE) 28 | set(CMAKE_STRIP ${CMAKE_STRIP} CACHE PATH "strip executable") 29 | 30 | execute_process(COMMAND xcrun --sdk ${SDK} --show-sdk-path 31 | OUTPUT_VARIABLE CMAKE_OSX_SYSROOT 32 | OUTPUT_STRIP_TRAILING_WHITESPACE) 33 | set(CMAKE_OSX_SYSROOT ${CMAKE_OSX_SYSROOT} CACHE PATH "sysroot") 34 | 35 | set(CMAKE_CXX_COMPILER_WORKS TRUE) 36 | set(CMAKE_C_COMPILER_WORKS TRUE) 37 | set(CMAKE_Swift_COMPILER_WORKS TRUE) 38 | 39 | set(CMAKE_FIND_FRAMEWORK FIRST) 40 | set(CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks) 41 | 42 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 43 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 44 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 45 | --------------------------------------------------------------------------------