optool注入工具二次改进失败

查看了一下目前常用的一些动态链接库注入工具,基本都是将动态库注入到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;}

姿势哪里不对?求大佬支招啊!

2 Likes

我觉得你的方向错了 :thinking:

意思是无法达到任意位置注入的目的?

链接库的顺序和每个函数的相对地址是编译时就确定好的。
我感觉如果改注入顺序的话,并不是只改LC_LOAD_DYLIB头就够的。
包括后面所有函数的相对地址都要跟着变才行。

1 Like

同时改下__symbol_stub和__nl_symbol_ptr试试。

明白了,后面试试

可以,可以

大佬现在修改optool能实现注入到指定位置不