fishhook掃盲

一.啥是fishhook

fishhook是一個(gè)運(yùn)行在IOS的模擬器和真機(jī)環(huán)境挺份,在MACH-O文件中能夠動(dòng)態(tài)重新綁定符號(hào)的簡(jiǎn)單的庫(kù)。

二.咋用呢

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"");//先執(zhí)行一次系統(tǒng)函數(shù)杀捻,共享函數(shù)庫(kù)里才會(huì)加載進(jìn)這個(gè)方法 -> 待確定
//    struct rebinding {
//      const char *name;   //需要hook的函數(shù)名稱,c字符串
//      void *replacement;  //新函數(shù)的地址
//      void **replaced;    //原函數(shù)的地址
//    };
    struct rebinding logRebinding;
    logRebinding.name = "NSLog";
    logRebinding.replacement = myLog;
    logRebinding.replaced = (void *)&sys_nslog;
    
    struct rebinding rebs[1] = {logRebinding};
    
    //rebs: 存放rebinding結(jié)構(gòu)體的數(shù)組
    //1: 數(shù)組的長(zhǎng)度
    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

當(dāng)我們打印NSLog(@"originLog");時(shí),輸出了newLog,很神奇

三.為啥這么神奇?

道理很簡(jiǎn)單,就是找到NSlog的位置嫉髓,將其替換成我們自己的函數(shù)地址就行观腊,只是說(shuō)這個(gè)找尋過(guò)程有點(diǎn)麻煩而已。
這就需要我們具備一定的鋪墊知識(shí)算行。

3.1 動(dòng)態(tài)庫(kù)共享緩存(dyld shared cache)

在iOS系統(tǒng)中梧油,每個(gè)程序依賴的動(dòng)態(tài)庫(kù)都需要通過(guò)dyld(位于/usr/lib/dyld)一個(gè)一個(gè)加載到內(nèi)存,然而如果在每個(gè)程序運(yùn)行的時(shí)候都重復(fù)的去加載一次州邢,勢(shì)必造成運(yùn)行緩慢儡陨,為了優(yōu)化啟動(dòng)速度和提高程序性能,共享緩存機(jī)制就應(yīng)運(yùn)而生量淌。所有默認(rèn)的動(dòng)態(tài)鏈接庫(kù)被合并成一個(gè)大的緩存文件骗村,放到/System/Library/Caches/com.apple.dyld/目錄下,按不同的架構(gòu)保存著.

NSlog就在共享動(dòng)態(tài)庫(kù)里面

3.2 懶加載符號(hào)表

  • 我們把項(xiàng)目里的NSLog函數(shù)都注釋掉
圖片.png
  • 打開(kāi)NSLog函數(shù)呀枢,使用machoview看下胚股,發(fā)現(xiàn)NSLog函數(shù)是存放在Lazy Symbol Pointers即懶加載段里面, 偏移量為00008010
圖片.png

NSLog之前打一個(gè)斷點(diǎn),然后使用image list拿到基地址0x0000000100814000

圖片.png
  • 基地址+偏移量按理說(shuō)應(yīng)該可以拿到NSLog函數(shù)的內(nèi)存地址
    16進(jìn)制打印
圖片.png
  • 查看NSLog函數(shù)的指針,反匯編一下,發(fā)現(xiàn)并不是NSLog函數(shù)
圖片.png

圖片.png
  • 跳過(guò)NSLog這個(gè)斷點(diǎn),重新看下0x000000010081c018地址存放的數(shù)據(jù)裙秋,發(fā)現(xiàn)地址變了
圖片.png
  • 反編譯下
    震驚琅拌,發(fā)現(xiàn)該地址是NSLog函數(shù)
圖片.png
  • 結(jié)論: 懶加載表中的函數(shù),是在函數(shù)運(yùn)行一次后才會(huì)確定函數(shù)的地址
    所以使用fishhook需要先調(diào)用下要hook的函數(shù)

