// we pass around this header which includes some extra information // and a 32-bit header which we used for both 32-bit and 64-bit files // since the 64-bit just adds an extra field to the end which we don't need struct thin_header { uint32_t offset; uint32_t size; struct mach_header header; };
// 这部分的逻辑主要是检查macho文件的魔数字段,然后确定是FAT,还是单独架构的文件,然后将填充到自定义的header结构体 struct thin_header *headersFromBinary(struct thin_header *headers, NSData *binary, uint32_t *amount) { // In a MachO/FAT binary the first 4 bytes is a magic number // which gives details about the type of binary it is // CIGAM and co. mean the target binary has a byte order // in reverse relation to the host machine so we have to swap the bytes uint32_t magic = [binary intAtOffset:0]; bool shouldSwap = magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM; #define SWAP(NUM) shouldSwap ? CFSwapInt32(NUM) : NUM uint32_t numArchs = 0;
// a FAT file is basically a collection of thin MachO binaries if (magic == FAT_CIGAM || magic == FAT_MAGIC) { LOG("Found FAT Header"); // WE GOT A FAT ONE struct fat_header fat = *(struct fat_header *)binary.bytes; fat.nfat_arch = SWAP(fat.nfat_arch); int offset = sizeof(struct fat_header);
// Loop through the architectures within the FAT binary to find // a thin macho header that we can work with (x86 or x86_64) for (int i = 0; i < fat.nfat_arch; i++) { struct fat_arch arch; arch = *(struct fat_arch *)([binary bytes] + offset); arch.cputype = SWAP(arch.cputype); arch.offset = SWAP(arch.offset);
BOOL insertLoadEntryIntoBinary(NSString *dylibPath, 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"); returnNO; } // parse load commands to see if our load command is already there uint32_t lastOffset = 0; // 是否重复添加 if (binaryHasLoadCommandForDylib(binary, dylibPath, &lastOffset, macho)) { // there already exists a load command for this payload so change the command type uint32_t originalType = *(uint32_t *)(binary.bytes + lastOffset); 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(lastOffset, sizeof(type)) withBytes:&type]; } else { LOG("Load command already exists"); } returnYES; } // create a new load command // 新添加,创建加载动态库命令 unsignedint length = (unsignedint)sizeof(struct dylib_command) + (unsignedint)dylibPath.length; // 我添加的dylibPath.length = 51,整个length = 75, padding = 5 unsignedint padding = (8 - (length % 8)); // check if data we are replacing is null // 获取要添加command的位置的内容 NSData *occupant = [binary subdataWithRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size, 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 // 比较如果非空,说明到了代码的内容部分,就有问题。添加命令只能在Load Command后面且在代码内容前 if (strcmp([occupant bytes], "\0")) { NSLog(@"cannot inject payload into %s because there is no room", dylibPath.fileSystemRepresentation); returnNO; } LOG("Inserting a %s command for architecture: %s", LC(type), CPU(macho.header.cputype)); struct dylib_command command; struct dylib dylib; /* * A variable length string in a load command is represented by an lc_str * union. The strings are stored just after the load command structure and * the offset is from the start of the load command structure. The size * of the string is reflected in the cmdsize field of the load command. * Once again any padded bytes to bring the cmdsize field to a multiple * of 4 bytes must be zero. * lc_str 的注释 */ // 所以offset是dylib command的大小 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; unsignedint zeroByte = 0; NSMutableData *commandData = [NSMutableData data]; // 动态库 command的内容 [commandData appendBytes:&command length:sizeof(struct dylib_command)]; // 动态库的路径字符串 [commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]]; // 默认补0 [commandData appendBytes:&zeroByte length:padding]; // remove enough null bytes to account of our inserted data // 这部分内容用0覆盖,最后一个参数传0就是指定覆盖区间就是Range的length [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, commandData.length) withBytes:0 length:0]; // insert the data // 添加data [binary replaceBytesInRange:NSMakeRange(lastOffset, 0) withBytes:commandData.bytes length:commandData.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 // 替换mach的header部分 [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&macho.header]; returnYES; }