fishhook扫盲

一.啥是fishhook

fishhook是一个运行在IOS的模拟器和真机环境,在MACH-O文件中能够动态重新绑定符号的简单的库。

二.咋用呢

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"");//先执行一次系统函数,共享函数库里才会加载进这个方法 -> 待确定
//    struct rebinding {
//      const char *name;   //需要hook的函数名称,c字符串
//      void *replacement;  //新函数的地址
//      void **replaced;    //原函数的地址
//    };
    struct rebinding logRebinding;
    logRebinding.name = "NSLog";
    logRebinding.replacement = myLog;
    logRebinding.replaced = (void *)&sys_nslog;
    
    struct rebinding rebs[1] = {logRebinding};
    
    //rebs: 存放rebinding结构体的数组
    //1: 数组的长度
    rebind_symbols(rebs, 1);
    
    NSLog(@"originLog");
    return YES;
}

static void (*sys_nslog)(NSString *format, ...);
void myLog(NSString *format, ...) {
    format = @"newLog";
    sys_nslog(format);
}

//2022-05-17 14:31:10.058392+0800 fishhookdemo[2753:1073295] newLog

当我们打印NSLog(@"originLog");时,输出了newLog,很神奇

三.为啥这么神奇?

道理很简单,就是找到NSlog的位置,将其替换成我们自己的函数地址就行,只是说这个找寻过程有点麻烦而已。
这就需要我们具备一定的铺垫知识。

3.1 动态库共享缓存(dyld shared cache)

在iOS系统中,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个加载到内存,然而如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下,按不同的架构保存着.

NSlog就在共享动态库里面

3.2 懒加载符号表

  • 我们把项目里的NSLog函数都注释掉
图片.png
  • 打开NSLog函数,使用machoview看下,发现NSLog函数是存放在Lazy Symbol Pointers即懒加载段里面, 偏移量为00008010
图片.png

NSLog之前打一个断点,然后使用image list拿到基地址0x0000000100814000

图片.png
  • 基地址+偏移量按理说应该可以拿到NSLog函数的内存地址
    16进制打印
图片.png
  • 查看NSLog函数的指针,反汇编一下,发现并不是NSLog函数
图片.png

图片.png
  • 跳过NSLog这个断点,重新看下0x000000010081c018地址存放的数据,发现地址变了
图片.png
  • 反编译下
    震惊,发现该地址是NSLog函数
图片.png
  • 结论: 懒加载表中的函数,是在函数运行一次后才会确定函数的地址
    所以使用fishhook需要先调用下要hook的函数

3.3 符号绑定& 重绑定符号

NSLog函数属于共享缓存库的函数,所以在编译期间,是不能确定它的地址的。只有在运行时,dyld才会从共享缓存库拿到NSLog函数并且确定它的地址,写入到懒加载符号表里

四.咋实现的呢

4.1 记录下简单的步骤

  1. 找到字符串表symtab_cmd->stroffsymtab_segment
  2. 找到符号表LC_SYMTABsegment
  3. 找到动态符号表LC_DYSYMTABsegment
  4. indirect_symtab = LC_DYSYMTAB->indirectsymoff拿到间接符号表indirect_symtab,间接符号表其实就是动态库的符号表
  5. 拿到懒加载section__la_symbol_ptr,这里面就有NSLog函数的地址
  6. indirect_symbol_indices = indirect_symtab + __la_symbol_ptr.reserved1得到__la_symbol_ptr模块在indirect_symtab表的起始位置 7 8 9
  7. for循环取出LC_SYMTAB.7.n_un.n_strx拿到其在字符串表symtab_segment中的位置, 取出字符串NSLog
  8. 判断要替换的字符串和取出的字符串是否一样,一样的话将__la_symbol_ptr替换成我们的函数

4.2 搞个超简版demo,来替换NSLog

调用下面的函数,你会发现不管你NSLog什么,打印出来都是"替换NSLog成功啦"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"");//先执行一次系统函数,共享函数库里才会加载进这个方法 -> 因为是懒
    
    //验证
    replaceNSLog(youNSLog);
    NSLog(@"");
    return YES;
}

static void youNSLogFuc() {
    printf("替换NSLog成功啦");
}
void (*youNSLog)(void) = youNSLogFuc;

fishcook.h

#ifndef fishcook_h
#define fishcook_h

#include <stdio.h>


#ifdef __cplusplus
extern "C" {
#endif //__cplusplus

//替换成你自己的函数
int replaceNSLog(void *youNSLog);


#ifdef __cplusplus
}
#endif //__cplusplus


#endif /* fishcook_h */

fishcook.m

//
//  fishcook.c
//  fishhookdemo
//
//  Created by liyongkai on 2022/7/21.
//

