SResigner的实现:一款集动态库注入与删除、ipa重签名、ipa元数据修改为一体的MacOS App

背景介绍

近两年,除了正常的开发任务,业余时间也会研究一些逆向开发之类的技术,也小打小闹的写过一些插件,每次插件写完都会涉及到动态库的增删、重签名操作,很多时候还要帮朋友解决重签名后安装不上或启动崩溃的问题,每次处理这些问题的时候,不同的问题会用到不同的工具,而工具也不是万能的,不趁手了还得自己去改,这种事一次两次还好,做多了难免嫌麻烦,于是就萌生了一个想法:为什么不把这些操作都做在一个工具里呢?于是,SResigner 就诞生了,SResigner 将逆向开发中"打包"这个步骤中常见的操作整合到了一起:Mach-O文件的动态库编辑、IPA元数据修改、重签名导出。下文主要是对 SResigner 其中部分功能的实现做一些说明。

动态库的注入与删除

要完成这两个功能,主要涉及到对Mach-O文件 Load Commands区域的修改,关于到Mach-O文件的格式,网上已经有太多太多的介绍, 这里就不再赘述了,通过对Mach-O文件的编辑,可以完成许多有意思的功能,比如:启动速度优化、App瘦身、去除签名信息等,这里主要介绍下针对Mach-O文件中动态库链接信息的编辑。

因为我们只关注动态库部分的处理,所以下面我画了一张处理过程中涉及到的各个数据结构在Mach-O文件中的布局,图中左侧是 struct 在Mach-O文件中的布局,右侧是 struct 的定义:

(64): 表示该结构体有64位版本,由于篇幅关系,64位版本的struct并未在图中标明。

Mach-O结构

注入

首先我们先看下如何读取所有的 load_command,有了上面的图片,读取 load_command 的过程就比较清晰了:

  1. 读取魔数(文件的前4个字节)
  2. 根据魔数判断是否是fat版的Mach-O(注:这里按fat版本的处理流程讲解,thin版本的处理流程只比fat版少一个fat_header和fat_arch的处理)
  3. 读取到 fat_header
  4. 根据 fat_header 中的 nfat_arch 读取到所有的 fat_arch
  5. 读取到 fat_arch 后,就可以根据 fat_arch->offset获取到 mach_header 的起始位置,接着就可以读取 mach_header
  6. 读取到 mach_header后,就可以根据 mach_header 中的 ncmds字段(number of load commands)读取到所有的 load_command
  7. 读取到 load_command 后,就可以根据 load_command 中的 cmd字段(type of load command)判断该load_command 是否是 LC_LOAD_DYLIB 类型。
  8. 读取到 LC_LOAD_DYLIB 类型的 load_command 后,就可以根据 load_command 的 offset(这个值是从上面的步骤计算出来的) 和 load_command->cmdsize 获取到 dylib_command

上述流程是主要逻辑,其中会有一些诸大小端转换、64位架构的细节处理,下面给出完整的示例Demo:

本文中给出的Demo并未有过多的封装,主要是为了让读者能够把关注点放在处理流程上,所有Demo都是复制出来即可运行的。

//  main.m

