fishhook源碼分析

? ? 之前一直都有聽(tīng)說(shuō)過(guò)fishhook是用來(lái)hook系統(tǒng)自帶的C函數(shù)的,也大概知道原理是重新綁定符號(hào)來(lái)達(dá)到hook的目的,一直沒(méi)有深入的去讀一下fishhook的源碼。這幾天好好的坐下來(lái)讀了一下fishhook的源碼,在這個(gè)過(guò)程中也對(duì)C/C++指針以及結(jié)構(gòu)體的內(nèi)存布局都有了進(jìn)一步認(rèn)識(shí),其實(shí)之前不愿意讀fishhook的源碼有很大一部分原因是因?yàn)榻Y(jié)構(gòu)體和指針以及C/C++基礎(chǔ)知識(shí)的欠缺讓我打了退堂鼓。

? ? ?在閱讀源碼的過(guò)程中也查閱了很多的資料、博客來(lái)加深認(rèn)識(shí)和理解,但是發(fā)現(xiàn)大多數(shù)人的博客基本上都是千篇一律的拿machO文件來(lái)作說(shuō)明,很少有人從fishhook的代碼的角度去細(xì)致的推導(dǎo)出hook的具體流程,我寫這篇文章的目的就是基于此,給那些想知道為什么代碼這么寫并且這么寫能夠達(dá)到什么一個(gè)什么目的人,也是我在這個(gè)過(guò)程中最讓我頭痛的問(wèn)題,如果有什么不對(duì)的地方,歡迎指正。(我使用的代碼是戴銘老師GCDFetchFeed項(xiàng)目的代碼,和fishhook其實(shí)是一樣的)

? ??????

? ? ? ? 1:首先,介紹幾個(gè)C/C++相關(guān)的知識(shí),在下面會(huì)用得上。

????????結(jié)構(gòu)體的內(nèi)存布局。結(jié)構(gòu)體的所占字節(jié)數(shù)=結(jié)構(gòu)體各個(gè)元素所占字節(jié)數(shù)之和(假如結(jié)構(gòu)體元素的類型不一致,那么所占字節(jié)是最大元素的倍數(shù),自己可以用一個(gè)int和一個(gè)NSString測(cè)試,最終是占用16個(gè)字節(jié)而不是12個(gè)字節(jié));并且一個(gè)結(jié)構(gòu)體指針p加上1表示這個(gè)指針指向的地址往后移動(dòng)這個(gè)結(jié)構(gòu)體大小個(gè)字節(jié)。也就是說(shuō),假如我結(jié)構(gòu)體占16個(gè)字節(jié),有一個(gè)指針p指向一個(gè)結(jié)構(gòu)體變量test的地址,那么p+1就等于test的地址+16,如下圖-1。每一個(gè)NSString占用8個(gè)字節(jié),三個(gè)NSString類型變量總共占用24個(gè)字節(jié);指針+1后面再講;arm64下指針是占有8個(gè)字節(jié),結(jié)構(gòu)體指針是指向結(jié)構(gòu)體的首地址。

圖-1

? ? ? ? 2:開(kāi)始綁定是調(diào)用intrebind_symbols這個(gè)函數(shù),然后調(diào)用prepend_rebindings這個(gè)函數(shù),江數(shù)據(jù)由結(jié)構(gòu)體rebinding準(zhǔn)換成結(jié)構(gòu)體rebindings_entry。然后程序繼續(xù)執(zhí)行會(huì)調(diào)用_dyld_register_func_for_add_image這個(gè)函數(shù),這個(gè)函數(shù)的作用是當(dāng)dyld加載我們的程序的時(shí)候,無(wú)論是添加還是刪除image(模塊),都會(huì)調(diào)用這個(gè)方法注冊(cè)的回調(diào)。這個(gè)涉及到dyld方面的知識(shí),我說(shuō)一下我的理解:在我們點(diǎn)擊app到main函數(shù)之前,是由dyld來(lái)負(fù)責(zé)加載我們的應(yīng)用程序到內(nèi)存,也包括很多的image,在裝載每一個(gè)image進(jìn)入內(nèi)存的時(shí)候,都會(huì)調(diào)用這個(gè)回調(diào),在調(diào)試這個(gè)代碼的過(guò)程中,我發(fā)現(xiàn)這個(gè)回調(diào)會(huì)執(zhí)行很多次,這個(gè)也是合理的,因?yàn)槲覀兊腶pp會(huì)有很多個(gè)image要載入內(nèi)存,我們具體不知道哪一個(gè)image里面有我們想要hook的函數(shù),所以每一個(gè)image都會(huì)去執(zhí)行同一套綁定的代碼,一旦命中了我們需要hook的符號(hào),那么就能夠達(dá)到我們的目的。_dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))這個(gè)方法的參數(shù)是一個(gè)函數(shù)指針,返回的參數(shù)一個(gè)是machO文件的指針mh,一個(gè)偏移量(在dyld加載我們的程序是,會(huì)使用ASLR生成一個(gè)隨機(jī)的偏移量,這個(gè)是dyld的知識(shí)不細(xì)說(shuō)),mh指向的就是當(dāng)前載入的這個(gè)文件的首地址。

