查看了一下目前常用的一些动态链接库注入工具,基本都是将动态库注入到Load Commands块的末尾。
突发奇想,希望能将动态库注入到Load Commands块中的任意位置,也就是能达到随意调整Load Commands块中各个命令的顺序。
于是拿optool下手,一通梭哈注入之后,使用MachOView查看结构:
“成功”将libdyldDylib.dylib库注入到CoreImage之前,但重打包安装应用打开闪退,提示:
Termination Description: DYLD, Symbol not found: OBJC_CLASS$_CIColor | Referenced from: /private/var/containers/Bundle/Application/48E82F7C-975C-4B88-8147-8E1499AAE259/ZZ.app/ZZ | Expected in: /private/var/containers/Bundle/Application/48E82F7C-975C-4B88-8147-8E1499AAE259/ZZ.app/Frameworks/libdyldDylib.dylib | in /private/var/containers/Bundle/Application/48E82F7C-975C-4B88-8147-8E1499AAE259/ZZ.app/ZZ
意思是libdyldDylib.dylib占用了原本CoreImage的位置,导致程序还是向原本CoreImage所处的位置继续寻找CoreImage库中的CIColor类,当然找不到
optool主要源码改动如下:增加一个新的注入函数insertLoadEntryIntoBinaryBefor
BOOL insertLoadEntryIntoBinaryBefor(NSString *dylibPath, NSString *targetSubDylib,NSMutableData *binary, struct thin_header macho, uint32_t type) {
if (type != LC_REEXPORT_DYLIB &&
type != LC_LOAD_WEAK_DYLIB &&
type != LC_LOAD_UPWARD_DYLIB &&
type != LC_LOAD_DYLIB) {
LOG("Invalid load command type");
return NO;
}
// parse load commands to see if our load command is already there
uint32_t insertPosition = 0;
if (binaryHasLoadCommandForDylib(binary, dylibPath, &insertPosition, macho)) {
// there already exists a load command for this payload so change the command type
uint32_t originalType = *(uint32_t *)(binary.bytes + insertPosition);
if (originalType != type) {
LOG("A load command already exists for %s. Changing command type from %s to desired %s", dylibPath.UTF8String, LC(originalType), LC(type));
[binary replaceBytesInRange:NSMakeRange(insertPosition, sizeof(type)) withBytes:&type];
} else {
LOG("Load command already exists");
}
return YES;
}
binary.currentOffset = macho.offset + macho.size;
NSMutableArray *cmds = [NSMutableArray array];
BOOL isFind = NO;
for (int i = 0; i < macho.header.ncmds; i++) {//遍历所有的命令
if (binary.currentOffset >= binary.length ||
binary.currentOffset > macho.offset + macho.size + macho.header.sizeofcmds)
break;
uint32_t cmd = [binary intAtOffset:binary.currentOffset];
uint32_t size = [binary intAtOffset:binary.currentOffset + 4];
if (isFind) {
NSData *da = [binary subdataWithRange:NSMakeRange(binary.currentOffset,size)];
[cmds addObject:da];
binary.currentOffset += size;
continue;
}
switch (cmd) {
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_LOAD_DYLIB: {
struct dylib_command command = *(struct dylib_command *)(binary.bytes + binary.currentOffset);
char *name = (char *)[[binary subdataWithRange:NSMakeRange(binary.currentOffset + command.dylib.name.offset, command.cmdsize - command.dylib.name.offset)] bytes];
if ([@(name) containsString:targetSubDylib]) {
//记录新插入dylib库的位置
NSLog(@"target dylib--%@",@(name));
insertPosition = (uint32_t)binary.currentOffset;
isFind = YES;
NSData *da = [binary subdataWithRange:NSMakeRange(binary.currentOffset,size)];
[cmds addObject:da];
}
break;
}
default:
break;
}
binary.currentOffset += size;
}
// create a new load command
unsigned int length = (unsigned int)sizeof(struct dylib_command) + (unsigned int)dylibPath.length;
//cmd大小是 long 对齐的 long长整型 8字节
unsigned int padding = (8 - (length % 8));
//position位置是Load Commands块的末端位置
uint32_t position = macho.offset + macho.size + macho.header.sizeofcmds;
NSData *occupant = [binary subdataWithRange:NSMakeRange(position,
length + padding)];
// All operations in optool try to maintain a constant byte size of the executable
// so we don't want to append new bytes to the binary (that would break the executable
// since everything is offset-based–we'd have to go in and adjust every offset)
// So instead take advantage of the huge amount of padding after the load commands
if (strcmp([occupant bytes], "\0")) {
NSLog(@"cannot inject payload into %s because there is no room", dylibPath.fileSystemRepresentation);
return NO;
}
LOG("Inserting a %s command for architecture: %s", LC(type), CPU(macho.header.cputype));
struct dylib_command command;
struct dylib dylib;
dylib.name.offset = sizeof(struct dylib_command);
dylib.timestamp = 2; // load commands I've seen use 2 for some reason
dylib.current_version = 0;
dylib.compatibility_version = 0;
command.cmd = type;
command.dylib = dylib;
command.cmdsize = length + padding;
unsigned int zeroByte = 0;
NSMutableData *commandData = [NSMutableData data];
[commandData appendBytes:&command length:sizeof(struct dylib_command)];
[commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]];
[commandData appendBytes:&zeroByte length:padding];
// remove enough null bytes to account of our inserted data
[binary replaceBytesInRange:NSMakeRange(insertPosition, commandData.length)
withBytes:0
length:0];
// insert the data
[binary replaceBytesInRange:NSMakeRange(insertPosition, 0) withBytes:commandData.bytes length:commandData.length];
//将注入位置后面所有的命令重新写入一遍
insertPosition += commandData.length;
if (isFind && cmds.count > 0) {
for(int i=0;i<cmds.count; i++) {
NSData *da = cmds[i];
[binary replaceBytesInRange:NSMakeRange(insertPosition, da.length)
withBytes:0
length:0];
[binary replaceBytesInRange:NSMakeRange(insertPosition, 0) withBytes:da.bytes length:da.length];
insertPosition += da.length;
}
}
// fix the existing header
macho.header.ncmds += 1;
macho.header.sizeofcmds += command.cmdsize;
// this is safe to do in 32bit because the 4 bytes after the header are still being put back
[binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header];
return YES;}
姿势哪里不对?求大佬支招啊!