#include "fishcook.h"
#include <mach-o/dyld.h>
#include <mach/mach.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>


void *_youNSLog;

/// 替换函数
/// @param symtab 符号表
/// @param strtab 字符串表
/// @param section __la_symbol_ptr
/// @param indirect_symtab 间接符号表
static void replaceImp(intptr_t vmaddr_slide,
                       struct nlist_64 *symtab,
                       char *strtab,
                       struct section_64 *section,
                       uint32_t *indirect_symtab) {
    
    //拿到__la_symbol_ptr section里面的内容在间接表中的起始位置
    uint32_t *indirect_symtab_indices = indirect_symtab + section->reserved1;
    
    for (uint i = 0; i < section->size / sizeof(void *); i ++) {
        uint32_t tabIndex = indirect_symtab_indices[i];
        
        //通过符号表拿到nslog在字符串表中的位置
        uint32_t strtab_offset = symtab[tabIndex].n_un.n_strx;
        
        //拿到字符串
        char *symbol_name = strtab + strtab_offset;
        
        //如果一样就替换
        if (strcmp(&symbol_name[1], _youNSLog)) {
            
            void **la_symbol_prt_address = (void **)(vmaddr_slide + section->addr);
            la_symbol_prt_address[i] = _youNSLog;
            
            return;
        }
    }
}




static void allImages(const struct mach_header* header, intptr_t vmaddr_slide) {
    
    //1.能到符号表segment和动态符号表segment
    //记录当前的segmnet
    struct segment_command_64 *cur_seg_cmd;
    
    //符号表LC_SYMTAB
    struct symtab_command* symtab_cmd = NULL;
    //动态符号表LC_DYSYMTAB
    struct dysymtab_command* dysymtab_cmd = NULL;

    //第一个segment的地址
    uintptr_t cur = (uintptr_t )header + sizeof(struct mach_header_64);
    
    //循环遍历,找到  符号表LC_SYMTAB, 动态符号表LC_DYSYMTAB
    for (uint i = 0; i < header->ncmds; i ++, cur += cur_seg_cmd->cmdsize) {
        cur_seg_cmd = (struct segment_command_64 *)cur;
        
        if (cur_seg_cmd->cmd == LC_SYMTAB) {
            symtab_cmd = (struct symtab_command *)cur_seg_cmd;
        } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
            dysymtab_cmd = (struct dysymtab_command *)cur_seg_cmd;
        }
    }
    
    if (!symtab_cmd || !dysymtab_cmd) {
        // 符号表或者动态符号表的segment为空,说明没有动态库,return
        return;
    }
    
    
    
    
    //2.通过segment拿到符号表section, string table, 间接符号表
    uintptr_t mh_addr = (uintptr_t)_dyld_get_image_header(0);

    //拿到符号表section
    struct nlist_64 *symtab = (struct nlist_64 *) (mh_addr +symtab_cmd->symoff);
    
    //拿到string table
    char *strtab = (char *)(mh_addr + symtab_cmd->stroff);
    
    //拿到间接符号表, 也就是动态库的符号表
    uint32_t *indirect_symtab = (uint32_t *)(mh_addr + dysymtab_cmd->indirectsymoff);
    
    //找到__got section 和 la_symbol_ptr section, 他们都在__DATA segment里面
    cur = (uintptr_t)header + sizeof(struct mach_header_64);
    for (uint i = 0; i < header->ncmds; i ++, cur += cur_seg_cmd->cmdsize) {
        cur_seg_cmd = (struct segment_command_64 *)cur;
        if (cur_seg_cmd->cmd == LC_SEGMENT_64) {
            if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
                strcmp(cur_seg_cmd->segname, "__DATA_CONST") != 0) {
              continue;
            }
            
            for (uint j = 0; j < cur_seg_cmd->nsects; j ++) {
                struct section_64 *sect = (struct section_64 *)(cur + sizeof(struct segment_command_64)) + j;
                if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
                    // 懒加载符号表
                    replaceImp(vmaddr_slide, symtab, strtab, sect, indirect_symtab);
                    return;
                }
            }
        }
    }
}


int replaceNSLog(void *youNSLog) {
    _youNSLog = youNSLog;
    
    //监听各个image
    _dyld_register_func_for_add_image(allImages);
    
    return 0;
}

五.总结

说白了,fishhook代码看似复杂,主要mach-O内部结构体复杂,找来找去很是麻烦。但如果有一天你对mach-O了如执掌,那么fishhook也就不攻自破了。

  • fishhook只能hook动态库里面的函数,包含__got和la_symbol_ptr段
  • 使用fishhook需要先调用下要hook的函数
  • 将hook代码尽量提前

六.Q

总结哪些函数属于OC函数,哪些属于共享缓存库

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容