? ? ? ? 3:接著調(diào)用了這個(gè)函數(shù)rebind_symbols_for_image,是我們要分析的第一個(gè)很重要的函數(shù)。首先我先說(shuō)一下這個(gè)函數(shù)的目的,這里就不過(guò)多介紹machO文件的知識(shí),這個(gè)machO文件加載進(jìn)內(nèi)存后,地址都是連續(xù)的,目前我們的mh指針指向的是這個(gè)machO文件的首地址,如圖2。??

圖-2

? ? ? ? 4:我們要從這個(gè)machO文件的找到dysymtab_command和symtab_command這兩個(gè)load_commond,找這兩個(gè)是為了求出需要hook的函數(shù)的偏移地址(后面再分析怎么求),還要找一個(gè)linkedit_segment這個(gè)commod,從后面的代碼來(lái)找這個(gè)commond的目的就是求出一個(gè)0x0000000100000000這樣一個(gè)地址,這個(gè)是段虛擬內(nèi)存的起始地址,也就是說(shuō)虛擬內(nèi)存的起始地址不是從0開(kāi)始的,arm64是從0x0000000100000000這個(gè)值開(kāi)始的。我們還需要找到系統(tǒng)函數(shù)的符號(hào)表所在的load_commond對(duì)應(yīng)的section。至此我們要找到4個(gè)值,分別是代碼里面的3個(gè)加上下面代碼的一個(gè)sect,圖-3。這個(gè)sect待會(huì)再說(shuō),這幾個(gè)變量分別對(duì)應(yīng)的是machO文件的位置如圖-4。

圖-3


圖-4

? ? ? ? 5:我們說(shuō)過(guò),mh目前指向的是machO文件的首地址,machO文件最開(kāi)始的元素是mach_header,這個(gè)主要記錄著當(dāng)前這個(gè)machO文件的一些信息??梢跃唧w看mach_header這個(gè)結(jié)構(gòu)體。并且machO文件的內(nèi)存布局是連續(xù)的,那么要找得到load_commod的首地址,我們需要將mh指針向下移動(dòng)sizeof(mach_header)個(gè)字節(jié)大小。如圖-5.

圖-5

圖-6是根據(jù)上面的表達(dá)式驗(yàn)證的過(guò)程,在arm64下,mach_header_t是mach_header_64的別名,結(jié)構(gòu)體mach_header_64有8個(gè)變量,每一個(gè)變量都是占用4個(gè)字節(jié),那么就是占用32個(gè)字節(jié),就是16進(jìn)制的0x20.

圖6

? ? ? ? 6:接下來(lái)的代碼如圖-7,就是求得我們?cè)诓襟E4里面說(shuō)的三個(gè)變量,具體的做法就是遍歷machO里面所有的load_commond元素,cur就是指向當(dāng)前這個(gè)load_commond的首地址。因?yàn)樵趦?nèi)存中的布局是連續(xù)的,所以下一個(gè)commod的首地址=cur+上一個(gè)commod的大小,就是for循環(huán)里面的cur+=cur_seg_cmd->cmdSize,也可以在machO文件里面驗(yàn)證,看圖-8,圖-9

圖-7

圖8的Text段的file offset就是起在machO文件的起始偏移地址位0,大小是0xA8000,下面的DATA段的起始地址就是0+0xA8000=0xA8000這個(gè)地址,這個(gè)只是偏移地址,并不是實(shí)際地址,內(nèi)存中的實(shí)際地址是偏移地址+ASLR+0x0000000100000000才是真實(shí)地址,但是偏移地址和最終的fileSize都是一樣的不會(huì)變的。這也能說(shuō)明machO文件在內(nèi)存中的布局就是連續(xù)的,commond連著commond。

圖-8
圖-9