#import <Foundation/Foundation.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach-o/fat.h>
//MARK: - 定义一些工具方法
BOOL isFatOfMagic(uint32 magic) {
    return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
BOOL isMagic64(uint32 magic) {
    return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
BOOL shouldSwapBytesOfMagic(uint32 magic) {
    return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64;
}
BOOL isValidMachoOfMagic(uint32 magic){
    return magic == FAT_MAGIC || magic == FAT_CIGAM || magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
@interface NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
- (const void *)readBytesFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
@end

@implementation NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length{
    [self seekToFileOffset:offset];
    return [self readDataOfLength:length];
}
- (const void *)readBytesFromOffSet:(unsigned long long)offset length:(NSUInteger)length{
    return [[self readDataFromOffSet:offset length:length] bytes];
}
@end

//Demo 主逻辑
int main(int argc, const char * argv[]) {
    NSString * machoFilePath = @"/Users/jerry/Desktop/testMacho";
    NSFileHandle * machoFileHandle = [NSFileHandle fileHandleForReadingAtPath: machoFilePath];
    /* 读取魔数(前4个字节)
     要注意的是,后面还有一个魔数,是MachO部分的,此处是针对 fat_arch 和 fat_header 相关信息的。
     后面的是用于某个arch下的 MachO 的。
     */
    uint32 magic = *((uint32 *)[machoFileHandle readBytesFromOffSet:0 length:4]);
    //thin 还是 fat 版本
    BOOL isFat = isFatOfMagic(magic);
    //是否是64位
    BOOL is64 = isMagic64(magic);
    //大小端转换判断
    BOOL shouldSwap = shouldSwapBytesOfMagic(magic);
    
    if(isFat){
        int fat_header_size = sizeof(struct fat_header);
        struct fat_header * fatHeader = (struct fat_header *)[machoFileHandle readBytesFromOffSet:0 length:fat_header_size];
        if (shouldSwap) {
            swap_fat_header(fatHeader, 0);
        }
        int arch_offset = fat_header_size;
        uint64_t machHeaderOffset = 0;
        //遍历所有 arch
        for (int i = 0; i < fatHeader->nfat_arch; i++) {
            if(is64){
                int fat_arch_size = sizeof(struct fat_arch_64);
                struct fat_arch_64 * arch = (struct fat_arch_64 *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    //大小端转换
                    swap_fat_arch_64(arch, 1, 0);
                }
                //保存 mach header 的起始位置
                machHeaderOffset = arch->offset;
                //计算下一个 fat_arch 的起始位置
                arch_offset += fat_arch_size;
                NSLog(@"------ arch cpu type: %d ------", arch->cpusubtype);
            }else{
                int fat_arch_size = sizeof(struct fat_arch);
                struct fat_arch * arch = (struct fat_arch *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    swap_fat_arch(arch, 1, 0);
                }
                machHeaderOffset = arch->offset;
                arch_offset += fat_arch_size;
                NSLog(@"------ arch cpu type: %d ------", arch->cpusubtype);
            }
            
            /*------ 开始读取 mach header ------*/
            //读取魔数(前4个字节)
            uint32 machMagic = *((uint32 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length:4]);
            BOOL isMach64 = isMagic64(machMagic);
            BOOL shoudMachSwap = shouldSwapBytesOfMagic(machMagic);
            
            int ncmds = 0;
            long loadCommandsOffset = 0;
            if(isMach64){
                int machHeaderSize = sizeof(struct mach_header_64);
                struct mach_header_64 * header = (struct mach_header_64 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header_64(header, 0);
                }
                //获取 load commands 的个数
                ncmds = header -> ncmds;
                //load commands的起始位置
                loadCommandsOffset = machHeaderOffset + machHeaderSize;
            }else{
                int machHeaderSize = sizeof(struct mach_header);
                struct mach_header * header = (struct mach_header *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header(header, 0);
                }
                
                ncmds = header -> ncmds;
                loadCommandsOffset = machHeaderOffset + machHeaderSize;
            }
            
            /*------ 开始读取 load command ------*/
            for (int  i = 0; i < ncmds; i++) {
                struct load_command *cmd = (struct load_command *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset length:sizeof(struct load_command)];
                if (shoudMachSwap) {
                    swap_load_command(cmd, 0);
                }
                //如果是 LC_LOAD_DYLIB类型, 则获取 dylib_command
                if (cmd->cmd == LC_LOAD_DYLIB){
                    struct dylib_command * dylibCmd = (struct dylib_command *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset length:cmd->cmdsize];
                    if (shoudMachSwap) {
                        swap_dylib_command(dylibCmd, 0);
                    }
                    //读取 dylib_command 的 name 信息
                    int pathstringLen = dylibCmd->cmdsize -  dylibCmd->dylib.name.offset;
                    char * cPath = (char *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset + dylibCmd->dylib.name.offset length: pathstringLen];
                    NSString * path =  [[NSString alloc]initWithUTF8String:cPath];
                    NSLog(@"%@", path);
                }
                //计算下个 load_command 的起始位置
                loadCommandsOffset += cmd->cmdsize;
            }
        }
    }else{
        //如果是 thin 版本,就直接从上面fat版处理步骤中的 "开始读取 mach header" 开始,这里就不再重复实现了。
        assert(false);
    }
    
    return 0;
}

现在,我们已经知道怎么读取 load_command 了,接着便是想办法添加一个 LC_LOAD_DYLIB 类型的 load_command(即 dylib_command),这里有一个要注意的地方:

无论是注入还是删除动态库链接,都不可以改变整个Mach-O文件的大小。

这是因为在Mach-O文件中有很多内容是靠offset去定位的,如果我们改变了大小,那么那些offset就会失效,这样Mach-O文件就损坏了。

所以,我们不可以添加或删除字节,我们只能覆写无用字节。在研究过程中(其实是参考的yololib这个库),发现 load_command 那块区域的尾部有一些全0的字节区域(这篇文章里我称之为:冗余字节区,上面第一张图里面有标识),
所以,添加 dylib_command 就可以通过覆写这些0字节完成。流程就比较简单了:

  1. 更新mach_header: 将其中的 ncmds 增1,sizeofcmds 增加 dylib_command 结构体大小(因为增加了一个dylib_command,而 mach_header 中存有load_command的信息)
  2. 找到存放 load_command 信息区域的尾部
  3. 创建一个 dylib_command ,覆写从 load_command 区域尾部开始的0字节区域

这种方案的问题是,如果想要覆写的字节数超过了0字节区域的所有字节,就会覆写到后面的正常数据,这样还是会导致Mach-O文件的损坏。
目前从网上查到的各种资料也都是这么实现,所以暂时还没有找到其他更好的方案。
很明显,这样会限制注入库的个数,不过大多数情况下都是够用了,假如真的有很多动态库要注入,那么只能想办法将部分动态库合为一个。

下面给出Demo示例:


//  main.m

#import <Foundation/Foundation.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach-o/fat.h>
//MARK: - 定义一些工具方法
BOOL isFatOfMagic(uint32 magic) {
    return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
BOOL isMagic64(uint32 magic) {
    return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
BOOL shouldSwapBytesOfMagic(uint32 magic) {
    return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64;
}
BOOL isValidMachoOfMagic(uint32 magic){
    return magic == FAT_MAGIC || magic == FAT_CIGAM || magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
@interface NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
- (const void *)readBytesFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
- (void)writeData:(NSData *)data offSet:(unsigned long long)offset;
- (void)writeBytes:(void *)bytes offSet:(unsigned long long)offSet length:(NSUInteger)length;
@end

@implementation NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length{
    [self seekToFileOffset:offset];
    return [self readDataOfLength:length];
}
- (const void *)readBytesFromOffSet:(unsigned long long)offSet length:(NSUInteger)length{
    return [[self readDataFromOffSet:offSet length:length] bytes];
}
- (void)writeData:(NSData *)data offSet:(unsigned long long)offset{
    [self seekToFileOffset:offset];
    [self writeData:data];
}
- (void)writeBytes:(void *)bytes offSet:(unsigned long long)offSet length:(NSUInteger)length{
    NSData * d = [[NSData alloc]initWithBytes:bytes length:length];
    [self writeData:d offSet: offSet];
}
@end

//Demo 主逻辑
int main(int argc, const char * argv[]) {
  
    NSString * machoFilePath = @"/Users/jerry/Desktop/testMacho";
    NSFileHandle * machoFileHandle = [NSFileHandle fileHandleForUpdatingAtPath: machoFilePath];
    /* 读取魔数(前4个字节)
     要注意的是,后面还有一个魔数,是MachO部分的,此处是针对 fat_arch 和 fat_header 相关信息的。
     后面的是用于某个arch下的 MachO 的。
     */
    uint32 magic = *((uint32 *)[machoFileHandle readBytesFromOffSet:0 length:4]);
    //thin 还是 fat 版本
    BOOL isFat = isFatOfMagic(magic);
    //是否是64位
    BOOL is64 = isMagic64(magic);
    //大小端转换判断
    BOOL shouldSwap = shouldSwapBytesOfMagic(magic);
    
    if(isFat){
        int fat_header_size = sizeof(struct fat_header);
        struct fat_header * fatHeader = (struct fat_header *)[machoFileHandle readBytesFromOffSet:0 length:fat_header_size];
        if (shouldSwap) {
            swap_fat_header(fatHeader, 0);
        }
        int arch_offset = fat_header_size;
        uint64_t machHeaderOffset = 0;
        //遍历所有 arch
        for (int i = 0; i < fatHeader->nfat_arch; i++) {
            if(is64){
                int fat_arch_size = sizeof(struct fat_arch_64);
                struct fat_arch_64 * arch = (struct fat_arch_64 *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    //大小端转换
                    swap_fat_arch_64(arch, 1, 0);
                }
                //保存 mach header 的起始位置
                machHeaderOffset = arch->offset;
                //计算下个 fat_arch 的位置
                arch_offset += fat_arch_size;
            }else{
                int fat_arch_size = sizeof(struct fat_arch);
                struct fat_arch * arch = (struct fat_arch *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    swap_fat_arch(arch, 1, 0);
                }
                machHeaderOffset = arch->offset;
                arch_offset += fat_arch_size;
            }
            //创建要添加的 dylib_command
            NSString * dylibLinkStr = @"@executable_path/test.dylib";
            uint32 dylib_size = (uint32)[[dylibLinkStr dataUsingEncoding:NSUTF8StringEncoding] length] + sizeof(struct dylib_command);
            dylib_size += sizeof(long) - (dylib_size % sizeof(long)); //按 long 类型长度对齐
            struct dylib_command dyld;
            dyld.cmd = LC_LOAD_DYLIB;
            dyld.cmdsize = dylib_size;
            dyld.dylib.compatibility_version = 0;
            dyld.dylib.current_version = 0;
            dyld.dylib.timestamp = 0;
            dyld.dylib.name.offset = sizeof(struct dylib_command);
            
            uint32 machMagic = *((uint32 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length:4]);
            BOOL isMach64 = isMagic64(machMagic);
            BOOL shoudMachSwap = shouldSwapBytesOfMagic(machMagic);
            int totalCmdSize = 0;
            int machHeaderSize = 0;
            if(isMach64){
                machHeaderSize = sizeof(struct mach_header_64);
                struct mach_header_64 * header = (struct mach_header_64 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header_64(header, 0);
                }
         
                totalCmdSize = header->sizeofcmds;
                header->ncmds += 1;
                header->sizeofcmds += dyld.cmdsize;
                [machoFileHandle writeBytes: header offSet: machHeaderOffset length: machHeaderSize];
            }else{
                machHeaderSize = sizeof(struct mach_header);
                struct mach_header * header = (struct mach_header *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header(header, 0);
                }
                totalCmdSize = header->sizeofcmds;
                header->ncmds += 1;
                header->sizeofcmds += dyld.cmdsize;
                [machoFileHandle writeBytes: header offSet: machHeaderOffset length: machHeaderSize];
            }
            [machoFileHandle writeBytes: &dyld offSet: machHeaderOffset + machHeaderSize + totalCmdSize length: sizeof(struct dylib_command)];
            [machoFileHandle writeData: [dylibLinkStr dataUsingEncoding:NSUTF8StringEncoding]];
        }
    }else{
        //thin版本 参考 fat 版本处理流程即可(从 mach_header出开始处理)
        assert(false);
    }
    
    return 0;
}

接着,用 otool -L /Users/jerry/Desktop/testMacho命令检查下:

删除

至此,往Macho-O中添加dylib_command的功能已经实现,接下来便是实现删除dylib_command的功能。删除操作很简单,与添加操作相反,将要删除的dylib_command所占的字节删除,然后再在尾部冗余字节区添加相同长度的0字节。删除的实现没有找到官方的说明也没有查到其他三方库的实现,是自己猜想出来的,后来经测试确实可行,至于为什么这么猜想,是因为发现在load_command中并不存在offset相关字段,每个load_command仅仅是按顺序排列,所以个人猜想删除其中一个然后再在冗余字节区添加上相同长度的0字节也不会有问题,因为这个操作既不会改变整个 load_command 区域的长度也仍然保持着 load_command排列列表是有效的。话不多说,直接上代码:

下面示例中,实现了删除Macho-O中每个架构的 /System/Library/Frameworks/Foundation.framework/Foundation 这条 dylib_command


//  main.m

#import <Foundation/Foundation.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach-o/fat.h>
//MARK: - 定义一些工具方法
BOOL isFatOfMagic(uint32 magic) {
    return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
BOOL isMagic64(uint32 magic) {
    return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
BOOL shouldSwapBytesOfMagic(uint32 magic) {
    return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64;
}
BOOL isValidMachoOfMagic(uint32 magic){
    return magic == FAT_MAGIC || magic == FAT_CIGAM || magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
@interface NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
- (const void *)readBytesFromOffSet:(unsigned long long)offset length:(NSUInteger)length;
- (void)writeData:(NSData *)data offSet:(unsigned long long)offset;
- (void)writeBytes:(void *)bytes offSet:(unsigned long long)offSet length:(NSUInteger)length;
+ (void)insert:(NSString * )filePath toInsertData: (NSData *)toInsertData offset:(long)offset;
+ (void)delete:(NSString * )filePath offset:(long)offset size:(long)size;
@end

@implementation NSFileHandle(Extension)
- (NSData *)readDataFromOffSet:(unsigned long long)offset length:(NSUInteger)length{
    [self seekToFileOffset:offset];
    return [self readDataOfLength:length];
}
- (const void *)readBytesFromOffSet:(unsigned long long)offSet length:(NSUInteger)length{
    return [[self readDataFromOffSet:offSet length:length] bytes];
}
- (void)writeData:(NSData *)data offSet:(unsigned long long)offset{
    [self seekToFileOffset:offset];
    [self writeData:data];
}
- (void)writeBytes:(void *)bytes offSet:(unsigned long long)offSet length:(NSUInteger)length{
    NSData * d = [[NSData alloc]initWithBytes:bytes length:length];
    [self writeData:d offSet: offSet];
}
+ (void) insert:(NSString * )filePath toInsertData: (NSData *)toInsertData offset:(long)offset{
    NSFileHandle * fh = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    [fh seekToFileOffset:offset];
    NSData * remainData = [fh readDataToEndOfFile];
    [fh truncateFileAtOffset:offset];
    [fh writeData:toInsertData];
    [fh writeData:remainData];
    [fh synchronizeFile];
    [fh closeFile];
}
+ (void) delete:(NSString * )filePath offset:(long)offset size:(long)size{
    NSFileHandle * fh = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
    [fh seekToFileOffset:offset + size];
    NSData * remainData = [fh readDataToEndOfFile];
    [fh truncateFileAtOffset:offset];
    [fh writeData:remainData];
    [fh synchronizeFile];
    [fh closeFile];
}
@end

//Demo 主逻辑
int main(int argc, const char * argv[]) {
    NSString * toDeleteDylibLink = @"/System/Library/Frameworks/Foundation.framework/Foundation";
    NSString * machoFilePath = @"/Users/jerry/Desktop/testMacho";
    NSFileHandle * machoFileHandle = [NSFileHandle fileHandleForUpdatingAtPath:machoFilePath];
    /* 读取魔数(前4个字节)
     要注意的是,后面还有一个魔数,是MachO部分的,此处是针对 fat_arch 和 fat_header 相关信息的。
     后面的是用于某个arch下的 MachO 的。
     */
    uint32 magic = *((uint32 *)[machoFileHandle readBytesFromOffSet:0 length:4]);
    //thin 还是 fat 版本
    BOOL isFat = isFatOfMagic(magic);
    //是否是64位
    BOOL is64 = isMagic64(magic);
    //大小端转换判断
    BOOL shouldSwap = shouldSwapBytesOfMagic(magic);
    
    if(isFat){
        int fat_header_size = sizeof(struct fat_header);
        struct fat_header * fatHeader = (struct fat_header *)[machoFileHandle readBytesFromOffSet:0 length:fat_header_size];
        if (shouldSwap) {
            swap_fat_header(fatHeader, 0);
        }
        int arch_offset = fat_header_size;
        uint64_t machHeaderOffset = 0;
        //遍历所有 arch
        for (int i = 0; i < fatHeader->nfat_arch; i++) {
            if(is64){
                int fat_arch_size = sizeof(struct fat_arch_64);
                struct fat_arch_64 * arch = (struct fat_arch_64 *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    //大小端转换
                    swap_fat_arch_64(arch, 1, 0);
                }
                //保存 mach header 的起始位置
                machHeaderOffset = arch->offset;
                //计算下一个 fat_arch 的起始位置
                arch_offset += fat_arch_size;
            }else{
                int fat_arch_size = sizeof(struct fat_arch);
                struct fat_arch * arch = (struct fat_arch *)[machoFileHandle readBytesFromOffSet:arch_offset length:fat_arch_size];
                if (shouldSwap) {
                    swap_fat_arch(arch, 1, 0);
                }
                machHeaderOffset = arch->offset;
                arch_offset += fat_arch_size;
            }
            
            /*------ 开始读取 mach header ------*/
            //读取魔数(前4个字节)
            uint32 machMagic = *((uint32 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length:4]);
            BOOL isMach64 = isMagic64(machMagic);
            BOOL shoudMachSwap = shouldSwapBytesOfMagic(machMagic);
            
            int ncmds = 0;
            long loadCommandsOffset = 0;
            if(isMach64){
                int machHeaderSize = sizeof(struct mach_header_64);
                struct mach_header_64 * header = (struct mach_header_64 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header_64(header, 0);
                }
                //获取 load commands 的个数
                ncmds = header -> ncmds;
                //load commands的起始位置
                loadCommandsOffset = machHeaderOffset + machHeaderSize;
            }else{
                int machHeaderSize = sizeof(struct mach_header);
                struct mach_header * header = (struct mach_header *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                if (shoudMachSwap) {
                    swap_mach_header(header, 0);
                }
                
                ncmds = header -> ncmds;
                loadCommandsOffset = machHeaderOffset + machHeaderSize;
            }
            /*------ 开始读取 load command ------*/
            for (int  i = 0; i < ncmds; i++) {
                struct load_command *cmd = (struct load_command *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset length:sizeof(struct load_command)];
                if (shoudMachSwap) {
                    swap_load_command(cmd, 0);
                }
                //如果是 LC_LOAD_DYLIB类型, 则获取 dylib_command
                if (cmd->cmd == LC_LOAD_DYLIB){
                    struct dylib_command * dylibCmd = (struct dylib_command *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset length:cmd->cmdsize];
                    if (shoudMachSwap) {
                        swap_dylib_command(dylibCmd, 0);
                    }
                    //读取 dylib_command 的 name 信息
                    int pathstringLen = dylibCmd->cmdsize -  dylibCmd->dylib.name.offset;
                    char * cPath = (char *)[machoFileHandle readBytesFromOffSet:loadCommandsOffset + dylibCmd->dylib.name.offset length: pathstringLen];
                    NSString * path =  [[NSString alloc]initWithUTF8String:cPath];
                    if ([path isEqualToString:toDeleteDylibLink]){
                        int totalCmdSize = 0;
                        int machHeaderSize = 0;
                        /* 执行删除逻辑 */
                        //更新 header 信息
                        if(isMach64){
                            machHeaderSize = sizeof(struct mach_header_64);
                            struct mach_header_64 * header = (struct mach_header_64 *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                            if (shoudMachSwap) {
                                swap_mach_header_64(header, 0);
                            }
                            
                            totalCmdSize = header->sizeofcmds;
                            header->ncmds -= 1;
                            header->sizeofcmds -= dylibCmd -> cmdsize;
                            [machoFileHandle writeBytes: header offSet: machHeaderOffset length: machHeaderSize];
                        }else{
                            machHeaderSize = sizeof(struct mach_header);
                            struct mach_header * header = (struct mach_header *)[machoFileHandle readBytesFromOffSet:machHeaderOffset length: machHeaderSize];
                            if (shoudMachSwap) {
                                swap_mach_header(header, 0);
                            }
                            totalCmdSize = header->sizeofcmds;
                            header->ncmds -= 1;
                            header->sizeofcmds -= dylibCmd->cmdsize;
                            [machoFileHandle writeBytes: header offSet: machHeaderOffset length: machHeaderSize];
                        }
                        //构建 0 字节
                        int n = dylibCmd->cmdsize;
                        uint8 *arr;
                        arr = (uint8*)malloc(sizeof(uint8)*n);
                        for (int i = 0; i < n; i++)
                            arr[i] = 0;
                        //尾部添加
                        [NSFileHandle insert:machoFilePath toInsertData:[NSData dataWithBytes:arr length:n] offset:machHeaderOffset + machHeaderSize + totalCmdSize];
                        //删除 dylibCmd 占用的字节
                        [NSFileHandle delete:machoFilePath offset:loadCommandsOffset size:dylibCmd->cmdsize];
                        break;
                    }
                }
                //计算下个 load_command 的起始位置
                loadCommandsOffset += cmd->cmdsize;
            }
        }
    }else{
        //如果是 thin 版本,就直接从上面fat版处理步骤中的 "开始读取 mach header" 开始,这里就不再重复实现了。
        assert(false);
    }
    
    return 0;
}

用otool检查下效果:
删除前:
在这里插入图片描述

删除后:
在这里插入图片描述

至此,Macho-O文件dylib_command的增删已经实现,上述示例代码为了尽量简单,并没有做封装,直接复制后就能运行,这里我也写了一个封装后的版本: MachoHandle , SResigner 中使用的就是这个库。

IPA重签名

关于IPA的重签名,网上有很多博客对其过程进行了说明,这里稍微再啰嗦几句关于SResigner 中的实现,SResigner 中的重签名主要参考的是sigh这个命令行工具,个人在过去使用过各种各样的重签名工具如:iResign、iOS App Signer、sigh,最终发现sigh最好用,而且这个工具的作者也一直保持着更新,不过sigh也有一些不是很友好的地方:

  • sigh是命令行工具,对非技术人员难免会有些不友好。
  • sigh比较"正规",对于一些"另类"的情况会无法处理,比如微信的插件经常是做成dylib,然后放置到bundle中,sigh不会对dylib文件进行签名。
  • sigh会强制IPA的BundleID与mobileprovision保持一致,但有些情况,使用者并不希望更改BundleID。

针对以上这些问题,SResigner 都给出了相应的解决方案,比如 SResigner 会记录所有新增的文件,不管是dylib或是其他类型文件,
签名时都会先签这些新增的文件,虽然对于普通资源文件来说,在签Bundle时会自动被签名,但 SResigner 仍会会主动对其签名,因为目前 SResigner 只是单纯的记录新增的文件,并没有对新增文件的类型做判断,使用者很有可能会将dylib命名为XX.png添加至Bundle中(确实有这种场景,虽然对技术人员来说,这种混淆形同虚设,但还就是有人会这么做),更有甚者,会将一些文件放置到Frameworks、Plugins等标准目录下,这样的话,签Bundle时这些文件就不会被签名,codesign都是按照苹果规范的标准目录结构去实施签名,它并不知道会有"高玩"来这么一出。

还有一种略微复杂些的场景:我们拿到的是一个已经注入过动态库的IPA包,比如一个已经注入过红包插件的微信IPA,假设这个插件叫 hb.dylib,现在我要对这个包进行重签名,那么问题就是:对于hb.dylib来说,它不是一个资源文件,所以签名bundle时不会被签名,它也不是新增文件,所以SResigner 也无法在签名新增文件时将其签名,但是hb.dylib又是必须要签名的,否则整个app会无法安装或安装后启动崩溃。对于这个问题,SResigner 的解决方案是:
检测Bundle下的Mach-O文件的 dylib link列表(otool -L xxx 展示的那个列表),将所有 @executable_path 开头的动态库进行单独签名,因为根据个人经验,几乎所有后期注入的插件,都是采用 @executable_path 打头的路径。

接着上面的场景,更复杂一些,hb.dylib中又有对其他xxx.dylib的link,关于这种场景的解决方案是:对于后期添加的插件,用深度遍历的方式检查每个dylib的dylib link列表,只要判定是后期添加的,那么进行单独签名(这个功能目前暂时还没实现,后面会加上)。

上面说了这么多,主要是说明了SResigner 中重签名这块与其他工具的不同之处,其他相同的地方这里就不赘述了。
另外,看网上的帖子中有很多人反映某某工具重签某某包后会无法安装或启动崩溃,这种问题很多都是漏签导致的,漏签包含了:

  • 对部分文件的漏签,如dylib
  • 对nested app的漏签,如extension

对于IPA无法安装或无法启动的问题如何排查,这里介绍一个很有用的苹果官方提供的工具: Apple Configurator 2,这个App安装完后,可以通过它安装一个命令行工具:cfgutil,这个工具有很多功能,个人最常用的就是:在非越狱的手机上查看系统log (手机连接USB,终端输入:cfgutil syslog 即可)。这样可以很方便的排查问题。

最后,SResigner 还很稚嫩,亟需完善与成长,还请各位读者不要吝啬自己的灵感,欢迎issue和PR,当然,如果您对我的劳动成果有一点点认可,还请 Star SResigner ,你们的支持将是我前进的动力,谢谢~

macOS开发中,App的生命周期是指App从启动到关闭的整个过程。主要包括以下几个阶段: 1. 启动阶段:当用户单击或双击App的图标时,系统会检测并初始化App的相关资源,包括加载App的代码和资源文件等。同时,系统会调用App的代理对象的一些方法,比如`applicationDidFinishLaunching`方法,开发者可以在此方法中进行一些初始化操作,如注册通知、设置全局参数等。 2. 运行阶段:App启动后,用户可以与其进行交互。App会响应用户的操作事件,如点击按钮、滑动屏幕等。在运行阶段,App会接收来自系统的事件消息,并做出相应的处理。同时,App还可以主动发送消息给其他对象,实现不同模块之间的通信和交互。 3. 后台和挂起阶段:当用户将App切换到后台时,App进入后台执行状态,可以继续执行一些任务,比如下载、数据上传等。在后台状态下,App可以接收远程推送通知,并执行相关操作,如更新内容、显示提醒等。当系统资源紧张时,会将App挂起,此时App会进入挂起状态,停止执行,释放资源,以便系统能够优先处理其他任务。 4. 退出阶段:当用户关闭App时,系统会调用App代理对象的`applicationWillTerminate`方法,开发者可以在此方法中进行一些清理操作,如保存数据、关闭文件等。之后,系统会释放App的相关资源,关闭App进程。 总结:App的生命周期包括启动、运行、挂起和退出等阶段。开发者可以通过合理地实现代理方法和事件处理,控制和管理App的运行流程,提高用户体验和性能。
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值