├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── optool.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── optool ├── NSData+Reading.h ├── NSData+Reading.m ├── defines.h ├── headers.h ├── headers.m ├── main.m ├── operations.h └── operations.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "optool/FSArgumentParser"] 2 | path = optool/FSArgumentParser 3 | url = https://github.com/mysteriouspants/ArgumentParser.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Alex Zielenski 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | optool 2 | ====== 3 | 4 | optool is a tool which interfaces with MachO binaries in order to insert/remove load commands, strip code signatures, resign, and remove aslr. Below is its help. 5 | 6 | ``` 7 | optool v0.1 8 | 9 | USAGE: 10 | install -c -p -t [-o=] [-b] [--resign] In 11 | serts an LC_LOAD command into the target binary which points to the payload. T 12 | his may render some executables unusable. 13 | 14 | uninstall -p -t [-o=] [-b] [--resign] Removes any L 15 | C_LOAD commands which point to a given payload from the target binary. This ma 16 | y render some executables unusable. 17 | 18 | strip [-w] -t Removes a code signature load command from the given bi 19 | nary. 20 | 21 | restore -t Restores any backup made on the target by this tool. 22 | 23 | aslr -t [-o=] [-b] [--resign] Removes an ASLR flag from the m 24 | acho header if it exists. This may render some executables unusable 25 | 26 | 27 | OPTIONS: 28 | [-w --weak] Used with the STRIP command to weakly remove the signature. Withou 29 | t this, the code signature is replaced with null bytes on the binary and its L 30 | OAD command is removed. 31 | 32 | [--resign] Try to repair the code signature after any operations are done. Thi 33 | s may render some executables unusable. 34 | 35 | -t|--target Required of all commands to specify the target executable 36 | to modify 37 | 38 | -p|--payload Required of the INSTALL and UNINSTALL commands to speci 39 | fy the path to a DYLIB to point the LOAD command to 40 | 41 | [-c --command] Specify which type of load command to use in INSTALL. Can be re 42 | export for LC_REEXPORT_DYLIB, weak for LC_LOAD_WEAK_DYLIB, upward for LC_LOAD_ 43 | UPWARD_DYLIB, or load for LC_LOAD_DYLIB 44 | 45 | [-b --backup] Backup the executable to a suffixed path (in the form of _backup 46 | .BUNDLEVERSION) 47 | 48 | [-h --help] Show this message 49 | 50 | 51 | (C) 2014 Alexander S. Zielenski. Licensed under BSD 52 | ``` 53 | 54 | License 55 | === 56 | 57 | optool is licensed under BSD. Below is the license reproduced in its entirety: 58 | 59 | ``` 60 | Copyright (c) 2014, Alex Zielenski 61 | All rights reserved. 62 | 63 | Redistribution and use in source and binary forms, with or without 64 | modification, are permitted provided that the following conditions are met: 65 | 66 | * Redistributions of source code must retain the above copyright notice, this 67 | list of conditions and the following disclaimer. 68 | 69 | * Redistributions in binary form must reproduce the above copyright notice, 70 | this list of conditions and the following disclaimer in the documentation 71 | and/or other materials provided with the distribution. 72 | 73 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 74 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 75 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 76 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 77 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 78 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 79 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 80 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 81 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 82 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /optool.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA0D296D1990A2E300EC5F74 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FA0D296C1990A2E300EC5F74 /* main.m */; }; 11 | FA0D297A1990A32500EC5F74 /* headers.m in Sources */ = {isa = PBXBuildFile; fileRef = FA0D29751990A32500EC5F74 /* headers.m */; }; 12 | FA0D297B1990A32500EC5F74 /* NSData+Reading.m in Sources */ = {isa = PBXBuildFile; fileRef = FA0D29771990A32500EC5F74 /* NSData+Reading.m */; }; 13 | FA0D297C1990A32500EC5F74 /* operations.m in Sources */ = {isa = PBXBuildFile; fileRef = FA0D29791990A32500EC5F74 /* operations.m */; }; 14 | FA0D2A0C1990A45300EC5F74 /* libArgumentParser-Static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0D2A0B1990A44C00EC5F74 /* libArgumentParser-Static.a */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | FA0D2A001990A44C00EC5F74 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 21 | proxyType = 2; 22 | remoteGlobalIDString = DACF2EC414FD3B2F0097C754; 23 | remoteInfo = desc; 24 | }; 25 | FA0D2A021990A44C00EC5F74 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 28 | proxyType = 2; 29 | remoteGlobalIDString = DACF2EDF14FD3B630097C754; 30 | remoteInfo = "long-desc"; 31 | }; 32 | FA0D2A041990A44C00EC5F74 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 35 | proxyType = 2; 36 | remoteGlobalIDString = DACF2EF214FD3B7A0097C754; 37 | remoteInfo = spiffy; 38 | }; 39 | FA0D2A061990A44C00EC5F74 /* PBXContainerItemProxy */ = { 40 | isa = PBXContainerItemProxy; 41 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 42 | proxyType = 2; 43 | remoteGlobalIDString = DA00447A155D94FF0028A012; 44 | remoteInfo = "ArgumentParser-Dynamic"; 45 | }; 46 | FA0D2A0A1990A44C00EC5F74 /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 49 | proxyType = 2; 50 | remoteGlobalIDString = DA87348918970AB800113896; 51 | remoteInfo = "ArgumentParser-Static"; 52 | }; 53 | FA0D2A0D1990A45700EC5F74 /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 56 | proxyType = 1; 57 | remoteGlobalIDString = DA87348818970AB800113896; 58 | remoteInfo = "ArgumentParser-Static"; 59 | }; 60 | FA1C058B1B3DB170000E0591 /* PBXContainerItemProxy */ = { 61 | isa = PBXContainerItemProxy; 62 | containerPortal = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 63 | proxyType = 2; 64 | remoteGlobalIDString = DA01E2FF1A8471E20012BCAE; 65 | remoteInfo = "ArgumentParser-Dynamic Tests"; 66 | }; 67 | /* End PBXContainerItemProxy section */ 68 | 69 | /* Begin PBXCopyFilesBuildPhase section */ 70 | FA0D29671990A2E300EC5F74 /* CopyFiles */ = { 71 | isa = PBXCopyFilesBuildPhase; 72 | buildActionMask = 2147483647; 73 | dstPath = /usr/share/man/man1/; 74 | dstSubfolderSpec = 0; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 1; 78 | }; 79 | /* End PBXCopyFilesBuildPhase section */ 80 | 81 | /* Begin PBXFileReference section */ 82 | FA0D29691990A2E300EC5F74 /* optool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = optool; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | FA0D296C1990A2E300EC5F74 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 84 | FA0D29731990A32500EC5F74 /* defines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = defines.h; sourceTree = ""; }; 85 | FA0D29741990A32500EC5F74 /* headers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = headers.h; sourceTree = ""; }; 86 | FA0D29751990A32500EC5F74 /* headers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = headers.m; sourceTree = ""; }; 87 | FA0D29761990A32500EC5F74 /* NSData+Reading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Reading.h"; sourceTree = ""; }; 88 | FA0D29771990A32500EC5F74 /* NSData+Reading.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Reading.m"; sourceTree = ""; }; 89 | FA0D29781990A32500EC5F74 /* operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = operations.h; sourceTree = ""; }; 90 | FA0D29791990A32500EC5F74 /* operations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = operations.m; sourceTree = ""; }; 91 | FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArgumentParser.xcodeproj; path = FSArgumentParser/ArgumentParser.xcodeproj; sourceTree = ""; }; 92 | /* End PBXFileReference section */ 93 | 94 | /* Begin PBXFrameworksBuildPhase section */ 95 | FA0D29661990A2E300EC5F74 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | FA0D2A0C1990A45300EC5F74 /* libArgumentParser-Static.a in Frameworks */, 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | /* End PBXFrameworksBuildPhase section */ 104 | 105 | /* Begin PBXGroup section */ 106 | FA0D29601990A2E300EC5F74 = { 107 | isa = PBXGroup; 108 | children = ( 109 | FA0D296B1990A2E300EC5F74 /* optool */, 110 | FA0D296A1990A2E300EC5F74 /* Products */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | FA0D296A1990A2E300EC5F74 /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | FA0D29691990A2E300EC5F74 /* optool */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | FA0D296B1990A2E300EC5F74 /* optool */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */, 126 | FA0D29731990A32500EC5F74 /* defines.h */, 127 | FA0D29741990A32500EC5F74 /* headers.h */, 128 | FA0D29751990A32500EC5F74 /* headers.m */, 129 | FA0D29761990A32500EC5F74 /* NSData+Reading.h */, 130 | FA0D29771990A32500EC5F74 /* NSData+Reading.m */, 131 | FA0D29781990A32500EC5F74 /* operations.h */, 132 | FA0D29791990A32500EC5F74 /* operations.m */, 133 | FA0D296C1990A2E300EC5F74 /* main.m */, 134 | ); 135 | path = optool; 136 | sourceTree = ""; 137 | }; 138 | FA0D29F81990A44C00EC5F74 /* Products */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | FA0D2A011990A44C00EC5F74 /* desc */, 142 | FA0D2A031990A44C00EC5F74 /* long-desc */, 143 | FA0D2A051990A44C00EC5F74 /* spiffy */, 144 | FA0D2A071990A44C00EC5F74 /* ArgumentParser-Dynamic.dylib */, 145 | FA0D2A0B1990A44C00EC5F74 /* libArgumentParser-Static.a */, 146 | FA1C058C1B3DB170000E0591 /* ArgumentParser-Dynamic Tests.xctest */, 147 | ); 148 | name = Products; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXNativeTarget section */ 154 | FA0D29681990A2E300EC5F74 /* optool */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = FA0D29701990A2E300EC5F74 /* Build configuration list for PBXNativeTarget "optool" */; 157 | buildPhases = ( 158 | FA0D29651990A2E300EC5F74 /* Sources */, 159 | FA0D29661990A2E300EC5F74 /* Frameworks */, 160 | FA0D29671990A2E300EC5F74 /* CopyFiles */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | FA0D2A0E1990A45700EC5F74 /* PBXTargetDependency */, 166 | ); 167 | name = optool; 168 | productName = optool; 169 | productReference = FA0D29691990A2E300EC5F74 /* optool */; 170 | productType = "com.apple.product-type.tool"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | FA0D29611990A2E300EC5F74 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | LastUpgradeCheck = 0820; 179 | ORGANIZATIONNAME = "Alex Zielenski"; 180 | TargetAttributes = { 181 | FA0D29681990A2E300EC5F74 = { 182 | CreatedOnToolsVersion = 6.0; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = FA0D29641990A2E300EC5F74 /* Build configuration list for PBXProject "optool" */; 187 | compatibilityVersion = "Xcode 3.2"; 188 | developmentRegion = English; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | ); 193 | mainGroup = FA0D29601990A2E300EC5F74; 194 | productRefGroup = FA0D296A1990A2E300EC5F74 /* Products */; 195 | projectDirPath = ""; 196 | projectReferences = ( 197 | { 198 | ProductGroup = FA0D29F81990A44C00EC5F74 /* Products */; 199 | ProjectRef = FA0D29F71990A44C00EC5F74 /* ArgumentParser.xcodeproj */; 200 | }, 201 | ); 202 | projectRoot = ""; 203 | targets = ( 204 | FA0D29681990A2E300EC5F74 /* optool */, 205 | ); 206 | }; 207 | /* End PBXProject section */ 208 | 209 | /* Begin PBXReferenceProxy section */ 210 | FA0D2A011990A44C00EC5F74 /* desc */ = { 211 | isa = PBXReferenceProxy; 212 | fileType = "compiled.mach-o.executable"; 213 | path = desc; 214 | remoteRef = FA0D2A001990A44C00EC5F74 /* PBXContainerItemProxy */; 215 | sourceTree = BUILT_PRODUCTS_DIR; 216 | }; 217 | FA0D2A031990A44C00EC5F74 /* long-desc */ = { 218 | isa = PBXReferenceProxy; 219 | fileType = "compiled.mach-o.executable"; 220 | path = "long-desc"; 221 | remoteRef = FA0D2A021990A44C00EC5F74 /* PBXContainerItemProxy */; 222 | sourceTree = BUILT_PRODUCTS_DIR; 223 | }; 224 | FA0D2A051990A44C00EC5F74 /* spiffy */ = { 225 | isa = PBXReferenceProxy; 226 | fileType = "compiled.mach-o.executable"; 227 | path = spiffy; 228 | remoteRef = FA0D2A041990A44C00EC5F74 /* PBXContainerItemProxy */; 229 | sourceTree = BUILT_PRODUCTS_DIR; 230 | }; 231 | FA0D2A071990A44C00EC5F74 /* ArgumentParser-Dynamic.dylib */ = { 232 | isa = PBXReferenceProxy; 233 | fileType = "compiled.mach-o.dylib"; 234 | path = "ArgumentParser-Dynamic.dylib"; 235 | remoteRef = FA0D2A061990A44C00EC5F74 /* PBXContainerItemProxy */; 236 | sourceTree = BUILT_PRODUCTS_DIR; 237 | }; 238 | FA0D2A0B1990A44C00EC5F74 /* libArgumentParser-Static.a */ = { 239 | isa = PBXReferenceProxy; 240 | fileType = archive.ar; 241 | path = "libArgumentParser-Static.a"; 242 | remoteRef = FA0D2A0A1990A44C00EC5F74 /* PBXContainerItemProxy */; 243 | sourceTree = BUILT_PRODUCTS_DIR; 244 | }; 245 | FA1C058C1B3DB170000E0591 /* ArgumentParser-Dynamic Tests.xctest */ = { 246 | isa = PBXReferenceProxy; 247 | fileType = wrapper.cfbundle; 248 | path = "ArgumentParser-Dynamic Tests.xctest"; 249 | remoteRef = FA1C058B1B3DB170000E0591 /* PBXContainerItemProxy */; 250 | sourceTree = BUILT_PRODUCTS_DIR; 251 | }; 252 | /* End PBXReferenceProxy section */ 253 | 254 | /* Begin PBXSourcesBuildPhase section */ 255 | FA0D29651990A2E300EC5F74 /* Sources */ = { 256 | isa = PBXSourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | FA0D297B1990A32500EC5F74 /* NSData+Reading.m in Sources */, 260 | FA0D297A1990A32500EC5F74 /* headers.m in Sources */, 261 | FA0D296D1990A2E300EC5F74 /* main.m in Sources */, 262 | FA0D297C1990A32500EC5F74 /* operations.m in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin PBXTargetDependency section */ 269 | FA0D2A0E1990A45700EC5F74 /* PBXTargetDependency */ = { 270 | isa = PBXTargetDependency; 271 | name = "ArgumentParser-Static"; 272 | targetProxy = FA0D2A0D1990A45700EC5F74 /* PBXContainerItemProxy */; 273 | }; 274 | /* End PBXTargetDependency section */ 275 | 276 | /* Begin XCBuildConfiguration section */ 277 | FA0D296E1990A2E300EC5F74 /* Debug */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_EMPTY_BODY = YES; 289 | CLANG_WARN_ENUM_CONVERSION = YES; 290 | CLANG_WARN_INFINITE_RECURSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | COPY_PHASE_STRIP = NO; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_TESTABILITY = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_DYNAMIC_NO_PIC = NO; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_OPTIMIZATION_LEVEL = 0; 303 | GCC_PREPROCESSOR_DEFINITIONS = ( 304 | "DEBUG=1", 305 | "$(inherited)", 306 | ); 307 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | HEADER_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "$(SRCROOT)/../FSArgumentParser", 317 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 318 | ); 319 | MACOSX_DEPLOYMENT_TARGET = 10.9; 320 | MTL_ENABLE_DEBUG_INFO = YES; 321 | ONLY_ACTIVE_ARCH = YES; 322 | OTHER_LDFLAGS = "-ObjC"; 323 | SDKROOT = macosx; 324 | }; 325 | name = Debug; 326 | }; 327 | FA0D296F1990A2E300EC5F74 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 332 | CLANG_CXX_LIBRARY = "libc++"; 333 | CLANG_ENABLE_MODULES = YES; 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INFINITE_RECURSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | COPY_PHASE_STRIP = YES; 347 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 348 | ENABLE_NS_ASSERTIONS = NO; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | GCC_C_LANGUAGE_STANDARD = gnu99; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 354 | GCC_WARN_UNDECLARED_SELECTOR = YES; 355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 356 | GCC_WARN_UNUSED_FUNCTION = YES; 357 | GCC_WARN_UNUSED_VARIABLE = YES; 358 | HEADER_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "$(SRCROOT)/../FSArgumentParser", 361 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 362 | ); 363 | MACOSX_DEPLOYMENT_TARGET = 10.9; 364 | MTL_ENABLE_DEBUG_INFO = NO; 365 | OTHER_LDFLAGS = "-ObjC"; 366 | SDKROOT = macosx; 367 | }; 368 | name = Release; 369 | }; 370 | FA0D29711990A2E300EC5F74 /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | CURRENT_PROJECT_VERSION = 0.2; 374 | DYLIB_COMPATIBILITY_VERSION = 0.1; 375 | DYLIB_CURRENT_VERSION = 0.2; 376 | OTHER_LDFLAGS = "-ObjC"; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SDKROOT = macosx; 379 | }; 380 | name = Debug; 381 | }; 382 | FA0D29721990A2E300EC5F74 /* Release */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | CURRENT_PROJECT_VERSION = 0.2; 386 | DYLIB_COMPATIBILITY_VERSION = 0.1; 387 | DYLIB_CURRENT_VERSION = 0.2; 388 | OTHER_LDFLAGS = "-ObjC"; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SDKROOT = macosx; 391 | }; 392 | name = Release; 393 | }; 394 | /* End XCBuildConfiguration section */ 395 | 396 | /* Begin XCConfigurationList section */ 397 | FA0D29641990A2E300EC5F74 /* Build configuration list for PBXProject "optool" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | FA0D296E1990A2E300EC5F74 /* Debug */, 401 | FA0D296F1990A2E300EC5F74 /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | FA0D29701990A2E300EC5F74 /* Build configuration list for PBXNativeTarget "optool" */ = { 407 | isa = XCConfigurationList; 408 | buildConfigurations = ( 409 | FA0D29711990A2E300EC5F74 /* Debug */, 410 | FA0D29721990A2E300EC5F74 /* Release */, 411 | ); 412 | defaultConfigurationIsVisible = 0; 413 | defaultConfigurationName = Release; 414 | }; 415 | /* End XCConfigurationList section */ 416 | }; 417 | rootObject = FA0D29611990A2E300EC5F74 /* Project object */; 418 | } 419 | -------------------------------------------------------------------------------- /optool.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /optool/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 | #import 29 | 30 | @interface NSData (Reading) 31 | 32 | - (NSUInteger)currentOffset; 33 | - (void)setCurrentOffset:(NSUInteger)offset; 34 | 35 | - (uint8_t)nextByte; 36 | - (uint8_t)byteAtOffset:(NSUInteger)offset; 37 | 38 | - (uint16_t)nextShort; 39 | - (uint16_t)shortAtOffset:(NSUInteger)offset; 40 | 41 | - (uint32_t)nextInt; 42 | - (uint32_t)intAtOffset:(NSUInteger)offset; 43 | 44 | - (uint64_t)nextLong; 45 | - (uint64_t)longAtOffset:(NSUInteger)offset; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /optool/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 | { 36 | NSNumber *value = objc_getAssociatedObject(self, &OFFSET); 37 | return value.unsignedIntegerValue; 38 | } 39 | 40 | - (void)setCurrentOffset:(NSUInteger)offset 41 | { 42 | [self willChangeValueForKey:@"currentOffset"]; 43 | objc_setAssociatedObject(self, &OFFSET, [NSNumber numberWithUnsignedInteger:offset], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 44 | [self didChangeValueForKey:@"currentOffset"]; 45 | } 46 | 47 | - (uint8_t)nextByte 48 | { 49 | uint8_t nextByte = [self byteAtOffset:self.currentOffset]; 50 | self.currentOffset += sizeof(uint8_t); 51 | return nextByte; 52 | } 53 | 54 | - (uint8_t)byteAtOffset:(NSUInteger)offset 55 | { 56 | uint8_t result; 57 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 58 | return result; 59 | } 60 | 61 | - (uint16_t)nextShort 62 | { 63 | uint16_t nextShort = [self shortAtOffset:self.currentOffset]; 64 | self.currentOffset += sizeof(uint16_t); 65 | return nextShort; 66 | } 67 | 68 | - (uint16_t)shortAtOffset:(NSUInteger)offset 69 | { 70 | uint16_t result; 71 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 72 | return result; 73 | } 74 | 75 | - (uint32_t)nextInt 76 | { 77 | uint32_t nextInt = [self intAtOffset:self.currentOffset]; 78 | self.currentOffset += sizeof(uint32_t); 79 | return nextInt; 80 | } 81 | 82 | - (uint32_t)intAtOffset:(NSUInteger)offset 83 | { 84 | uint32_t result; 85 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 86 | return result; 87 | } 88 | 89 | - (uint64_t)nextLong 90 | { 91 | uint64_t nextLong = [self longAtOffset:self.currentOffset]; 92 | self.currentOffset += sizeof(uint64_t); 93 | return nextLong; 94 | } 95 | 96 | - (uint64_t)longAtOffset:(NSUInteger)offset; 97 | { 98 | uint64_t result; 99 | [self getBytes:&result range:NSMakeRange(offset, sizeof(result))]; 100 | return result; 101 | } 102 | 103 | @end 104 | 105 | @implementation NSMutableData (ByteAdditions) 106 | 107 | - (void)appendByte:(uint8_t)value 108 | { 109 | [self appendBytes:&value length:sizeof(value)]; 110 | } 111 | 112 | - (void)appendShort:(uint16_t)value 113 | { 114 | uint16_t swap = CFSwapInt16HostToLittle(value); 115 | [self appendBytes:&swap length:sizeof(swap)]; 116 | } 117 | 118 | - (void)appendInt:(uint32_t)value 119 | { 120 | uint32_t swap = CFSwapInt32HostToLittle(value); 121 | [self appendBytes:&swap length:sizeof(swap)]; 122 | } 123 | 124 | - (void)appendLong:(uint64_t)value; 125 | { 126 | uint64_t swap = CFSwapInt64HostToLittle(value); 127 | [self appendBytes:&swap length:sizeof(swap)]; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /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 | 29 | #import 30 | 31 | #ifndef CPU_TYPE_ARM64 32 | #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) 33 | #endif 34 | 35 | #define LOG(fmt, args...) printf(fmt "\n", ##args) 36 | 37 | #define CPU(CPUTYPE) ({ \ 38 | const char *c = ""; \ 39 | if (CPUTYPE == CPU_TYPE_I386) \ 40 | c = "x86"; \ 41 | if (CPUTYPE == CPU_TYPE_X86_64) \ 42 | c = "x86_64"; \ 43 | if (CPUTYPE == CPU_TYPE_ARM) \ 44 | c = "arm"; \ 45 | if (CPUTYPE == CPU_TYPE_ARM64) \ 46 | c = "arm64"; \ 47 | c; \ 48 | }) 49 | 50 | #define LC(LOADCOMMAND) ({ \ 51 | const char *c = ""; \ 52 | if (LOADCOMMAND == LC_REEXPORT_DYLIB) \ 53 | c = "LC_REEXPORT_DYLIB";\ 54 | else if (LOADCOMMAND == LC_LOAD_WEAK_DYLIB) \ 55 | c = "LC_LOAD_WEAK_DYLIB";\ 56 | else if (LOADCOMMAND == LC_LOAD_UPWARD_DYLIB) \ 57 | c = "LC_LOAD_UPWARD_DYLIB";\ 58 | else if (LOADCOMMAND == LC_LOAD_DYLIB) \ 59 | c = "LC_LOAD_DYLIB";\ 60 | c;\ 61 | }) 62 | 63 | #define COMMAND(str) ({ \ 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 | struct thin_header { 80 | uint32_t offset; 81 | uint32_t size; 82 | struct mach_header header; 83 | }; 84 | 85 | typedef NS_ENUM(int, OPError) { 86 | OPErrorNone = 0, 87 | OPErrorRead = 1, // failed to read target path 88 | OPErrorIncompatibleBinary = 2, // couldn't find x86 or x86_64 architecture in binary 89 | OPErrorStripFailure = 3, // failed to strip codesignature 90 | OPErrorWriteFailure = 4, // failed to write data to final output path 91 | OPErrorNoBackup = 5, // no backup to restore 92 | OPErrorRemovalFailure = 6, // failed to remove executable during restore 93 | OPErrorMoveFailure = 7, // failed to move backup to correct location 94 | OPErrorNoEntries = 8, // cant remove dylib entries because they dont exist 95 | OPErrorInsertFailure = 9, // failed to insert load command 96 | OPErrorInvalidLoadCommand = 10, // user provided an unnacceptable load command string 97 | OPErrorResignFailure = 11, // codesign failed for some reason 98 | OPErrorBackupFailure = 12, // failed to write backup 99 | OPErrorInvalidArguments = 13, // bad arguments 100 | OPErrorBadULEB = 14, // uleb while reading binding ordinals is in an invalid format 101 | OPErrorULEBEncodeFailure = 15 // failed to encode a uleb within specified length requirements 102 | }; -------------------------------------------------------------------------------- /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 29 | #import "defines.h" 30 | 31 | struct thin_header headerAtOffset(NSData *binary, uint32_t offset); 32 | struct thin_header *headersFromBinary(struct thin_header *headers, NSData *binary, uint32_t *amount); -------------------------------------------------------------------------------- /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 "headers.h" 29 | #import 30 | #import 31 | #import "NSData+Reading.h" 32 | 33 | struct thin_header headerAtOffset(NSData *binary, uint32_t offset) { 34 | struct thin_header macho; 35 | macho.offset = offset; 36 | macho.header = *(struct mach_header *)(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 && macho.header.cputype != CPU_TYPE_ARM && macho.header.cputype != CPU_TYPE_ARM64){ 43 | macho.size = 0; 44 | } 45 | 46 | return macho; 47 | } 48 | 49 | struct thin_header *headersFromBinary(struct thin_header *headers, NSData *binary, uint32_t *amount) { 50 | // In a MachO/FAT binary the first 4 bytes is a magic number 51 | // which gives details about the type of binary it is 52 | // CIGAM and co. mean the target binary has a byte order 53 | // in reverse relation to the host machine so we have to swap the bytes 54 | uint32_t magic = [binary intAtOffset:0]; 55 | bool shouldSwap = magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; 56 | #define SWAP(NUM) shouldSwap ? CFSwapInt32(NUM) : NUM 57 | 58 | uint32_t numArchs = 0; 59 | 60 | // a FAT file is basically a collection of thin MachO binaries 61 | if (magic == FAT_CIGAM || magic == FAT_MAGIC) { 62 | LOG("Found FAT Header"); 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 (int i = 0; i < fat.nfat_arch; i++) { 72 | struct fat_arch arch; 73 | arch = *(struct fat_arch *)([binary bytes] + offset); 74 | arch.cputype = SWAP(arch.cputype); 75 | arch.offset = SWAP(arch.offset); 76 | 77 | struct thin_header macho = headerAtOffset(binary, arch.offset); 78 | if (macho.size > 0) { 79 | LOG("Found thin header..."); 80 | 81 | headers[numArchs] = macho; 82 | numArchs++; 83 | } 84 | 85 | offset += sizeof(struct fat_arch); 86 | } 87 | // The binary is thin, meaning it contains only one architecture 88 | } else if (magic == MH_MAGIC || magic == MH_MAGIC_64) { 89 | struct thin_header macho = headerAtOffset(binary, 0); 90 | if (macho.size > 0) { 91 | LOG("Found thin header..."); 92 | 93 | numArchs++; 94 | headers[0] = macho; 95 | } 96 | 97 | } else { 98 | LOG("No headers found."); 99 | } 100 | 101 | *amount = numArchs; 102 | 103 | return headers; 104 | } 105 | -------------------------------------------------------------------------------- /optool/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.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 | 29 | #import 30 | #import "FSArgumentParser/ArgumentParser/XPMArguments.h" 31 | #import "FSArgumentParser/ArgumentParser/NSString+Indenter.h" 32 | #import 33 | #import 34 | #import "defines.h" 35 | #import "headers.h" 36 | #import "operations.h" 37 | 38 | int main(int argc, const char * argv[]) { 39 | @autoreleasepool { 40 | BOOL showHelp = NO; 41 | 42 | // Flags 43 | XPMArgumentSignature *weak = [XPMArgumentSignature argumentSignatureWithFormat:@"[-w --weak]"]; 44 | XPMArgumentSignature *resign = [XPMArgumentSignature argumentSignatureWithFormat:@"[--resign]"]; 45 | XPMArgumentSignature *target = [XPMArgumentSignature argumentSignatureWithFormat:@"[-t --target]={1,1}"]; 46 | XPMArgumentSignature *payload = [XPMArgumentSignature argumentSignatureWithFormat:@"[-p --payload]={1,1}"]; 47 | XPMArgumentSignature *command = [XPMArgumentSignature argumentSignatureWithFormat:@"[-c --command]={1,1}"]; 48 | XPMArgumentSignature *backup = [XPMArgumentSignature argumentSignatureWithFormat:@"[-b --backup]"]; 49 | XPMArgumentSignature *output = [XPMArgumentSignature argumentSignatureWithFormat:@"[-o --output]={1,1}"]; 50 | XPMArgumentSignature *help = [XPMArgumentSignature argumentSignatureWithFormat:@"[-h --help]"]; 51 | 52 | // Actions 53 | XPMArgumentSignature *strip = [XPMArgumentSignature argumentSignatureWithFormat:@"[s strip]"]; 54 | XPMArgumentSignature *restore = [XPMArgumentSignature argumentSignatureWithFormat:@"[r restore]"]; 55 | XPMArgumentSignature *install = [XPMArgumentSignature argumentSignatureWithFormat:@"[i install]"]; 56 | XPMArgumentSignature *rename = [XPMArgumentSignature argumentSignatureWithFormat:@"[r rename]={1,2}"]; 57 | XPMArgumentSignature *uninstall = [XPMArgumentSignature argumentSignatureWithFormat:@"[u uninstall]"]; 58 | XPMArgumentSignature *aslr = [XPMArgumentSignature argumentSignatureWithFormat:@"[a aslr]"]; 59 | XPMArgumentSignature *unrestrict = [XPMArgumentSignature argumentSignatureWithFormat:@"[c unrestrict]"]; 60 | 61 | [strip setInjectedSignatures:[NSSet setWithObjects:target, weak, nil]]; 62 | [restore setInjectedSignatures:[NSSet setWithObjects:target, nil]]; 63 | [install setInjectedSignatures:[NSSet setWithObjects:target, payload, nil]]; 64 | [uninstall setInjectedSignatures:[NSSet setWithObjects:target, payload, nil]]; 65 | [aslr setInjectedSignatures:[NSSet setWithObjects:target, nil]]; 66 | [unrestrict setInjectedSignatures:[NSSet setWithObjects:target, weak, nil]]; 67 | [rename setInjectedSignatures:[NSSet setWithObjects:target, nil]]; 68 | 69 | [weak setInjectedSignatures:[NSSet setWithObjects:strip, unrestrict, nil]]; 70 | [payload setInjectedSignatures:[NSSet setWithObjects:install, uninstall, nil]]; 71 | [command setInjectedSignatures:[NSSet setWithObjects:install, nil]]; 72 | 73 | XPMArgumentPackage *package = [[NSProcessInfo processInfo] xpmargs_parseArgumentsWithSignatures:@[resign, command, strip, restore, install, uninstall, output, backup, aslr, help, unrestrict, rename]]; 74 | 75 | NSString *targetPath = [package firstObjectForSignature:target]; 76 | if (!targetPath || [package unknownSwitches].count > 0 || [package booleanValueForSignature:help]) { 77 | // Invalid arguments, show help 78 | showHelp = YES; 79 | goto help; 80 | } 81 | 82 | // Start a new scope so ARC releases everything before the goto 83 | { 84 | 85 | NSBundle *bundle = [NSBundle bundleWithPath:targetPath]; 86 | NSString *executablePath = [[bundle.executablePath ?: targetPath stringByExpandingTildeInPath] stringByResolvingSymlinksInPath]; 87 | NSString *backupPath = ({ 88 | NSString *bkp = [executablePath stringByAppendingString:@"_backup"]; 89 | if (bundle) { 90 | NSString *vers = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; 91 | if (vers) 92 | bkp = [bkp stringByAppendingPathExtension:vers]; 93 | 94 | } 95 | bkp; 96 | });; 97 | 98 | NSString *outputPath = [package firstObjectForSignature:output] ?: executablePath; 99 | NSString *dylibPath = [package firstObjectForSignature:payload]; 100 | 101 | NSFileManager *manager = [NSFileManager defaultManager]; 102 | 103 | if ([package booleanValueForSignature:restore]) { 104 | LOG("Attempting to restore %s...", backupPath.UTF8String); 105 | 106 | if ([manager fileExistsAtPath:backupPath]) { 107 | NSError *error = nil; 108 | if ([manager removeItemAtPath:executablePath error:&error]) { 109 | if ([manager moveItemAtPath:backupPath toPath:executablePath error:&error]) { 110 | LOG("Successfully restored backup"); 111 | return OPErrorNone; 112 | } 113 | LOG("Failed to move backup to correct location"); 114 | return OPErrorMoveFailure; 115 | } 116 | 117 | LOG("Failed to remove executable. (%s)", error.localizedDescription.UTF8String); 118 | return OPErrorRemovalFailure; 119 | } 120 | 121 | LOG("No backup for that target exists"); 122 | return OPErrorNoBackup; 123 | } 124 | 125 | NSData *originalData = [NSData dataWithContentsOfFile:executablePath]; 126 | NSMutableData *binary = originalData.mutableCopy; 127 | if (!binary) 128 | return OPErrorRead; 129 | 130 | struct thin_header headers[4]; 131 | uint32_t numHeaders = 0; 132 | headersFromBinary(headers, binary, &numHeaders); 133 | 134 | if (numHeaders == 0) { 135 | LOG("No compatible architecture found"); 136 | return OPErrorIncompatibleBinary; 137 | } 138 | 139 | // Loop through all of the thin headers we found for each operation 140 | for (uint32_t i = 0; i < numHeaders; i++) { 141 | struct thin_header macho = headers[i]; 142 | 143 | if ([package booleanValueForSignature:strip]) { 144 | if (!stripCodeSignatureFromBinary(binary, macho, [package booleanValueForSignature:weak])) { 145 | LOG("Found no code signature to strip"); 146 | return OPErrorStripFailure; 147 | } else { 148 | LOG("Successfully stripped code signatures"); 149 | } 150 | } else if ([package booleanValueForSignature:unrestrict]) { 151 | if (!unrestrictBinary(binary, macho, [package booleanValueForSignature:weak])) { 152 | LOG("Found no restrict section to remove"); 153 | return OPErrorStripFailure; 154 | } else { 155 | LOG("Sucessfully removed restrict section"); 156 | } 157 | 158 | } else if ([package booleanValueForSignature:uninstall]) { 159 | if (removeLoadEntryFromBinary(binary, macho, dylibPath)) { 160 | LOG("Successfully removed all entries for %s", dylibPath.UTF8String); 161 | } else { 162 | LOG("No entries for %s exist to remove", dylibPath.UTF8String); 163 | return OPErrorNoEntries; 164 | } 165 | } else if ([package booleanValueForSignature:install]) { 166 | NSString *lc = [package firstObjectForSignature:command]; 167 | uint32_t command = LC_LOAD_DYLIB; 168 | if (lc) 169 | command = COMMAND(lc); 170 | if (command == -1) { 171 | LOG("Invalid load command."); 172 | return OPErrorInvalidLoadCommand; 173 | } 174 | 175 | if (insertLoadEntryIntoBinary(dylibPath, binary, macho, command)) { 176 | LOG("Successfully inserted a %s command for %s", LC(command), CPU(macho.header.cputype)); 177 | } else { 178 | LOG("Failed to insert a %s command for %s", LC(command), CPU(macho.header.cputype)); 179 | return OPErrorInsertFailure; 180 | } 181 | } else if ([package booleanValueForSignature:aslr]) { 182 | LOG("Attempting to remove ASLR"); 183 | if (removeASLRFromBinary(binary, macho)) { 184 | LOG("Successfully removed ASLR from binary"); 185 | } 186 | } else if ([package countOfSignature:rename] > 0) { 187 | NSLog(@"%@", [package allObjectsForSignature:rename]); 188 | 189 | NSString *first = [package firstObjectForSignature:rename]; 190 | NSString *last = [package lastObjectForSignature:rename]; 191 | 192 | if (first == last) 193 | first = nil; 194 | 195 | LOG("Attempting to rename..."); 196 | if (renameBinary(binary, macho, first, last)) { 197 | LOG("Successfully renamed"); 198 | } 199 | 200 | } else { 201 | // Invalid arguments. Show help 202 | showHelp = YES; 203 | goto help; 204 | } 205 | } 206 | 207 | if ([package booleanValueForSignature:backup]) { 208 | NSError *error = nil; 209 | LOG("Backing up executable (%s)...", executablePath.UTF8String); 210 | if (![manager fileExistsAtPath:backupPath isDirectory:NULL] && ![manager copyItemAtPath:executablePath toPath:backupPath error:&error]) { 211 | LOG("Encountered error during backup: %s", error.localizedDescription.UTF8String); 212 | return OPErrorBackupFailure; 213 | } 214 | } 215 | 216 | LOG("Writing executable to %s...", outputPath.UTF8String); 217 | if (![binary writeToFile:outputPath atomically:NO]) { 218 | LOG("Failed to write data. Permissions?"); 219 | return OPErrorWriteFailure; 220 | } 221 | 222 | if ([package booleanValueForSignature:resign]) { 223 | const char *resignPath = outputPath ? outputPath.UTF8String : (bundle ? bundle.bundlePath.UTF8String : executablePath.UTF8String); 224 | LOG("Attempting to resign %s...", resignPath); 225 | NSPipe *output = [NSPipe pipe]; 226 | NSTask *task = [[NSTask alloc] init]; 227 | task.launchPath = @"/usr/bin/codesign"; 228 | task.arguments = @[ @"-f", @"-s", @"-", @(resignPath) ]; 229 | 230 | [task setStandardOutput:output]; 231 | [task setStandardError:output]; 232 | [task launch]; 233 | [task waitUntilExit]; 234 | 235 | NSFileHandle *read = [output fileHandleForReading]; 236 | NSData *dataRead = [read readDataToEndOfFile]; 237 | NSString *stringRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding]; 238 | LOG("%s", stringRead.UTF8String); 239 | if (task.terminationStatus == 0) { 240 | LOG("Successfully resigned executable"); 241 | } else { 242 | LOG("Failed to resign executable. Reverting..."); 243 | if (![package firstObjectForSignature:output]) { 244 | // Don't overwrite the executable if an output was actually specified 245 | [originalData writeToFile:executablePath atomically:NO]; 246 | } 247 | return OPErrorResignFailure; 248 | } 249 | } 250 | 251 | // End scope 252 | } 253 | 254 | help: 255 | if (showHelp) { 256 | struct winsize ws; 257 | ioctl(0, TIOCGWINSZ, &ws); 258 | 259 | #define SHOW(SIG) LOG("%s", [[SIG xpmargs_mutableStringByIndentingToWidth:2 lineLength:ws.ws_col] UTF8String]) 260 | 261 | LOG("optool v0.2\n"); 262 | LOG("USAGE:"); 263 | SHOW(@"install -c -p -t [-o=] [-b] [--resign] Inserts an LC_LOAD command into the target binary which points to the payload. This may render some executables unusable."); 264 | SHOW(@"uninstall -p -t [-o=] [-b] [--resign] Removes any LC_LOAD commands which point to a given payload from the target binary. This may render some executables unusable."); 265 | SHOW(@"strip [-w] -t Removes a code signature load command from the given binary."); 266 | SHOW(@"unrestrict [-w] -t Removes a __restrict section from the given binary. The weak flag makes this a non-destructive operation which merely renames the __restrict section to something not understandable by dyld; otherwise, this operation removes all the __restrict data from the binary."); 267 | SHOW(@"restore -t Restores any backup made on the target by this tool."); 268 | SHOW(@"aslr -t [-o=] [-b] [--resign] Removes an ASLR flag from the macho header if it exists. This may render some executables unusable"); 269 | LOG("\nOPTIONS:"); 270 | SHOW(@"[-w --weak] Used with the STRIP or UNRESTRICT commands to weakly remove the signature. Without this, the code signature is replaced with null bytes on the binary and its LOAD command is removed."); 271 | SHOW(@"[--resign] Try to repair the code signature after any operations are done. This may render some executables unusable."); 272 | SHOW(@"-t|--target Required of all commands to specify the target executable to modify"); 273 | SHOW(@"-p|--payload Required of the INSTALL and UNINSTALL commands to specify the path to a DYLIB to point the LOAD command to"); 274 | SHOW(@"[-c --command] Specify which type of load command to use in INSTALL. Can be reexport for LC_REEXPORT_DYLIB, weak for LC_LOAD_WEAK_DYLIB, upward for LC_LOAD_UPWARD_DYLIB, or load for LC_LOAD_DYLIB"); 275 | SHOW(@"[-b --backup] Backup the executable to a suffixed path (in the form of _backup.BUNDLEVERSION)"); 276 | SHOW(@"[-h --help] Show this message"); 277 | LOG("\n(C) 2014 Alexander S. Zielenski. Licensed under BSD"); 278 | 279 | return ([package booleanValueForSignature:help]) ? OPErrorNone : OPErrorInvalidArguments; 280 | } 281 | } 282 | 283 | return OPErrorNone; 284 | } 285 | 286 | 287 | -------------------------------------------------------------------------------- /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 | 29 | #import 30 | #import "defines.h" 31 | 32 | BOOL stripCodeSignatureFromBinary(NSMutableData *binary, struct thin_header macho, BOOL soft); 33 | BOOL unrestrictBinary(NSMutableData *binary, struct thin_header macho, BOOL soft); 34 | BOOL removeLoadEntryFromBinary(NSMutableData *binary, struct thin_header macho, NSString *payload); 35 | BOOL binaryHasLoadCommandForDylib(NSMutableData *binary, NSString *dylib, uint32_t *lastOffset, struct thin_header macho); 36 | BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, struct thin_header macho, uint32_t type); 37 | BOOL removeASLRFromBinary(NSMutableData *binary, struct thin_header macho); 38 | BOOL renameBinary(NSMutableData *binary, struct thin_header macho, NSString *from, NSString *to); 39 | -------------------------------------------------------------------------------- /optool/operations.m: -------------------------------------------------------------------------------- 1 | // 2 | // operations.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 | 29 | #import "operations.h" 30 | #import "NSData+Reading.h" 31 | #import "defines.h" 32 | 33 | // Reads ulen128 from a buffer and returns the value and the length read 34 | static uint64_t read_uleb128(uint8_t *p, uint32_t *read_length) { 35 | uint8_t *orig = p; 36 | uint64_t value = 0; 37 | unsigned shift = 0; 38 | 39 | do { 40 | value += (*p & 0x7f) << shift; 41 | shift += 7; 42 | } while (*p++ >= 128); 43 | 44 | if (read_length) 45 | *read_length = (uint32_t)(p - orig); 46 | 47 | return value; 48 | } 49 | 50 | // Writes a uint64_t as a uleb to a buffer and returns the length 51 | // Also passes in a length limit so we can pad to it 52 | static uint32_t write_uleb128(uint8_t *p, uint64_t value, uint32_t length_limit) { 53 | uint8_t *orig = p; 54 | do { 55 | uint8_t byte = value & 0x7f; 56 | value >>= 7; 57 | 58 | if (value != 0) { 59 | byte |= 0x80; 60 | } 61 | 62 | *p++ = byte; 63 | } while (value != 0); 64 | 65 | uint32_t len = (uint32_t)(p - orig); 66 | 67 | int32_t pad = length_limit - len; 68 | if (pad < 0 && length_limit != 0) { 69 | exit(OPErrorULEBEncodeFailure); 70 | } 71 | 72 | if (pad != 0 && pad > 0) { 73 | // mark these bytes to show more follow 74 | for (; pad != 1; --pad) { 75 | *p++ = '\x80'; 76 | } 77 | 78 | // mark terminating byte 79 | *p++ = '\x00'; 80 | } 81 | 82 | return len; 83 | } 84 | 85 | unsigned int OP_SOFT_STRIP = 0x00001337; 86 | const char *OP_SOFT_UNRESTRICT = "\xf0\x9f\x92\xa9"; 87 | 88 | BOOL stripCodeSignatureFromBinary(NSMutableData *binary, struct thin_header macho, BOOL softStrip) { 89 | binary.currentOffset = macho.offset + macho.size; 90 | BOOL success = NO; 91 | 92 | // Loop through the commands until we found an LC_CODE_SIGNATURE command 93 | // and either replace it and its corresponding signature with zero-bytes 94 | // or change LC_CODE_SIGNATURE to OP_SOFT_STRIP, so the compiler 95 | // can't interpret the load command for the code signature and treats 96 | // the binary as if it doesn't exist 97 | for (int i = 0; i < macho.header.ncmds; i++) { 98 | if (binary.currentOffset >= binary.length || 99 | binary.currentOffset > macho.header.sizeofcmds + macho.size + macho.offset) // dont go past the header 100 | break; 101 | 102 | uint32_t cmd = [binary intAtOffset:binary.currentOffset]; 103 | uint32_t size = [binary intAtOffset:binary.currentOffset + sizeof(uint32_t)]; 104 | 105 | switch (cmd) { 106 | case LC_CODE_SIGNATURE: { 107 | struct linkedit_data_command command = *(struct linkedit_data_command *)(binary.bytes + binary.currentOffset); 108 | LOG("stripping code signature for architecture %s...", CPU(macho.header.cputype)); 109 | 110 | if (!softStrip) { 111 | macho.header.ncmds -= 1; 112 | macho.header.sizeofcmds -= sizeof(struct linkedit_data_command); 113 | [binary replaceBytesInRange:NSMakeRange(command.dataoff, command.datasize) withBytes:0 length:command.datasize]; 114 | [binary replaceBytesInRange:NSMakeRange(binary.currentOffset, sizeof(struct linkedit_data_command)) withBytes:0 length:0]; 115 | [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, 0) withBytes:0 length:size]; 116 | } else { 117 | [binary replaceBytesInRange:NSMakeRange(binary.currentOffset, 4) 118 | withBytes:&OP_SOFT_STRIP]; 119 | } 120 | 121 | success = YES; 122 | break; 123 | } 124 | default: 125 | binary.currentOffset += size; 126 | break; 127 | } 128 | } 129 | 130 | // paste in a modified header with an updated number and size of load commands 131 | if (!softStrip) { 132 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header length:sizeof(macho.header)]; 133 | } 134 | 135 | return success; 136 | } 137 | 138 | BOOL unrestrictBinary(NSMutableData *binary, struct thin_header macho, BOOL soft) { 139 | binary.currentOffset = macho.offset + macho.size; 140 | BOOL success = NO; 141 | 142 | // Loop through the commands for an LC_SEGMENT(_64) command which has the name __RESTRICT 143 | // then remove its section with the name "__restrict". If soft is true, then we will simply rename 144 | // the __restrict section, if not, we will completely delete it and delete the entire __RESTRICT segment if it is 145 | // empty. 146 | LOG("unrestricting for architecture %s...", CPU(macho.header.cputype)); 147 | 148 | for (int i = 0; i < macho.header.ncmds; i++) { 149 | if (binary.currentOffset >= binary.length || 150 | binary.currentOffset > macho.header.sizeofcmds + macho.size + macho.offset) // dont go past the header 151 | break; 152 | 153 | uint32_t cmd = [binary intAtOffset:binary.currentOffset]; 154 | uint32_t size = [binary intAtOffset:binary.currentOffset + sizeof(uint32_t)]; 155 | 156 | #define CROSS(CODE...) \ 157 | case LC_SEGMENT: {\ 158 | typedef struct segment_command segment_type; \ 159 | typedef struct section section_type; \ 160 | CODE \ 161 | }\ 162 | case LC_SEGMENT_64: {\ 163 | typedef struct segment_command_64 segment_type; \ 164 | typedef struct section_64 section_type; \ 165 | CODE \ 166 | } 167 | 168 | switch (cmd) { 169 | CROSS( 170 | segment_type *command = (segment_type *)(binary.mutableBytes + binary.currentOffset); 171 | if (!strncmp(command->segname, "__RESTRICT", 16)) { 172 | LOG("Found __RESTRICT segment"); 173 | if (size < sizeof(command) || 174 | command->nsects > (size - sizeof(*command)) / sizeof(section_type)) { 175 | LOG("Bad segment_command"); 176 | return false; 177 | } 178 | 179 | section_type *section = (section_type *)(binary.mutableBytes + binary.currentOffset + sizeof(*command)); 180 | for (uint32_t i = 0; i < command->nsects; i++, section++) { 181 | if (!strncmp(section->sectname, "__restrict", 16)) { 182 | LOG("Found __restrict section. Patching..."); 183 | 184 | if (soft) { 185 | strcpy(section->sectname, OP_SOFT_UNRESTRICT); 186 | success = YES; 187 | } else { 188 | command->nsects--; 189 | command->cmdsize -= sizeof(*section); 190 | macho.header.sizeofcmds -= sizeof(*section); 191 | 192 | uint64_t sectionSize = sizeof(*section); 193 | [binary replaceBytesInRange:NSMakeRange((NSUInteger)section - (NSUInteger)binary.mutableBytes, 194 | sectionSize) 195 | withBytes:0 196 | length:0]; 197 | [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, 0) 198 | withBytes:0 199 | length:sectionSize]; 200 | success = YES; 201 | } 202 | } 203 | } 204 | 205 | // remove the whole segment 206 | if (command->nsects == 0 && !soft) { 207 | LOG("__RESTRICT segment has no more sections. Removing..."); 208 | macho.header.ncmds--; 209 | uint32_t cmdSize = sizeof(*command); 210 | macho.header.sizeofcmds -= command->cmdsize; 211 | [binary replaceBytesInRange:NSMakeRange((NSUInteger)command - (NSUInteger)binary.mutableBytes, 212 | cmdSize) 213 | withBytes:0 214 | length:0]; 215 | [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, 0) 216 | withBytes:0 217 | length:cmdSize]; 218 | } else { 219 | binary.currentOffset += command->cmdsize; 220 | } 221 | 222 | } else { 223 | binary.currentOffset += size; 224 | } 225 | break; 226 | ) 227 | default: 228 | binary.currentOffset += size; 229 | break; 230 | } 231 | } 232 | #undef CROSS 233 | 234 | // paste in a modified header with an updated number and size of commands 235 | if (!soft) { 236 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) 237 | withBytes:&macho.header 238 | length:sizeof(macho.header)]; 239 | } 240 | 241 | return success; 242 | } 243 | 244 | BOOL removeLoadEntryFromBinary(NSMutableData *binary, struct thin_header macho, NSString *payload) { 245 | // parse load commands to see if our load command is already there 246 | binary.currentOffset = macho.offset + macho.size; 247 | 248 | uint32_t num = 0; 249 | uint32_t cumulativeSize = 0; 250 | uint32_t removedOrdinal = -1; 251 | 252 | for (int i = 0; i < macho.header.ncmds; i++) { 253 | if (binary.currentOffset >= binary.length || 254 | binary.currentOffset > macho.offset + macho.size + macho.header.sizeofcmds) 255 | break; 256 | 257 | uint32_t cmd = [binary intAtOffset:binary.currentOffset]; 258 | uint32_t size = [binary intAtOffset:binary.currentOffset + 4]; 259 | 260 | // delete the bytes in all of the load commands matching the description 261 | switch (cmd) { 262 | case LC_REEXPORT_DYLIB: 263 | case LC_LOAD_UPWARD_DYLIB: 264 | case LC_LOAD_WEAK_DYLIB: 265 | case LC_LOAD_DYLIB: { 266 | 267 | struct dylib_command command = *(struct dylib_command *)(binary.bytes + binary.currentOffset); 268 | char *name = (char *)[[binary subdataWithRange:NSMakeRange(binary.currentOffset + command.dylib.name.offset, command.cmdsize - command.dylib.name.offset)] bytes]; 269 | if ([@(name) isEqualToString:payload] && removedOrdinal == -1) { 270 | LOG("removing payload from %s...", LC(cmd)); 271 | // remove load command 272 | // remove these bytes and append zeroes to the end of the header 273 | [binary replaceBytesInRange:NSMakeRange(binary.currentOffset, size) withBytes:0 length:0]; 274 | num++; 275 | cumulativeSize += size; 276 | 277 | removedOrdinal = i; 278 | } 279 | 280 | binary.currentOffset += size; 281 | break; 282 | } 283 | 284 | //! EXPERIMENTAL: Shifting binding ordinals 285 | case LC_DYLD_INFO: 286 | case LC_DYLD_INFO_ONLY: { 287 | if (removedOrdinal == -1) { 288 | binary.currentOffset += size; 289 | break; 290 | } 291 | #ifdef DEBUG 292 | struct dyld_info_command info; 293 | [binary getBytes:&info range:NSMakeRange(binary.currentOffset, size)]; 294 | 295 | uint8_t *p = (uint8_t *)binary.mutableBytes + info.bind_off; 296 | uint32_t s = 0; 297 | while (s < info.bind_size) { 298 | 299 | uint8_t opcode = *p & BIND_OPCODE_MASK; 300 | 301 | p++; 302 | s += (sizeof(&p)); 303 | 304 | // we dont support opcode == BIND_OPCODE_SET_DYLIB_SPECIAL_IMM 305 | if (opcode == BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB) { 306 | NSLog(@"ULEB"); 307 | 308 | // Convert the ULEB into something we can use 309 | uint32_t len = 0; 310 | uint64_t ordinal = read_uleb128(p, &len); 311 | // Decrement it if necessary 312 | if (ordinal > removedOrdinal) { 313 | ordinal--; 314 | 315 | // Write it back if necessary 316 | write_uleb128(p, ordinal, len); 317 | } 318 | 319 | // increment s by the size of the ULEB 320 | s += len; 321 | 322 | 323 | } else if (opcode == BIND_OPCODE_SET_DYLIB_ORDINAL_IMM) { 324 | uint8_t immediate = *p & BIND_IMMEDIATE_MASK; 325 | 326 | // decrement this immediate if this opcode is greater than the one we removed 327 | if (immediate > i) { 328 | immediate--; 329 | 330 | // no need to shift the opcode since we never shifted it right 331 | uint8_t binding = opcode | (immediate & BIND_IMMEDIATE_MASK); 332 | // reset the binding ordinal 333 | *p = binding; 334 | } 335 | } 336 | } 337 | #endif 338 | binary.currentOffset += size; 339 | break; 340 | } 341 | default: 342 | binary.currentOffset += size; 343 | break; 344 | } 345 | } 346 | 347 | if (num == 0) 348 | return NO; 349 | 350 | // fix the header 351 | macho.header.ncmds -= num; 352 | macho.header.sizeofcmds -= cumulativeSize; 353 | 354 | unsigned int zeroByte = 0; 355 | 356 | // append a null byte for every one we removed to the end of the header 357 | [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, 0) withBytes:&zeroByte length:cumulativeSize]; 358 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) 359 | withBytes:&macho.header 360 | length:sizeof(macho.header)]; 361 | 362 | return YES; 363 | } 364 | 365 | BOOL binaryHasLoadCommandForDylib(NSMutableData *binary, NSString *dylib, uint32_t *lastOffset, struct thin_header macho) { 366 | binary.currentOffset = macho.size + macho.offset; 367 | unsigned int loadOffset = (unsigned int)binary.currentOffset; 368 | 369 | // Loop through compatible LC_LOAD commands until we find one which points 370 | // to the given dylib and tell the caller where it is and if it exists 371 | for (int i = 0; i < macho.header.ncmds; i++) { 372 | if (binary.currentOffset >= binary.length || 373 | binary.currentOffset > macho.offset + macho.size + macho.header.sizeofcmds) 374 | break; 375 | 376 | uint32_t cmd = [binary intAtOffset:binary.currentOffset]; 377 | uint32_t size = [binary intAtOffset:binary.currentOffset + 4]; 378 | 379 | switch (cmd) { 380 | case LC_REEXPORT_DYLIB: 381 | case LC_LOAD_UPWARD_DYLIB: 382 | case LC_LOAD_WEAK_DYLIB: 383 | case LC_LOAD_DYLIB: { 384 | struct dylib_command command = *(struct dylib_command *)(binary.bytes + binary.currentOffset); 385 | char *name = (char *)[[binary subdataWithRange:NSMakeRange(binary.currentOffset + command.dylib.name.offset, command.cmdsize - command.dylib.name.offset)] bytes]; 386 | 387 | if ([@(name) isEqualToString:dylib]) { 388 | *lastOffset = (unsigned int)binary.currentOffset; 389 | return YES; 390 | } 391 | 392 | binary.currentOffset += size; 393 | loadOffset = (unsigned int)binary.currentOffset; 394 | break; 395 | } 396 | default: 397 | binary.currentOffset += size; 398 | break; 399 | } 400 | } 401 | 402 | if (lastOffset != NULL) 403 | *lastOffset = loadOffset; 404 | 405 | return NO; 406 | } 407 | 408 | BOOL renameBinary(NSMutableData *binary, struct thin_header macho, NSString *from, NSString *to) { 409 | binary.currentOffset = macho.size + macho.offset; 410 | 411 | BOOL success = NO; 412 | 413 | // Loop through compatible LC_LOAD commands until we find one which points 414 | // to the given dylib and tell the caller where it is and if it exists 415 | for (int i = 0; i < macho.header.ncmds; i++) { 416 | if (binary.currentOffset >= binary.length || 417 | binary.currentOffset > macho.offset + macho.size + macho.header.sizeofcmds) 418 | break; 419 | 420 | uint32_t cmd = [binary intAtOffset:binary.currentOffset]; 421 | uint32_t size = [binary intAtOffset:binary.currentOffset + 4]; 422 | 423 | switch (cmd) { 424 | case LC_ID_DYLIB: 425 | case LC_REEXPORT_DYLIB: 426 | case LC_LOAD_UPWARD_DYLIB: 427 | case LC_LOAD_WEAK_DYLIB: 428 | case LC_LOAD_DYLIB: { 429 | struct dylib_command *command = (struct dylib_command *)(binary.mutableBytes + binary.currentOffset); 430 | off_t name_offset = binary.currentOffset + command->dylib.name.offset; 431 | NSRange name_range = NSMakeRange(name_offset, command->cmdsize - command->dylib.name.offset); 432 | char *name = (char *)[[binary subdataWithRange:name_range] bytes]; 433 | 434 | if ([@(name) isEqualToString:from] || (!from && cmd == LC_ID_DYLIB)) { 435 | const char *replacement = to.fileSystemRepresentation; 436 | 437 | NSInteger name_length = strlen(replacement) + 1; 438 | unsigned int padding = (4 - (name_length % 4)); 439 | if (padding < 4) 440 | name_length += padding; 441 | 442 | NSInteger shift = name_length - name_range.length; 443 | 444 | if (shift > 0) { 445 | // accomodate for shift by replacing null bytes 446 | [binary replaceBytesInRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size, 447 | shift) 448 | withBytes:0 449 | length:0]; 450 | 451 | } else if (shift < 0) { 452 | uint8_t zero = 0; 453 | // accomodate for shift by inserting null bytes 454 | [binary replaceBytesInRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size, 455 | 0) 456 | withBytes:&zero 457 | length:labs(shift)]; 458 | } 459 | 460 | command->cmdsize += shift; 461 | macho.header.sizeofcmds += shift; 462 | 463 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header]; 464 | [binary replaceBytesInRange:name_range withBytes:replacement length:name_length]; 465 | 466 | success = YES; 467 | } 468 | 469 | binary.currentOffset += size; 470 | break; 471 | } 472 | default: 473 | binary.currentOffset += size; 474 | break; 475 | } 476 | } 477 | 478 | return success; 479 | } 480 | 481 | BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, struct thin_header macho, uint32_t type) { 482 | if (type != LC_REEXPORT_DYLIB && 483 | type != LC_LOAD_WEAK_DYLIB && 484 | type != LC_LOAD_UPWARD_DYLIB && 485 | type != LC_LOAD_DYLIB) { 486 | LOG("Invalid load command type"); 487 | return NO; 488 | } 489 | // parse load commands to see if our load command is already there 490 | uint32_t lastOffset = 0; 491 | if (binaryHasLoadCommandForDylib(binary, dylibPath, &lastOffset, macho)) { 492 | // there already exists a load command for this payload so change the command type 493 | uint32_t originalType = *(uint32_t *)(binary.bytes + lastOffset); 494 | if (originalType != type) { 495 | LOG("A load command already exists for %s. Changing command type from %s to desired %s", dylibPath.UTF8String, LC(originalType), LC(type)); 496 | [binary replaceBytesInRange:NSMakeRange(lastOffset, sizeof(type)) withBytes:&type]; 497 | } else { 498 | LOG("Load command already exists"); 499 | } 500 | 501 | return YES; 502 | } 503 | 504 | // create a new load command 505 | unsigned int length = (unsigned int)sizeof(struct dylib_command) + (unsigned int)dylibPath.length; 506 | unsigned int padding = (8 - (length % 8)); 507 | 508 | // check if data we are replacing is null 509 | NSData *occupant = [binary subdataWithRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size, 510 | length + padding)]; 511 | 512 | // All operations in optool try to maintain a constant byte size of the executable 513 | // so we don't want to append new bytes to the binary (that would break the executable 514 | // since everything is offset-based–we'd have to go in and adjust every offset) 515 | // So instead take advantage of the huge amount of padding after the load commands 516 | if (strcmp([occupant bytes], "\0")) { 517 | NSLog(@"cannot inject payload into %s because there is no room", dylibPath.fileSystemRepresentation); 518 | return NO; 519 | } 520 | 521 | LOG("Inserting a %s command for architecture: %s", LC(type), CPU(macho.header.cputype)); 522 | 523 | struct dylib_command command; 524 | struct dylib dylib; 525 | dylib.name.offset = sizeof(struct dylib_command); 526 | dylib.timestamp = 2; // load commands I've seen use 2 for some reason 527 | dylib.current_version = 0; 528 | dylib.compatibility_version = 0; 529 | command.cmd = type; 530 | command.dylib = dylib; 531 | command.cmdsize = length + padding; 532 | 533 | unsigned int zeroByte = 0; 534 | NSMutableData *commandData = [NSMutableData data]; 535 | [commandData appendBytes:&command length:sizeof(struct dylib_command)]; 536 | [commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]]; 537 | [commandData appendBytes:&zeroByte length:padding]; 538 | 539 | // remove enough null bytes to account of our inserted data 540 | [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, commandData.length) 541 | withBytes:0 542 | length:0]; 543 | // insert the data 544 | [binary replaceBytesInRange:NSMakeRange(lastOffset, 0) withBytes:commandData.bytes length:commandData.length]; 545 | 546 | // fix the existing header 547 | macho.header.ncmds += 1; 548 | macho.header.sizeofcmds += command.cmdsize; 549 | 550 | // this is safe to do in 32bit because the 4 bytes after the header are still being put back 551 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header]; 552 | 553 | return YES; 554 | } 555 | BOOL removeASLRFromBinary(NSMutableData *binary, struct thin_header macho) { 556 | // MH_PIE is a flag on the macho header whcih indicates that the address space of the executable 557 | // should be randomized 558 | if (macho.header.flags & MH_PIE) { 559 | macho.header.flags &= ~MH_PIE; 560 | [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header]; 561 | } else { 562 | LOG("binary is not protected by ASLR"); 563 | return NO; 564 | } 565 | 566 | return YES; 567 | } 568 | --------------------------------------------------------------------------------