? ? ? ? 7:要求的的三個(gè)變量linkedit_segment,symtab_cmd,dysymtab_cmd分別是三種不同類型的load_commond。具體為什么是這三種可以找找資料,這里只分析這個(gè)結(jié)果怎么計(jì)算來(lái)的,不分析為什么取這個(gè)類型。別問(wèn),問(wèn)就是machO的結(jié)構(gòu)就是這樣,因?yàn)槲覀兒竺嬉襍ymbol Table的起始地址能根據(jù)symtab_cmd的symoff這個(gè)字段得到,也能得到String Table的起始地址,看圖-10,圖11。這個(gè)String Table就是我們要找的符號(hào)表?,F(xiàn)在只有起始地址,沒(méi)有我們要hook的函數(shù)具體在哪里,繼續(xù)往下看。


圖10?
圖-11

我們發(fā)現(xiàn)LC_SYMTAB的SymBol Table Offset這個(gè)字段剛好存的就是圖11的SymTable的起始地址。同樣String Table OffSet也是對(duì)應(yīng)的String Table的偏移地址。dysymtab_cmd的indirectsymoff這個(gè)字段就是我們要找的Dynamic Symbol Table的起始偏移地址。

? ? ? ? 8.接著繼續(xù)看代碼,圖-12,下面4句代碼就是根據(jù)上面遍歷得到load_commond來(lái)獲得我們后面需要用到的幾個(gè)變量。linkedit_segment->vmaddr- linkedit_segment->fileoff第一行這一部分的意思就是要得到0x0000000100000000這個(gè)虛擬內(nèi)存起始的地址,可以對(duì)著machO文件計(jì)算一下這個(gè)值,我覺(jué)得這個(gè)值不一定要從這個(gè)linkedit_segment來(lái)取,Text段的的VM Address不就是這個(gè)值么。然后加上slide表示的是程序在內(nèi)存中的偏移地址。

圖-12

這里小結(jié)一下,a)linkedit_base這個(gè)值是在內(nèi)存中偏移的地址,就是ASLR算出來(lái)的那一部分+空出來(lái)的部分,只需要這個(gè)值加上每一個(gè)部分的偏移地址就是實(shí)際的內(nèi)存地址;b)symtab就是Symbol Table的偏移地址;c)strtab就是String Table的偏移地址,這個(gè)也是我們最終的需要hook的符號(hào)位置;d:)indirect_symtab這個(gè)是Dynamic_symbol_Table的地址。我們已經(jīng)找到的元素在machO文件的位置看圖-13。我們還缺點(diǎn)什么,怎么具體定位到我們需要hook的函數(shù),我們只是有了需要用到的元素的起始地址。

圖-13

? ? ? ? 9:這個(gè)函數(shù)還有剩下的一部分,如圖-14.這一部分也是重新遍歷所有的load_commond段,找到__DATA段或者_(dá)_DATA_CONST段,別問(wèn)為什么找這兩個(gè)段,因?yàn)檫@是代碼告訴我的,問(wèn)就是這兩個(gè)段能夠幫助我們找到需要hook的函數(shù)的位置。當(dāng)是滿足是SEG_DATA和SEG_DATA_CONST的時(shí)候,就遍歷這個(gè)load_commond,取到type為S_LAZY_SYMBOL_POINTERS和N_NON_LAZY_SYMBOL_POINTERS這兩種類型的section,因?yàn)橄到y(tǒng)的C函數(shù)會(huì)在lz_symbol_ptr里面。對(duì)應(yīng)machO文件的__got和__la_symbol_ptr段,如圖-15.

圖-14

反正我打印這個(gè)sectname,發(fā)現(xiàn)也是需要從__got這個(gè)類型里面找的。這個(gè)sect是為了找到section為la_symbol_ptr的偏移地址,圖16。至此這一階段所有需要查找的偏移地址都已經(jīng)找到了,在圖-13和圖16里面,使我們后面會(huì)用上的地址。看到這個(gè)地方的你還好嘛。

圖-15


圖-16


? ? ? ? 10:接著看下一個(gè)重量級(jí)的函數(shù)perform_rebinding_with_section,這個(gè)函數(shù)需要的參數(shù)就是我們上面列出來(lái)的4個(gè)加上一個(gè)ASLR的slide和一個(gè)我們傳進(jìn)來(lái)的需要替換的函數(shù)的指針結(jié)構(gòu)體rebindings,總共六個(gè)參數(shù)。這里擺出一個(gè)很多博客都說(shuō)的結(jié)論,就是Dynamic Symbol Table符號(hào)表里面符號(hào)出現(xiàn)的下標(biāo)和Lazy_symbol_ptr里面出現(xiàn)的是一樣的。大概的意思就是說(shuō)假如我需要hook的NSLog,假如NSLog在Symbol里面出現(xiàn)的下標(biāo)是100,那么在Dynamic Symbol Table出現(xiàn)的下標(biāo)也是100,我們可以證明一下這個(gè)結(jié)論.看下圖-17,18,19,20,21