3.3 符號(hào)綁定& 重綁定符號(hào)

NSLog函數(shù)屬于共享緩存庫(kù)的函數(shù)摘刑,所以在編譯期間进宝,是不能確定它的地址的。只有在運(yùn)行時(shí)枷恕,dyld才會(huì)從共享緩存庫(kù)拿到NSLog函數(shù)并且確定它的地址,寫入到懶加載符號(hào)表里

四.咋實(shí)現(xiàn)的呢

4.1 記錄下簡(jiǎn)單的步驟

  1. 找到字符串表symtab_cmd->stroffsymtab_segment
  2. 找到符號(hào)表LC_SYMTABsegment
  3. 找到動(dòng)態(tài)符號(hào)表LC_DYSYMTABsegment
  4. indirect_symtab = LC_DYSYMTAB->indirectsymoff拿到間接符號(hào)表indirect_symtab党晋,間接符號(hào)表其實(shí)就是動(dòng)態(tài)庫(kù)的符號(hào)表
  5. 拿到懶加載section__la_symbol_ptr,這里面就有NSLog函數(shù)的地址
  6. indirect_symbol_indices = indirect_symtab + __la_symbol_ptr.reserved1得到__la_symbol_ptr模塊在indirect_symtab表的起始位置 7 8 9
  7. for循環(huán)取出LC_SYMTAB.7.n_un.n_strx拿到其在字符串表symtab_segment中的位置, 取出字符串NSLog
  8. 判斷要替換的字符串和取出的字符串是否一樣徐块,一樣的話將__la_symbol_ptr替換成我們的函數(shù)

4.2 搞個(gè)超簡(jiǎn)版demo未玻,來(lái)替換NSLog

調(diào)用下面的函數(shù),你會(huì)發(fā)現(xiàn)不管你NSLog什么,打印出來(lái)都是"替換NSLog成功啦"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"");//先執(zhí)行一次系統(tǒng)函數(shù)胡控,共享函數(shù)庫(kù)里才會(huì)加載進(jìn)這個(gè)方法 -> 因?yàn)槭菓?    
    //驗(yàn)證
    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

//替換成你自己的函數(shù)
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;

/// 替換函數(shù)
/// @param symtab 符號(hào)表
/// @param strtab 字符串表
/// @param section __la_symbol_ptr
/// @param indirect_symtab 間接符號(hào)表
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里面的內(nèi)容在間接表中的起始位置
    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];
        
        //通過(guò)符號(hào)表拿到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.能到符號(hào)表segment和動(dòng)態(tài)符號(hào)表segment
    //記錄當(dāng)前的segmnet
    struct segment_command_64 *cur_seg_cmd;
    
    //符號(hào)表LC_SYMTAB
    struct symtab_command* symtab_cmd = NULL;
    //動(dòng)態(tài)符號(hào)表LC_DYSYMTAB
    struct dysymtab_command* dysymtab_cmd = NULL;

    //第一個(gè)segment的地址
    uintptr_t cur = (uintptr_t )header + sizeof(struct mach_header_64);
    
    //循環(huán)遍歷,找到  符號(hào)表LC_SYMTAB, 動(dòng)態(tài)符號(hào)表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) {
        // 符號(hào)表或者動(dòng)態(tài)符號(hào)表的segment為空,說(shuō)明沒(méi)有動(dòng)態(tài)庫(kù),return
        return;
    }
    
    
    
    
    //2.通過(guò)segment拿到符號(hào)表section, string table, 間接符號(hào)表
    uintptr_t mh_addr = (uintptr_t)_dyld_get_image_header(0);

    //拿到符號(hào)表section
    struct nlist_64 *symtab = (struct nlist_64 *) (mh_addr +symtab_cmd->symoff);
    
    //拿到string table
    char *strtab = (char *)(mh_addr + symtab_cmd->stroff);
    
    //拿到間接符號(hào)表, 也就是動(dòng)態(tài)庫(kù)的符號(hào)表
    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) {
                    // 懶加載符號(hào)表
                    replaceImp(vmaddr_slide, symtab, strtab, sect, indirect_symtab);
                    return;
                }
            }
        }
    }
}


