├── .gitignore ├── insert_dylib_tests ├── test.c.foo ├── Info.plist └── insert_dylib_tests.m ├── insert_dylib.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── README.md └── insert_dylib └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata 3 | *.xccheckout 4 | -------------------------------------------------------------------------------- /insert_dylib_tests/test.c.foo: -------------------------------------------------------------------------------- 1 | // If we have a .c extension, Xcode will compile the file instead of just copying it. 2 | #include 3 | 4 | int main(void) { 5 | printf("insert_dylib_test\n"); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /insert_dylib.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /insert_dylib_tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.tyilo.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | insert_dylib 2 | ============ 3 | 4 | Command line utility for inserting a dylib load command into a Mach-O binary. 5 | 6 | Does the following (to each arch if the binary is fat): 7 | 8 | - Adds a `LC_LOAD_DYLIB` load command to the end of the load commands 9 | - Increments the mach header's `ncmds` and adjusts its `sizeofcmds` 10 | - ([Removes code signature if present](#removing-code-signature)) 11 | 12 | Usage 13 | ----- 14 | 15 | ``` 16 | Usage: insert_dylib dylib_path binary_path [new_binary_path] 17 | Option flags: --inplace --weak --overwrite --strip-codesig --no-strip-codesig --all-yes 18 | ``` 19 | 20 | `insert_dylib` inserts a load command to load the `dylib_path` in `binary_path`. 21 | 22 | Unless `--inplace` option is specified, `insert_dylib` will produce a new binary at `new_binary_path`. 23 | If neither `--inplace` nor `new_binary_path` is specified, the output binary will be located at the same location as the input binary with `_patched` prepended to the name. 24 | 25 | If the `--weak` option is specified, `insert_dylib` will insert a `LC_LOAD_WEAK_DYLIB` load command instead of `LC_LOAD_DYLIB`. 26 | 27 | ### Example 28 | 29 | ``` 30 | $ cat > test.c 31 | int main(void) { 32 | printf("Testing\n"); 33 | return 0; 34 | } 35 | ^D 36 | $ clang test.c -o test &> /dev/null 37 | $ insert_dylib /usr/lib/libfoo.dylib test 38 | The provided dylib path doesn't exist. Continue anyway? [y/n] y 39 | Added LC_LOAD_DYLIB to test_patched 40 | $ ./test 41 | Testing 42 | $ ./test_patched 43 | dyld: Library not loaded: /usr/lib/libfoo.dylib 44 | Referenced from: /Users/Tyilo/./test_patched 45 | Reason: image not found 46 | Trace/BPT trap: 5 47 | ``` 48 | 49 | #### `otool` `diff` between original and patched binary 50 | ``` 51 | $ diff -u <(otool -hl test) <(otool -hl test_patched) 52 | --- /dev/fd/63 2014-07-30 04:08:40.000000000 +0200 53 | +++ /dev/fd/62 2014-07-30 04:08:40.000000000 +0200 54 | @@ -1,7 +1,7 @@ 55 | -test: 56 | +test_patched: 57 | Mach header 58 | magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 59 | - 0xfeedfacf 16777223 3 0x80 2 16 1296 0x00200085 60 | + 0xfeedfacf 16777223 3 0x80 2 17 1344 0x00200085 61 | Load command 0 62 | cmd LC_SEGMENT_64 63 | cmdsize 72 64 | @@ -231,3 +231,10 @@ 65 | cmdsize 16 66 | dataoff 8296 67 | datasize 64 68 | +Load command 16 69 | + cmd LC_LOAD_DYLIB 70 | + cmdsize 48 71 | + name /usr/lib/libfoo.dylib (offset 24) 72 | + time stamp 0 Thu Jan 1 01:00:00 1970 73 | + current version 0.0.0 74 | +compatibility version 0.0.0 75 | ``` 76 | 77 | #### `--weak` option 78 | 79 | ``` 80 | $ insert_dylib --weak /usr/lib/libfoo.dylib test test_patched2 81 | The provided dylib path doesn't exist. Continue anyway? [y/n] y 82 | Added LC_LOAD_WEAK_DYLIB to test_patched2 83 | $ ./test_patched2 84 | Testing 85 | ``` 86 | 87 | Removing code signature 88 | ---- 89 | 90 | To remove the code signature it is enough to delete the `LC_CODE_SIGNATURE` load command and fixup the mach header's `ncmds` and `sizeofcmds`, assuming it is the last load command. 91 | 92 | However if you just do this `codesign_allocate` (used by `codesign` and `ldid`) will fail with the error: 93 | 94 | ``` 95 | .../codesign_allocate: file not in an order that can be processed (link edit information does not fill the __LINKEDIT segment): 96 | ``` 97 | 98 | To fix this `insert_dylib` assumes that the code signature that `LC_CODE_SIGNATURE` is in the end of the `__LINKEDIT` segment and the that the segment is in the end of the architectures slice. 99 | 100 | It then truncate that slice to remove the code signature part of the `__LINKEDIT` segment. It also updates the `LC_SEGMENT` (or `LC_SEGMENT64`) load command for the `__LINKEDIT` segment from the new file size. If the binary is fat we also update the size and we might also move the slice and so the offset should also be updated. 101 | 102 | After removing the code signature from the `__LINKEDIT` segment, the last thing in that segment is typically the string table. As the code signature seems to be aligned by `0x10`, and so after removing the code signature, nothing points to the padding at the end of the segment, which `codesign_allocate` doesn't like either. To fix this we just increase the size of the string table in the `LC_SYMTAB` command so it also includes the padding. 103 | 104 | Todo 105 | ---- 106 | 107 | - Improved checking for free space to insert the new load command 108 | - Allow removal of `LC_CODE_SIGNATURE` if it isn't the last load command 109 | - Remove `__RESTRICT,__restrict` if not enough space (suggesion by dirkg) 110 | -------------------------------------------------------------------------------- /insert_dylib_tests/insert_dylib_tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // insert_dylib_tests.m 3 | // insert_dylib_tests 4 | // 5 | // Created by Asger Hautop Drewsen on 16/06/15. 6 | // Copyright (c) 2015 Tyilo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #define TEST_TEXT @"insert_dylib_test" 13 | #define XCRUN @"/usr/bin/xcrun" 14 | #define SYSROOT_SUFFIX @"/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk" 15 | #define TEST_DYLIB @"@executable_path/libfoo.dylib" 16 | #define CODESIGN_ID @"task_for_pid" 17 | 18 | NSString *insert_dylib_path; 19 | NSString *test_source; 20 | 21 | NSArray *x86_archs; 22 | NSArray *arm_archs; 23 | 24 | __attribute__((constructor)) void initialize_archs(void) { 25 | x86_archs = @[@"i386", @"x86_64"]; 26 | arm_archs = @[@"armv7", @"arm64"]; 27 | } 28 | 29 | NSArray *x86_binaries; 30 | NSArray *arm_binaries; 31 | 32 | @interface insert_dylib_tests : XCTestCase 33 | @property NSArray *x86_binaries; 34 | @property NSArray *arm_binaries; 35 | @property BOOL successful; 36 | @end 37 | 38 | @implementation insert_dylib_tests 39 | 40 | + (NSString *)tmpfile { 41 | char *tmp = strdup("/tmp/insert_dylib_test.XXXXXX"); 42 | NSString *file = [NSString stringWithUTF8String:mktemp(tmp)]; 43 | free(tmp); 44 | return file; 45 | } 46 | 47 | + (NSString *)getOutputFromPath:(NSString *)path arguments:(NSArray *)args status:(int *)status { 48 | NSTask *task = [NSTask new]; 49 | task.launchPath = path; 50 | task.arguments = args; 51 | 52 | NSPipe *pipe = [NSPipe pipe]; 53 | task.standardOutput = pipe; 54 | 55 | [task launch]; 56 | [task waitUntilExit]; 57 | 58 | int _status = [task terminationStatus]; 59 | 60 | if(status) { 61 | *status = _status; 62 | } else { 63 | assert(_status == 0); 64 | } 65 | 66 | NSData *data = [pipe.fileHandleForReading readDataToEndOfFile]; 67 | NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 68 | 69 | return [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 70 | } 71 | 72 | + (NSString *)iOSSysrootDirectory { 73 | static NSString *dir = nil; 74 | if(!dir) { 75 | dir = [[self getOutputFromPath:XCRUN arguments:@[@"xcode-select", @"-p"] status:NULL] stringByAppendingString:SYSROOT_SUFFIX]; 76 | } 77 | return dir; 78 | } 79 | 80 | + (NSString *)generateBinaryWithArchitectures:(NSArray *)archs foriOS:(BOOL)iOS { 81 | NSMutableArray *args = [NSMutableArray new]; 82 | [args addObject:@"clang"]; 83 | [args addObject:@"-x"]; 84 | [args addObject:@"c"]; 85 | 86 | for(NSString *arch in archs) { 87 | [args addObject:@"-arch"]; 88 | [args addObject:arch]; 89 | } 90 | 91 | if(iOS) { 92 | [args addObject:@"-miphoneos-version-min=1.0"]; 93 | [args addObject:@"-isysroot"]; 94 | [args addObject:[self iOSSysrootDirectory]]; 95 | } 96 | 97 | NSString *binary = [self tmpfile]; 98 | 99 | [args addObject:@"-o"]; 100 | [args addObject:binary]; 101 | 102 | [args addObject:test_source]; 103 | 104 | [self getOutputFromPath:XCRUN arguments:args status:NULL]; 105 | 106 | return binary; 107 | } 108 | 109 | + (NSArray *)generateBinariesForArchs:(NSArray *)archs iOS:(BOOL)iOS { 110 | NSMutableArray *arr = [NSMutableArray new]; 111 | for(NSString *arch in archs) { 112 | [arr addObject:[self generateBinaryWithArchitectures:@[arch] foriOS:iOS]]; 113 | } 114 | [arr addObject:[self generateBinaryWithArchitectures:archs foriOS:iOS]]; 115 | return arr; 116 | } 117 | 118 | + (void)setUp { 119 | [super setUp]; 120 | 121 | NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 122 | 123 | insert_dylib_path = [[[bundle bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"insert_dylib"]; 124 | test_source = [bundle pathForResource:@"test.c" ofType:@"foo"]; 125 | 126 | x86_binaries = [self generateBinariesForArchs:x86_archs iOS:NO]; 127 | arm_binaries = [self generateBinariesForArchs:arm_archs iOS:YES]; 128 | } 129 | 130 | + (void)tearDown { 131 | for(NSString *binary in [x86_binaries arrayByAddingObjectsFromArray:arm_binaries]) { 132 | [[NSFileManager defaultManager] removeItemAtPath:binary error:NULL]; 133 | } 134 | } 135 | 136 | - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected { 137 | [super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected]; 138 | self.successful = NO; 139 | } 140 | 141 | - (NSArray *)copyBinaries:(NSArray *)binaries { 142 | NSMutableArray *arr = [NSMutableArray new]; 143 | for(NSString *binary in binaries) { 144 | NSString *tmp = [[self class] tmpfile]; 145 | [[NSFileManager defaultManager] copyItemAtPath:binary toPath:tmp error:NULL]; 146 | [arr addObject:tmp]; 147 | } 148 | return arr; 149 | } 150 | 151 | - (void)setUp { 152 | [super setUp]; 153 | 154 | self.successful = YES; 155 | 156 | self.x86_binaries = [self copyBinaries:x86_binaries]; 157 | self.arm_binaries = [self copyBinaries:arm_binaries]; 158 | } 159 | 160 | - (void)tearDown { 161 | if(self.successful) { 162 | for(NSString *binary in [self.x86_binaries arrayByAddingObjectsFromArray:self.arm_binaries]) { 163 | [[NSFileManager defaultManager] removeItemAtPath:binary error:NULL]; 164 | } 165 | } 166 | 167 | [super tearDown]; 168 | } 169 | 170 | - (NSString *)getOutputFromPath:(NSString *)path arguments:(NSArray *)args { 171 | int res; 172 | NSString *output = [[self class] getOutputFromPath:path arguments:args status:&res]; 173 | XCTAssertEqual(res, 0, @"Failed to run %@ with arguments: %@", path, args); 174 | return output; 175 | 176 | } 177 | 178 | - (void)insertDylib:(NSArray *)args { 179 | [self getOutputFromPath:insert_dylib_path arguments:args]; 180 | } 181 | 182 | - (void)testBinary:(NSString *)path { 183 | XCTAssertEqualObjects([self getOutputFromPath:path arguments:@[]], TEST_TEXT, @"%@ outputted unexpected text", path); 184 | } 185 | 186 | - (void)codesign:(NSString *)path { 187 | [self getOutputFromPath:XCRUN arguments:@[@"codesign", @"-s", CODESIGN_ID, path]]; 188 | } 189 | 190 | - (void)testRunAfterAddingWeakLibrary { 191 | for(NSString *binary in self.x86_binaries) { 192 | [self insertDylib:@[@"--all-yes", @"--inplace", @"--weak", TEST_DYLIB, binary]]; 193 | [self testBinary:binary]; 194 | } 195 | } 196 | 197 | - (void)testCodesign { 198 | for(NSString *binary in self.x86_binaries) { 199 | [self codesign:binary]; 200 | [self testBinary:binary]; 201 | 202 | [self insertDylib:@[@"--all-yes", @"--inplace", @"--weak", TEST_DYLIB, binary]]; 203 | [self testBinary:binary]; 204 | 205 | [self codesign:binary]; 206 | [self testBinary:binary]; 207 | } 208 | for(NSString *binary in self.arm_binaries) { 209 | [self codesign:binary]; 210 | 211 | [self insertDylib:@[@"--all-yes", @"--inplace", @"--weak", TEST_DYLIB, binary]]; 212 | 213 | [self codesign:binary]; 214 | } 215 | } 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /insert_dylib.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 55708CC21B308025002D62A8 /* insert_dylib_tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 55708CC11B308025002D62A8 /* insert_dylib_tests.m */; }; 11 | 55708CCD1B308B77002D62A8 /* test.c.foo in Resources */ = {isa = PBXBuildFile; fileRef = 55708CCC1B308B77002D62A8 /* test.c.foo */; }; 12 | 55ABCB4D19881CA600B03F31 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 55ABCB4C19881CA600B03F31 /* main.c */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 55ABCB4719881CA600B03F31 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 55708CBD1B308025002D62A8 /* insert_dylib_tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = insert_dylib_tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 55708CC01B308025002D62A8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 55708CC11B308025002D62A8 /* insert_dylib_tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = insert_dylib_tests.m; sourceTree = ""; }; 31 | 55708CCC1B308B77002D62A8 /* test.c.foo */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = test.c.foo; sourceTree = ""; }; 32 | 55ABCB4919881CA600B03F31 /* insert_dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = insert_dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 55ABCB4C19881CA600B03F31 /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 55708CBA1B308025002D62A8 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | 55ABCB4619881CA600B03F31 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 55708CBE1B308025002D62A8 /* insert_dylib_tests */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 55708CC11B308025002D62A8 /* insert_dylib_tests.m */, 58 | 55708CBF1B308025002D62A8 /* Supporting Files */, 59 | ); 60 | path = insert_dylib_tests; 61 | sourceTree = ""; 62 | }; 63 | 55708CBF1B308025002D62A8 /* Supporting Files */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 55708CCC1B308B77002D62A8 /* test.c.foo */, 67 | 55708CC01B308025002D62A8 /* Info.plist */, 68 | ); 69 | name = "Supporting Files"; 70 | sourceTree = ""; 71 | }; 72 | 55ABCB4019881CA600B03F31 = { 73 | isa = PBXGroup; 74 | children = ( 75 | 55ABCB4B19881CA600B03F31 /* insert_dylib */, 76 | 55708CBE1B308025002D62A8 /* insert_dylib_tests */, 77 | 55ABCB4A19881CA600B03F31 /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 55ABCB4A19881CA600B03F31 /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 55ABCB4919881CA600B03F31 /* insert_dylib */, 85 | 55708CBD1B308025002D62A8 /* insert_dylib_tests.xctest */, 86 | ); 87 | name = Products; 88 | sourceTree = ""; 89 | }; 90 | 55ABCB4B19881CA600B03F31 /* insert_dylib */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 55ABCB4C19881CA600B03F31 /* main.c */, 94 | ); 95 | path = insert_dylib; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | 55708CBC1B308025002D62A8 /* insert_dylib_tests */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = 55708CC51B308025002D62A8 /* Build configuration list for PBXNativeTarget "insert_dylib_tests" */; 104 | buildPhases = ( 105 | 55708CB91B308025002D62A8 /* Sources */, 106 | 55708CBA1B308025002D62A8 /* Frameworks */, 107 | 55708CBB1B308025002D62A8 /* Resources */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = insert_dylib_tests; 114 | productName = insert_dylib_tests; 115 | productReference = 55708CBD1B308025002D62A8 /* insert_dylib_tests.xctest */; 116 | productType = "com.apple.product-type.bundle.unit-test"; 117 | }; 118 | 55ABCB4819881CA600B03F31 /* insert_dylib */ = { 119 | isa = PBXNativeTarget; 120 | buildConfigurationList = 55ABCB5219881CA600B03F31 /* Build configuration list for PBXNativeTarget "insert_dylib" */; 121 | buildPhases = ( 122 | 55ABCB4519881CA600B03F31 /* Sources */, 123 | 55ABCB4619881CA600B03F31 /* Frameworks */, 124 | 55ABCB4719881CA600B03F31 /* CopyFiles */, 125 | ); 126 | buildRules = ( 127 | ); 128 | dependencies = ( 129 | ); 130 | name = insert_dylib; 131 | productName = insert_dylib; 132 | productReference = 55ABCB4919881CA600B03F31 /* insert_dylib */; 133 | productType = "com.apple.product-type.tool"; 134 | }; 135 | /* End PBXNativeTarget section */ 136 | 137 | /* Begin PBXProject section */ 138 | 55ABCB4119881CA600B03F31 /* Project object */ = { 139 | isa = PBXProject; 140 | attributes = { 141 | LastUpgradeCheck = 0510; 142 | ORGANIZATIONNAME = Tyilo; 143 | TargetAttributes = { 144 | 55708CBC1B308025002D62A8 = { 145 | CreatedOnToolsVersion = 6.3.2; 146 | }; 147 | }; 148 | }; 149 | buildConfigurationList = 55ABCB4419881CA600B03F31 /* Build configuration list for PBXProject "insert_dylib" */; 150 | compatibilityVersion = "Xcode 3.2"; 151 | developmentRegion = English; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | ); 156 | mainGroup = 55ABCB4019881CA600B03F31; 157 | productRefGroup = 55ABCB4A19881CA600B03F31 /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | 55ABCB4819881CA600B03F31 /* insert_dylib */, 162 | 55708CBC1B308025002D62A8 /* insert_dylib_tests */, 163 | ); 164 | }; 165 | /* End PBXProject section */ 166 | 167 | /* Begin PBXResourcesBuildPhase section */ 168 | 55708CBB1B308025002D62A8 /* Resources */ = { 169 | isa = PBXResourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 55708CCD1B308B77002D62A8 /* test.c.foo in Resources */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXResourcesBuildPhase section */ 177 | 178 | /* Begin PBXSourcesBuildPhase section */ 179 | 55708CB91B308025002D62A8 /* Sources */ = { 180 | isa = PBXSourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | 55708CC21B308025002D62A8 /* insert_dylib_tests.m in Sources */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | 55ABCB4519881CA600B03F31 /* Sources */ = { 188 | isa = PBXSourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 55ABCB4D19881CA600B03F31 /* main.c in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | 55708CC31B308025002D62A8 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | COMBINE_HIDPI_IMAGES = YES; 203 | DEBUG_INFORMATION_FORMAT = dwarf; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | FRAMEWORK_SEARCH_PATHS = ( 206 | "$(DEVELOPER_FRAMEWORKS_DIR)", 207 | "$(inherited)", 208 | ); 209 | GCC_NO_COMMON_BLOCKS = YES; 210 | GCC_PREPROCESSOR_DEFINITIONS = ( 211 | "DEBUG=1", 212 | "$(inherited)", 213 | ); 214 | INFOPLIST_FILE = insert_dylib_tests/Info.plist; 215 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 216 | MACOSX_DEPLOYMENT_TARGET = 10.10; 217 | MTL_ENABLE_DEBUG_INFO = YES; 218 | PRODUCT_NAME = "$(TARGET_NAME)"; 219 | }; 220 | name = Debug; 221 | }; 222 | 55708CC41B308025002D62A8 /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | COMBINE_HIDPI_IMAGES = YES; 227 | COPY_PHASE_STRIP = NO; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | FRAMEWORK_SEARCH_PATHS = ( 230 | "$(DEVELOPER_FRAMEWORKS_DIR)", 231 | "$(inherited)", 232 | ); 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | INFOPLIST_FILE = insert_dylib_tests/Info.plist; 235 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 236 | MACOSX_DEPLOYMENT_TARGET = 10.10; 237 | MTL_ENABLE_DEBUG_INFO = NO; 238 | PRODUCT_NAME = "$(TARGET_NAME)"; 239 | }; 240 | name = Release; 241 | }; 242 | 55ABCB5019881CA600B03F31 /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | COPY_PHASE_STRIP = NO; 259 | GCC_C_LANGUAGE_STANDARD = gnu99; 260 | GCC_DYNAMIC_NO_PIC = NO; 261 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 262 | GCC_OPTIMIZATION_LEVEL = 0; 263 | GCC_PREPROCESSOR_DEFINITIONS = ( 264 | "DEBUG=1", 265 | "$(inherited)", 266 | ); 267 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | MACOSX_DEPLOYMENT_TARGET = 10.9; 275 | ONLY_ACTIVE_ARCH = YES; 276 | SDKROOT = macosx; 277 | }; 278 | name = Debug; 279 | }; 280 | 55ABCB5119881CA600B03F31 /* Release */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BOOL_CONVERSION = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | COPY_PHASE_STRIP = YES; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | ENABLE_NS_ASSERTIONS = NO; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | MACOSX_DEPLOYMENT_TARGET = 10.9; 308 | SDKROOT = macosx; 309 | }; 310 | name = Release; 311 | }; 312 | 55ABCB5319881CA600B03F31 /* Debug */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | }; 317 | name = Debug; 318 | }; 319 | 55ABCB5419881CA600B03F31 /* Release */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | }; 324 | name = Release; 325 | }; 326 | /* End XCBuildConfiguration section */ 327 | 328 | /* Begin XCConfigurationList section */ 329 | 55708CC51B308025002D62A8 /* Build configuration list for PBXNativeTarget "insert_dylib_tests" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 55708CC31B308025002D62A8 /* Debug */, 333 | 55708CC41B308025002D62A8 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | }; 337 | 55ABCB4419881CA600B03F31 /* Build configuration list for PBXProject "insert_dylib" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | 55ABCB5019881CA600B03F31 /* Debug */, 341 | 55ABCB5119881CA600B03F31 /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | 55ABCB5219881CA600B03F31 /* Build configuration list for PBXNativeTarget "insert_dylib" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | 55ABCB5319881CA600B03F31 /* Debug */, 350 | 55ABCB5419881CA600B03F31 /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | /* End XCConfigurationList section */ 356 | }; 357 | rootObject = 55ABCB4119881CA600B03F31 /* Project object */; 358 | } 359 | -------------------------------------------------------------------------------- /insert_dylib/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define IS_64_BIT(x) ((x) == MH_MAGIC_64 || (x) == MH_CIGAM_64) 15 | #define IS_LITTLE_ENDIAN(x) ((x) == FAT_CIGAM || (x) == MH_CIGAM_64 || (x) == MH_CIGAM) 16 | #define SWAP32(x, magic) (IS_LITTLE_ENDIAN(magic)? OSSwapInt32(x): (x)) 17 | #define SWAP64(x, magic) (IS_LITTLE_ENDIAN(magic)? OSSwapInt64(x): (x)) 18 | 19 | #define ROUND_UP(x, y) (((x) + (y) - 1) & -(y)) 20 | 21 | #define ABSDIFF(x, y) ((x) > (y)? (uintmax_t)(x) - (uintmax_t)(y): (uintmax_t)(y) - (uintmax_t)(x)) 22 | 23 | #define BUFSIZE 512 24 | 25 | void fbzero(FILE *f, off_t offset, size_t len) { 26 | static unsigned char zeros[BUFSIZE] = {0}; 27 | fseeko(f, offset, SEEK_SET); 28 | while(len != 0) { 29 | size_t size = MIN(len, sizeof(zeros)); 30 | fwrite(zeros, size, 1, f); 31 | len -= size; 32 | } 33 | } 34 | 35 | void fmemmove(FILE *f, off_t dst, off_t src, size_t len) { 36 | static unsigned char buf[BUFSIZE]; 37 | while(len != 0) { 38 | size_t size = MIN(len, sizeof(buf)); 39 | fseeko(f, src, SEEK_SET); 40 | fread(&buf, size, 1, f); 41 | fseeko(f, dst, SEEK_SET); 42 | fwrite(buf, size, 1, f); 43 | 44 | len -= size; 45 | src += size; 46 | dst += size; 47 | } 48 | } 49 | 50 | int inplace_flag = false; 51 | int weak_flag = false; 52 | int overwrite_flag = false; 53 | int codesig_flag = 0; 54 | int yes_flag = false; 55 | 56 | static struct option long_options[] = { 57 | {"inplace", no_argument, &inplace_flag, true}, 58 | {"weak", no_argument, &weak_flag, true}, 59 | {"overwrite", no_argument, &overwrite_flag, true}, 60 | {"strip-codesig", no_argument, &codesig_flag, 1}, 61 | {"no-strip-codesig", no_argument, &codesig_flag, 2}, 62 | {"all-yes", no_argument, &yes_flag, true}, 63 | {NULL, 0, NULL, 0} 64 | }; 65 | 66 | __attribute__((noreturn)) void usage(void) { 67 | printf("Usage: insert_dylib dylib_path binary_path [new_binary_path]\n"); 68 | 69 | printf("Option flags:"); 70 | 71 | struct option *opt = long_options; 72 | while(opt->name != NULL) { 73 | printf(" --%s", opt->name); 74 | opt++; 75 | } 76 | 77 | printf("\n"); 78 | 79 | exit(1); 80 | } 81 | 82 | __attribute__((format(printf, 1, 2))) bool ask(const char *format, ...) { 83 | char *question; 84 | asprintf(&question, "%s [y/n] ", format); 85 | 86 | va_list args; 87 | va_start(args, format); 88 | vprintf(question, args); 89 | va_end(args); 90 | 91 | free(question); 92 | 93 | while(true) { 94 | char *line = NULL; 95 | size_t size; 96 | if(yes_flag) { 97 | puts("y"); 98 | line = "y"; 99 | } else { 100 | getline(&line, &size, stdin); 101 | } 102 | 103 | switch(line[0]) { 104 | case 'y': 105 | case 'Y': 106 | return true; 107 | break; 108 | case 'n': 109 | case 'N': 110 | return false; 111 | break; 112 | default: 113 | printf("Please enter y or n: "); 114 | } 115 | } 116 | } 117 | 118 | size_t fpeek(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream) { 119 | off_t pos = ftello(stream); 120 | size_t result = fread(ptr, size, nitems, stream); 121 | fseeko(stream, pos, SEEK_SET); 122 | return result; 123 | } 124 | 125 | void *read_load_command(FILE *f, uint32_t cmdsize) { 126 | void *lc = malloc(cmdsize); 127 | 128 | fpeek(lc, cmdsize, 1, f); 129 | 130 | return lc; 131 | } 132 | 133 | bool check_load_commands(FILE *f, struct mach_header *mh, size_t header_offset, size_t commands_offset, const char *dylib_path, off_t *slice_size) { 134 | fseeko(f, commands_offset, SEEK_SET); 135 | 136 | uint32_t ncmds = SWAP32(mh->ncmds, mh->magic); 137 | 138 | off_t linkedit_32_pos = -1; 139 | off_t linkedit_64_pos = -1; 140 | struct segment_command linkedit_32; 141 | struct segment_command_64 linkedit_64; 142 | 143 | off_t symtab_pos = -1; 144 | uint32_t symtab_size = 0; 145 | 146 | for(int i = 0; i < ncmds; i++) { 147 | struct load_command lc; 148 | fpeek(&lc, sizeof(lc), 1, f); 149 | 150 | uint32_t cmdsize = SWAP32(lc.cmdsize, mh->magic); 151 | uint32_t cmd = SWAP32(lc.cmd, mh->magic); 152 | 153 | switch(cmd) { 154 | case LC_CODE_SIGNATURE: 155 | if(i == ncmds - 1) { 156 | if(codesig_flag == 2) { 157 | return true; 158 | } 159 | 160 | if(codesig_flag == 0 && !ask("LC_CODE_SIGNATURE load command found. Remove it?")) { 161 | return true; 162 | } 163 | 164 | struct linkedit_data_command *cmd = read_load_command(f, cmdsize); 165 | 166 | fbzero(f, ftello(f), cmdsize); 167 | 168 | uint32_t dataoff = SWAP32(cmd->dataoff, mh->magic); 169 | uint32_t datasize = SWAP32(cmd->datasize, mh->magic); 170 | 171 | free(cmd); 172 | 173 | uint64_t linkedit_fileoff = 0; 174 | uint64_t linkedit_filesize = 0; 175 | 176 | if(linkedit_32_pos != -1) { 177 | linkedit_fileoff = SWAP32(linkedit_32.fileoff, mh->magic); 178 | linkedit_filesize = SWAP32(linkedit_32.filesize, mh->magic); 179 | } else if(linkedit_64_pos != -1) { 180 | linkedit_fileoff = SWAP64(linkedit_64.fileoff, mh->magic); 181 | linkedit_filesize = SWAP64(linkedit_64.filesize, mh->magic); 182 | } else { 183 | fprintf(stderr, "Warning: __LINKEDIT segment not found.\n"); 184 | } 185 | 186 | if(linkedit_32_pos != -1 || linkedit_64_pos != -1) { 187 | if(linkedit_fileoff + linkedit_filesize != *slice_size) { 188 | fprintf(stderr, "Warning: __LINKEDIT segment is not at the end of the file, so codesign will not work on the patched binary.\n"); 189 | } else { 190 | if(dataoff + datasize != *slice_size) { 191 | fprintf(stderr, "Warning: Codesignature is not at the end of __LINKEDIT segment, so codesign will not work on the patched binary.\n"); 192 | } else { 193 | *slice_size -= datasize; 194 | //int64_t diff_size = 0; 195 | if(symtab_pos == -1) { 196 | fprintf(stderr, "Warning: LC_SYMTAB load command not found. codesign might not work on the patched binary.\n"); 197 | } else { 198 | fseeko(f, symtab_pos, SEEK_SET); 199 | struct symtab_command *symtab = read_load_command(f, symtab_size); 200 | 201 | uint32_t strsize = SWAP32(symtab->strsize, mh->magic); 202 | int64_t diff_size = SWAP32(symtab->stroff, mh->magic) + strsize - (int64_t)*slice_size; 203 | if(-0x10 <= diff_size && diff_size <= 0) { 204 | symtab->strsize = SWAP32((uint32_t)(strsize - diff_size), mh->magic); 205 | fwrite(symtab, symtab_size, 1, f); 206 | } else { 207 | fprintf(stderr, "Warning: String table doesn't appear right before code signature. codesign might not work on the patched binary. (0x%llx)\n", diff_size); 208 | } 209 | 210 | free(symtab); 211 | } 212 | 213 | linkedit_filesize -= datasize; 214 | uint64_t linkedit_vmsize = ROUND_UP(linkedit_filesize, 0x1000); 215 | 216 | if(linkedit_32_pos != -1) { 217 | linkedit_32.filesize = SWAP32((uint32_t)linkedit_filesize, mh->magic); 218 | linkedit_32.vmsize = SWAP32((uint32_t)linkedit_vmsize, mh->magic); 219 | 220 | fseeko(f, linkedit_32_pos, SEEK_SET); 221 | fwrite(&linkedit_32, sizeof(linkedit_32), 1, f); 222 | } else { 223 | linkedit_64.filesize = SWAP64(linkedit_filesize, mh->magic); 224 | linkedit_64.vmsize = SWAP64(linkedit_vmsize, mh->magic); 225 | 226 | fseeko(f, linkedit_64_pos, SEEK_SET); 227 | fwrite(&linkedit_64, sizeof(linkedit_64), 1, f); 228 | } 229 | 230 | goto fix_header; 231 | } 232 | } 233 | } 234 | 235 | // If we haven't truncated the file, zero out the code signature 236 | fbzero(f, header_offset + dataoff, datasize); 237 | 238 | fix_header: 239 | mh->ncmds = SWAP32(ncmds - 1, mh->magic); 240 | mh->sizeofcmds = SWAP32(SWAP32(mh->sizeofcmds, mh->magic) - cmdsize, mh->magic); 241 | 242 | return true; 243 | } else { 244 | printf("LC_CODE_SIGNATURE is not the last load command, so couldn't remove.\n"); 245 | } 246 | break; 247 | case LC_LOAD_DYLIB: 248 | case LC_LOAD_WEAK_DYLIB: { 249 | struct dylib_command *dylib_command = read_load_command(f, cmdsize); 250 | 251 | union lc_str offset = dylib_command->dylib.name; 252 | char *name = &((char *)dylib_command)[SWAP32(offset.offset, mh->magic)]; 253 | 254 | int cmp = strcmp(name, dylib_path); 255 | 256 | free(dylib_command); 257 | 258 | if(cmp == 0) { 259 | if(!ask("Binary already contains a load command for that dylib. Continue anyway?")) { 260 | return false; 261 | } 262 | } 263 | 264 | break; 265 | } 266 | case LC_SEGMENT: 267 | case LC_SEGMENT_64: 268 | if(cmd == LC_SEGMENT) { 269 | struct segment_command *cmd = read_load_command(f, cmdsize); 270 | if(strcmp(cmd->segname, "__LINKEDIT") == 0) { 271 | linkedit_32_pos = ftello(f); 272 | linkedit_32 = *cmd; 273 | } 274 | free(cmd); 275 | } else { 276 | struct segment_command_64 *cmd = read_load_command(f, cmdsize); 277 | if(strcmp(cmd->segname, "__LINKEDIT") == 0) { 278 | linkedit_64_pos = ftello(f); 279 | linkedit_64 = *cmd; 280 | } 281 | free(cmd); 282 | } 283 | case LC_SYMTAB: 284 | symtab_pos = ftello(f); 285 | symtab_size = cmdsize; 286 | } 287 | 288 | fseeko(f, SWAP32(lc.cmdsize, mh->magic), SEEK_CUR); 289 | } 290 | 291 | return true; 292 | } 293 | 294 | bool insert_dylib(FILE *f, size_t header_offset, const char *dylib_path, off_t *slice_size) { 295 | fseeko(f, header_offset, SEEK_SET); 296 | 297 | struct mach_header mh; 298 | fread(&mh, sizeof(struct mach_header), 1, f); 299 | 300 | if(mh.magic != MH_MAGIC_64 && mh.magic != MH_CIGAM_64 && mh.magic != MH_MAGIC && mh.magic != MH_CIGAM) { 301 | printf("Unknown magic: 0x%x\n", mh.magic); 302 | return false; 303 | } 304 | 305 | size_t commands_offset = header_offset + (IS_64_BIT(mh.magic)? sizeof(struct mach_header_64): sizeof(struct mach_header)); 306 | 307 | bool cont = check_load_commands(f, &mh, header_offset, commands_offset, dylib_path, slice_size); 308 | 309 | if(!cont) { 310 | return true; 311 | } 312 | 313 | // Even though a padding of 4 works for x86_64, codesign doesn't like it 314 | size_t path_padding = 8; 315 | 316 | size_t dylib_path_len = strlen(dylib_path); 317 | size_t dylib_path_size = (dylib_path_len & ~(path_padding - 1)) + path_padding; 318 | uint32_t cmdsize = (uint32_t)(sizeof(struct dylib_command) + dylib_path_size); 319 | 320 | struct dylib_command dylib_command = { 321 | .cmd = SWAP32(weak_flag? LC_LOAD_WEAK_DYLIB: LC_LOAD_DYLIB, mh.magic), 322 | .cmdsize = SWAP32(cmdsize, mh.magic), 323 | .dylib = { 324 | .name = SWAP32(sizeof(struct dylib_command), mh.magic), 325 | .timestamp = 0, 326 | .current_version = 0, 327 | .compatibility_version = 0 328 | } 329 | }; 330 | 331 | uint32_t sizeofcmds = SWAP32(mh.sizeofcmds, mh.magic); 332 | 333 | fseeko(f, commands_offset + sizeofcmds, SEEK_SET); 334 | char space[cmdsize]; 335 | 336 | fread(&space, cmdsize, 1, f); 337 | 338 | bool empty = true; 339 | for(int i = 0; i < cmdsize; i++) { 340 | if(space[i] != 0) { 341 | empty = false; 342 | break; 343 | } 344 | } 345 | 346 | if(!empty) { 347 | if(!ask("It doesn't seem like there is enough empty space. Continue anyway?")) { 348 | return false; 349 | } 350 | } 351 | 352 | fseeko(f, -((off_t)cmdsize), SEEK_CUR); 353 | 354 | char *dylib_path_padded = calloc(dylib_path_size, 1); 355 | memcpy(dylib_path_padded, dylib_path, dylib_path_len); 356 | 357 | fwrite(&dylib_command, sizeof(dylib_command), 1, f); 358 | fwrite(dylib_path_padded, dylib_path_size, 1, f); 359 | 360 | free(dylib_path_padded); 361 | 362 | mh.ncmds = SWAP32(SWAP32(mh.ncmds, mh.magic) + 1, mh.magic); 363 | sizeofcmds += cmdsize; 364 | mh.sizeofcmds = SWAP32(sizeofcmds, mh.magic); 365 | 366 | fseeko(f, header_offset, SEEK_SET); 367 | fwrite(&mh, sizeof(mh), 1, f); 368 | 369 | return true; 370 | } 371 | 372 | int main(int argc, const char *argv[]) { 373 | while(true) { 374 | int option_index = 0; 375 | 376 | int c = getopt_long(argc, (char *const *)argv, "", long_options, &option_index); 377 | 378 | if(c == -1) { 379 | break; 380 | } 381 | 382 | switch(c) { 383 | case 0: 384 | break; 385 | case '?': 386 | usage(); 387 | break; 388 | default: 389 | abort(); 390 | break; 391 | } 392 | } 393 | 394 | argv = &argv[optind - 1]; 395 | argc -= optind - 1; 396 | 397 | if(argc < 3 || argc > 4) { 398 | usage(); 399 | } 400 | 401 | const char *lc_name = weak_flag? "LC_LOAD_WEAK_DYLIB": "LC_LOAD_DYLIB"; 402 | 403 | const char *dylib_path = argv[1]; 404 | const char *binary_path = argv[2]; 405 | 406 | struct stat s; 407 | 408 | if(stat(binary_path, &s) != 0) { 409 | perror(binary_path); 410 | exit(1); 411 | } 412 | 413 | if(dylib_path[0] != '@' && stat(dylib_path, &s) != 0) { 414 | if(!ask("The provided dylib path doesn't exist. Continue anyway?")) { 415 | exit(1); 416 | } 417 | } 418 | 419 | bool binary_path_was_malloced = false; 420 | if(!inplace_flag) { 421 | char *new_binary_path; 422 | if(argc == 4) { 423 | new_binary_path = (char *)argv[3]; 424 | } else { 425 | asprintf(&new_binary_path, "%s_patched", binary_path); 426 | binary_path_was_malloced = true; 427 | } 428 | 429 | if(!overwrite_flag && stat(new_binary_path, &s) == 0) { 430 | if(!ask("%s already exists. Overwrite it?", new_binary_path)) { 431 | exit(1); 432 | } 433 | } 434 | 435 | if(copyfile(binary_path, new_binary_path, NULL, COPYFILE_DATA | COPYFILE_UNLINK)) { 436 | printf("Failed to create %s\n", new_binary_path); 437 | exit(1); 438 | } 439 | 440 | binary_path = new_binary_path; 441 | } 442 | 443 | FILE *f = fopen(binary_path, "r+"); 444 | 445 | if(!f) { 446 | printf("Couldn't open file %s\n", binary_path); 447 | exit(1); 448 | } 449 | 450 | bool success = true; 451 | 452 | fseeko(f, 0, SEEK_END); 453 | off_t file_size = ftello(f); 454 | rewind(f); 455 | 456 | uint32_t magic; 457 | fread(&magic, sizeof(uint32_t), 1, f); 458 | 459 | switch(magic) { 460 | case FAT_MAGIC: 461 | case FAT_CIGAM: { 462 | fseeko(f, 0, SEEK_SET); 463 | 464 | struct fat_header fh; 465 | fread(&fh, sizeof(fh), 1, f); 466 | 467 | uint32_t nfat_arch = SWAP32(fh.nfat_arch, magic); 468 | 469 | printf("Binary is a fat binary with %d archs.\n", nfat_arch); 470 | 471 | struct fat_arch archs[nfat_arch]; 472 | fread(archs, sizeof(archs), 1, f); 473 | 474 | int fails = 0; 475 | 476 | uint32_t offset = 0; 477 | if(nfat_arch > 0) { 478 | offset = SWAP32(archs[0].offset, magic); 479 | } 480 | 481 | for(int i = 0; i < nfat_arch; i++) { 482 | off_t orig_offset = SWAP32(archs[i].offset, magic); 483 | off_t orig_slice_size = SWAP32(archs[i].size, magic); 484 | offset = ROUND_UP(offset, 1 << SWAP32(archs[i].align, magic)); 485 | if(orig_offset != offset) { 486 | fmemmove(f, offset, orig_offset, orig_slice_size); 487 | fbzero(f, MIN(offset, orig_offset) + orig_slice_size, ABSDIFF(offset, orig_offset)); 488 | 489 | archs[i].offset = SWAP32(offset, magic); 490 | } 491 | 492 | off_t slice_size = orig_slice_size; 493 | bool r = insert_dylib(f, offset, dylib_path, &slice_size); 494 | if(!r) { 495 | printf("Failed to add %s to arch #%d!\n", lc_name, i + 1); 496 | fails++; 497 | } 498 | 499 | if(slice_size < orig_slice_size && i < nfat_arch - 1) { 500 | fbzero(f, offset + slice_size, orig_slice_size - slice_size); 501 | } 502 | 503 | file_size = offset + slice_size; 504 | offset += slice_size; 505 | archs[i].size = SWAP32((uint32_t)slice_size, magic); 506 | } 507 | 508 | rewind(f); 509 | fwrite(&fh, sizeof(fh), 1, f); 510 | fwrite(archs, sizeof(archs), 1, f); 511 | 512 | // We need to flush before truncating 513 | fflush(f); 514 | ftruncate(fileno(f), file_size); 515 | 516 | if(fails == 0) { 517 | printf("Added %s to all archs in %s\n", lc_name, binary_path); 518 | } else if(fails == nfat_arch) { 519 | printf("Failed to add %s to any archs.\n", lc_name); 520 | success = false; 521 | } else { 522 | printf("Added %s to %d/%d archs in %s\n", lc_name, nfat_arch - fails, nfat_arch, binary_path); 523 | } 524 | 525 | break; 526 | } 527 | case MH_MAGIC_64: 528 | case MH_CIGAM_64: 529 | case MH_MAGIC: 530 | case MH_CIGAM: 531 | if(insert_dylib(f, 0, dylib_path, &file_size)) { 532 | ftruncate(fileno(f), file_size); 533 | printf("Added %s to %s\n", lc_name, binary_path); 534 | } else { 535 | printf("Failed to add %s!\n", lc_name); 536 | success = false; 537 | } 538 | break; 539 | default: 540 | printf("Unknown magic: 0x%x\n", magic); 541 | exit(1); 542 | } 543 | 544 | fclose(f); 545 | 546 | if(!success) { 547 | if(!inplace_flag) { 548 | unlink(binary_path); 549 | } 550 | exit(1); 551 | } 552 | 553 | if(binary_path_was_malloced) { 554 | free((void *)binary_path); 555 | } 556 | 557 | return 0; 558 | } 559 | --------------------------------------------------------------------------------