圖-17? ?
圖-18
圖-19
圖-20
圖-21

可以看到,圖17是起始地址,圖18是NSLog的地址,兩者偏移為圖21的(0xA8520-0xa8240)/8=92個(gè)字節(jié),因?yàn)槊恳粋€(gè)是8個(gè)字節(jié),看圖17能夠看出來(lái)是8個(gè)字節(jié)的增長(zhǎng)。然后看圖19的起始地址是0x1258B0+92*4=0x125a20(每一個(gè)占用4個(gè)字節(jié),圖19可以看出來(lái)),剛好就是NSLog所在的偏移地址。至此下標(biāo)的問(wèn)題得以證明。接著繼續(xù)看。

? ? ? ? ?11:圖-22看代碼,我字節(jié)看的時(shí)候做了很多筆記,方便理解。這個(gè)函數(shù)的參數(shù)就是從前面那一個(gè)函數(shù)求得到的。紅框標(biāo)出來(lái)的那一個(gè)值我看了很久不知道是什么意思,但是最終我通過(guò)取值和對(duì)比地址,發(fā)現(xiàn)剛好就是在indirect_symbol_tab偏移了部分TEXT段,得到的第一個(gè)DATA段,看圖-23,24

圖-22
圖-23
圖24

可以看到圖-23通過(guò)實(shí)際的地址-slide-虛擬空出來(lái)的地址,就是偏移地址,而這個(gè)地址就是圖-24_CATransform3dIdentity的偏移地址,接著往下面運(yùn)行你會(huì)發(fā)現(xiàn)char*symbol_name = strtab + strtab_offset; 這個(gè)符號(hào)symbol_name剛好就是等于_CATransform3dIdentity,就是說(shuō)遍歷符號(hào)沒(méi)有從頭開(kāi)始,而是從某一個(gè)DATA開(kāi)始。剩下的循環(huán)的代碼和之前的也是差不多的。具體分析一下。之前就說(shuō)過(guò)獲取這section就是為了從addr里面拿到indirect_symbol_table的偏移地址,和slide相加就是內(nèi)存中的實(shí)際地址.下面那一個(gè)for循環(huán)的意思就是通過(guò)section的數(shù)量來(lái)遍歷。第4行的indirect_symbol_indices[i]表示的是指針往后移動(dòng),也就是一個(gè)一個(gè)的indirect_symbol遍歷,取到下標(biāo),然后從symtab里面取到具體的符號(hào)偏移地址,然后加上strtab_offset就是得到了這個(gè)符號(hào)的地址,下面的代碼就是比較兩個(gè)符號(hào)是否相同,相同就就hook處理,不相同就不處理。

圖-25

? ? ? ? 13:根據(jù)machO文件來(lái)說(shuō),indirect Symbols的Data字段就是要算出下標(biāo),和SymbolTable的下標(biāo)相對(duì)應(yīng),然后Symbol Table的DATA字段是對(duì)應(yīng)的StringTable的偏移值,通過(guò)遍歷section得到下標(biāo)symtab_index,然后SymbolTab去下標(biāo)為symtab_index的元素的n_strx字段得到StringTable的符號(hào)地址,然后比較符號(hào)是否和我們要hook的相同。其實(shí)最終的目的就是要取符號(hào)表和我們要hook的函數(shù)做比較。
if(strcmp(&symbol_name[1], cur->rebindings[j].name) ==0) 在比較兩個(gè)符號(hào)是否相等使用了這個(gè)表達(dá)式symbol_name[1]這個(gè)函數(shù)不是很懂,取第一個(gè)字符,也就是元素下標(biāo)為1的首地址?然后取值?沒(méi)太明白

?至此,fishhook的代碼就分析完畢。還有幾個(gè)疑問(wèn),圖-25里面那個(gè)section->reserved1這個(gè)值是否是我猜的那樣?如果是我猜的那樣,那么圖-20那張圖的NSLog對(duì)象的_TEXT段又怎么解釋,或者說(shuō)為什么兩個(gè)NSLog取了第一個(gè)?如有錯(cuò)誤歡迎指正,如有疑問(wèn)歡迎和我討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容