int replaceNSLog(void *youNSLog) {
    _youNSLog = youNSLog;
    
    //監(jiān)聽(tīng)各個(gè)image
    _dyld_register_func_for_add_image(allImages);
    
    return 0;
}

五.總結(jié)

說(shuō)白了扳剿,fishhook代碼看似復(fù)雜,主要mach-O內(nèi)部結(jié)構(gòu)體復(fù)雜铜犬,找來(lái)找去很是麻煩。但如果有一天你對(duì)mach-O了如執(zhí)掌轻庆,那么fishhook也就不攻自破了癣猾。

  • fishhook只能hook動(dòng)態(tài)庫(kù)里面的函數(shù),包含__got和la_symbol_ptr段
  • 使用fishhook需要先調(diào)用下要hook的函數(shù)
  • 將hook代碼盡量提前

六.Q

總結(jié)哪些函數(shù)屬于OC函數(shù)余爆,哪些屬于共享緩存庫(kù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纷宇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蛾方,更是在濱河造成了極大的恐慌像捶,老刑警劉巖上陕,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拓春,居然都是意外死亡释簿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門硼莽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庶溶,“玉大人,你說(shuō)我怎么就攤上這事懂鸵∑荩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵匆光,是天一觀的道長(zhǎng)套像。 經(jīng)常有香客問(wèn)我,道長(zhǎng)终息,這世上最難降的妖魔是什么夺巩? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮采幌,結(jié)果婚禮上劲够,老公的妹妹穿的比我還像新娘。我一直安慰自己休傍,他們只是感情好征绎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著磨取,像睡著了一般人柿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忙厌,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天凫岖,我揣著相機(jī)與錄音,去河邊找鬼逢净。 笑死哥放,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爹土。 我是一名探鬼主播甥雕,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼胀茵!你這毒婦竟也來(lái)了社露?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琼娘,失蹤者是張志新(化名)和其女友劉穎峭弟,沒(méi)想到半個(gè)月后附鸽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞒瘸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年坷备,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挨务。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡击你,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谎柄,到底是詐尸還是另有隱情丁侄,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布朝巫,位于F島的核電站鸿摇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏劈猿。R本人自食惡果不足惜拙吉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揪荣。 院中可真熱鬧筷黔,春花似錦、人聲如沸仗颈。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挨决。三九已至请祖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脖祈,已是汗流浹背肆捕。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盖高,地道東北人慎陵。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喻奥,于是被迫代替她去往敵國(guó)和親席纽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 一映凳、Hook概述 HOOK中文譯為掛鉤或鉤子胆筒。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)邮破。通過(guò)hook可以讓別人...
    HotPotCat閱讀 4,509評(píng)論 1 12
  • 1. Hook的方式 Hook是改變程序運(yùn)行流程的一種方式诈豌,通過(guò)Hook可以讓自己的代碼運(yùn)行在別人的程序中仆救。需要...
    大成小棧閱讀 1,558評(píng)論 1 8
  • 前言 本篇文章開(kāi)始給大家分享下Hook(鉤子)的原理,包括iOS系統(tǒng)原生的Method Swizzle矫渔,還有很有名...
    深圳_你要的昵稱閱讀 2,373評(píng)論 0 5
  • HOOK概述 HOOK彤蔽,中文譯為“掛鉤”或“鉤子”。在iOS逆向中是指改變程序運(yùn)行流程的一種技術(shù)庙洼。通過(guò)hook可以...
    冼同學(xué)閱讀 1,290評(píng)論 0 5
  • fishHook fishHook是Facebook提供的一個(gè)動(dòng)態(tài)修改鏈接mach-O文件的工具顿痪。利用MachO文...
    king_jensen閱讀 9,568評(píng)論 5 20