iOS逆向(6)-從fishhook看runtime伪节,hook系統(tǒng)C函數(shù)

在上篇文章不知MachO怎敢說自己懂DYLD中已經(jīng)詳細介紹了MachO吼砂,并且由MachO引出了dyld顷帖,再由dyld講述了App的啟動流程美旧,而在App的啟動流程中又說到了一些關鍵的名稱如:LC_LOAD_DYLINKER渤滞、LC_LOAD_DYLIB以及objc的回調(diào)函數(shù)_dyld_objc_notify_register等等。并且在末尾提出了MachO中還有一些符號表榴嗅,而有哪些符號表妄呕,這些符號表又有些什么用呢?筆者在這篇文章就將一一道來嗽测。

老規(guī)矩绪励,片頭先上福利:點擊下載demo,demo中有筆者給fishhook每句代碼加的詳細注釋_胫唷J栉骸!
這篇文章會用到的工具有:

在開始正文之前晤愧,假設面試官問了一個問題:
都知道Objective-C最大的特性就是runtime大莫,大家可以用使用runtime對OC的方法進行hook,那么C函數(shù)能不能hook官份?

有興趣回答的朋友可以先行在評論區(qū)回答只厘,答完之后再繼續(xù)閱讀或者預先偷窺一下文末的答案,看看這被炒了無數(shù)次冷飯的runtime自己是否真的了然于胸舅巷。

本將從以下幾方面回答上面所提的問題:

  • Runtime的Hook原理
  • 為什么C不能hook
  • 如何利用MachO“玩壞”系統(tǒng)C函數(shù)
  • fishhook源碼分析
  • 綁定系統(tǒng)C函數(shù)過程驗證

一羔味、Runtime的Hook原理

Runtime,從名稱上就知道是運行時钠右,也是它造就了OC運行時的特性赋元,而要想徹底明白什么是運行時,那么就需要將之與C語言有相比較飒房。
今天咱們就從匯編的角度看一看OC和C在調(diào)用方法(函數(shù))上有什么區(qū)別搁凸。

注:筆者使用的是iPhone 7征集調(diào)試,所有一下匯編都是基于arm64情屹,所以以下所有匯編默認為基于arm64坪仇。

新建一個工程取名為:FishhookDemo
敲入兩個OC方法mylogmylog2杂腰,掛上斷點垃你,如圖:

OC方法.png

開啟匯編斷點,如圖:

設置匯編斷點.png

運行工程喂很,會跳轉到如下圖的匯編斷點:


OC匯編斷點.png

從上圖可以看的出來調(diào)用了兩個objc_msgSend惜颇,這兩個很像是
我們的mylogmylog2,但現(xiàn)在還不能確定少辣。
想一想objc_msgSend的定義:

OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

第一個參數(shù)是self凌摄,第二個參數(shù)是SEL,所以可以知道SEL是放在x1的寄存器里面(什么是x1漓帅?繼續(xù)關注作者锨亏,之后的文章會有相關的匯編的專門篇章)痴怨。

馬不停蹄,掛上兩個匯編斷點器予,查看一下兩個x1中存放的到底是什么浪藻,如圖:

mylog1.png

mylog2.png

這也就驗證了咱們OC方法都是消息轉發(fā)(objc_msgSend)即碗。而同一個C函數(shù)的地址又都是一樣的(筆者這次運行的地址就是0x1026ce130) 客冈。

所以在每次調(diào)用OC方法的時候就讓我們有了一次改變消息轉發(fā)「目標」的機會。

這里稍微提一下runtime的源碼分析流程:
Step 1纸俭、方法查找
① 匯編快速查找緩存
② C/C++慢速查找:self->super->NSObject->找到換緩存起來
Step 2反浓、動態(tài)方法解析: _class_resolveMethod
_class_resolveInstanceMethod
_class_resolveClassMethod
Step 3萌丈、消息轉發(fā)
_forwardingTargetForSelector
_methodSignatureForSelector
_forwardInvocation
_doesNotRecognizeSelector

二、為什么C不能hook

同樣我們從匯編的角度切入雷则。
敲入代碼一些C函數(shù)辆雾,掛上斷點,如圖:

C函數(shù).png

運行工程:
會看到斷點斷到如下匯編:

匯編斷點.png

可以看到每個NSLog對應跳轉的地址都是0x10000a010月劈,每個printf對應跳轉的地址都是0x10000a184乾颁,也就是說每個C的函數(shù)都是一一對應著一個真實的地址空間。每次在調(diào)用一個C函數(shù)的時候都是執(zhí)行一句匯編bl 0xXXXXXXXX艺栈。

所以上面講述到的消息轉發(fā)的機會沒有了英岭,也就是沒有了利用runtime來Hook的機會了。

三湿右、如何利用MachO“玩壞”系統(tǒng)C函數(shù)

既然如此诅妹,那么是否C函數(shù)就真的那么牢不可破,無法對他進行Hook呢毅人?
答案肯定是否定的吭狡!
想要從根上理解這個問題,首先要了解:我們的C函數(shù)分為系統(tǒng)C函數(shù)和我們自定義的C函數(shù)丈莺。

1划煮、自定義的C函數(shù)

在上面的步驟中我們已經(jīng)了解到所有C函數(shù)的調(diào)用都是跳轉到一個「固定的地址」,那么就可以推斷得出這個「固定的地址」其實是在編譯期已經(jīng)被生成好了缔俄,所以才能快速弛秋、直接的跳轉到這個地址,實現(xiàn)函數(shù)調(diào)用俐载。
C語言被稱之為是靜態(tài)語言也就是這么個理蟹略。

2、系統(tǒng)的C函數(shù)

在上篇文章不知MachO怎敢說自己懂DYLD已經(jīng)提到了在dyld啟動app的第二個步驟就是加載共享緩存庫遏佣,共享緩存庫包括Foundation框架挖炬,NSLog是被包含在Foundation框架的。那么就可以確定一件事情状婶,在我們將自己工程打包出的MachO文件中是不可能預先確定NSLog的地址的意敛。

但是又因為C語言是靜態(tài)的特性馅巷,沒法在運行的時候實時獲取共享緩存庫中NSLog的地址。而共享緩存庫的存在好處太大草姻,既能節(jié)省大量內(nèi)存令杈,又能加快啟動速度提升性能,不能棄之而不用碴倾。

為了解決這個問題逗噩,Apple使用了PIC(Position-independent code)技術,在第一次使用對應函數(shù)(NSLog)的時候跌榔,從系統(tǒng)內(nèi)存中將對函數(shù)(NSLog)的內(nèi)存地址取出异雁,綁定到APP中對應函數(shù)(NSLog)上,就可以實現(xiàn)正常的C函數(shù)(NSLog)調(diào)用了僧须。

既然有這么個過程纲刀,iOS系統(tǒng)可以動態(tài)的綁定系統(tǒng)C函數(shù)的地址,那么咱們就也能担平。

四示绊、fishhook源碼分析

1、fishhook的總體思路

Facebook的開源庫fishhook就可以完美的實現(xiàn)這個任務暂论。
先上一張官網(wǎng)原理圖:

fishhook原理圖.png

總體來說面褐,步驟是這樣的:

  • 先找到四張表Lazy Symbol Pointer Table、Indirect Symbol Table取胎、Symbol Table展哭、String Table。
  • MachO有個規(guī)律:Lazy Symbol Pointer Table中第index行代表的函數(shù)和Indirect Symbol Table中第index行代表的函數(shù)是一樣的闻蛀。
  • Indirect Symbol Table中value值表示Symbol Table的index匪傍。
  • 找到Symbol Table的中對應index的對象,其data代表String Table的偏移值觉痛。
  • 用String Table的基值役衡,也就是第一行的pFile值,加上Symbol Table的中取到的偏移值薪棒,就能得到Indirect Symbol Table中value(這個value代表函數(shù)的偏移值)代表的函數(shù)名了手蝎。

2、驗證NSLog地址

下面就來驗證一下在NSLog的地址是不是真的就存在Indirect Symbol Table中盗尸。
同樣在NSLog處下好斷點柑船,打開匯編斷點帽撑,運行代碼泼各。會發(fā)現(xiàn)斷點斷在如下入位置:


NSLog斷點.png

注:筆者的工程重新build了,MachO也重新生成亏拉,所以此處的截圖和上文中斷住NSLog的截圖的地址不一樣扣蜻,這是正常情況逆巍。

可以發(fā)現(xiàn)NSLog的地址是0x104d36010,先記住這個值莽使。

然后查看我們APP在內(nèi)存中的偏移值锐极。
利用image list命令列出所有image,第一個image就是我們APP的偏移值芳肌,也就是內(nèi)存地址灵再。

APP在內(nèi)存中的偏移值.png

可以看到APP在內(nèi)存中的偏移值為0x104d30000
接著打開MachOView查看MachO中的Indirect Symbol Table中的value亿笤,如圖:

函數(shù)偏移地址.png

其值為0x100006010翎迁,去除最高位得到的0x6010就是NSLog在MachO中的偏移值。
最后將NSLog在MachO中的偏移值于APP在內(nèi)存中的偏移值相加就得到NSLog真實的內(nèi)存地址:
0x6010+0x104d30000=0x104d36010

最終證明净薛,在Indirect Symbol Table的value中的值就是其對應的函數(shù)的地址M衾啤!肃拜!

3痴腌、根據(jù)MachO的表查找對應的函數(shù)名和函數(shù)地址

咱們還是用NSLog來距離查找。

1燃领、Indirect Symbol Table

取出其data值0000010A士聪,用10進制表示,結果為266猛蔽,如圖:

Indirect Symbols Table.png

2戚嗅、Symbol Table

在Symbol Table中找到下標(offset)為266的的對象,取出其data0x124枢舶,如圖:

Symbols Table.png

3懦胞、String Table

將在Symbols中得到的偏移值0x124加上String Table的首個地址DC6C,得到值DD90凉泄,然后找到pFile為DD90的值躏尉,如下兩圖:

String Table 1.png

String Table 2.png

上述就是根據(jù)MachO的表查找對應的函數(shù)名和函數(shù)地址全過程了。

4后众、源碼分析

fishhook的源碼總共只有250行左右胀糜,所以結合MachO慢慢看,其實一點也不費勁蒂誉,在筆者的demo中有對其每一句函數(shù)的詳細注釋教藻。當然也有對fishhook使用的demo。

所以筆者就不在此處對fishhook做太過詳細的介紹了右锨。只對其中一些關鍵參數(shù)和關鍵函數(shù)做介紹括堤。

  • fishhook為維護一個鏈表,用來儲存需要hook的所有函數(shù)
// 給需要rebinding的方法結構體開辟出對應的空間
// 生成對應的鏈表結構(rebindings_entry),并將新的entry插入頭部
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel)
  • 根據(jù)linkedit的基值悄窃,找到對應的三張表:symbol_table讥电、string_table和indirect_symtab :
// 找到linkedit的頭地址
// linkedit_base其實就是MachO的頭地址!T埂恩敌!可以通過查看linkedit_base值和image list命令查看驗證!:崦摹>琅凇(文末附有驗證圖)
/**********************************************************
 Linkedit虛擬地址 = PAGEZERO(64位下1G) + FileOffset
 MachO地址 = PAGEZERO + ASLR
 上面兩個公式是已知的 得到下面這個公式
 MachO文件地址 = Linkedit虛擬地址 - FileOffset + ASLR(slide)
**********************************************************/
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
// 獲取symbol_table的真實地址
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
// 獲取string_table的真實地址
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
// 獲取indirect_symtab的真實地址
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
  • 最核心的一個步驟,查找并且替換目標函數(shù):
// 在四張表(section,symtab,strtab,indirect_symtab)中循環(huán)查找
// 直到找到對應的rebindings->name,將原先的函數(shù)復制給新的地址灯蝴,將新的函數(shù)地址賦值給原先的函數(shù)
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                           section_t *section,
                                           intptr_t slide,
                                           nlist_t *symtab,
                                           char *strtab,
                                           uint32_t *indirect_symtab)

五抗碰、綁定系統(tǒng)C函數(shù)過程驗證

上面說了這么多,那么咱們來驗證一下系統(tǒng)C函數(shù)是不是真的會這樣被綁定起來绽乔,并且看一看弧蝇,是在什么時候綁定的。

同樣折砸,在第一次敲入NSLog函數(shù)的地方加上斷點看疗,在第二個NSLog處也加上斷點:

兩個NSLog斷點.png

運行工程后,使用dis -s命令查看該函數(shù)的匯編代碼睦授,并且繼續(xù)查看其中第一次b指令,也就是函數(shù)調(diào)用的匯編去枷,如圖:

第一次NSLog匯編斷點+dis -s.png

從上圖就可以看到,在我們第一次調(diào)用NSLog的時候删顶,系統(tǒng)確實會默認的調(diào)用dyld_stub_binder函數(shù)對NSLog進行綁定竖螃。

繼續(xù)跳過這個斷點逗余,進入下一個NSLog的匯編斷點處,同樣利用dis -s命令查看該匯編:

第二次NSLog匯編斷點+dis -s.png

得到答案:
系統(tǒng)確實會在第一次調(diào)用系統(tǒng)C函數(shù)的時候對其進行綁定录粱!

還記得正文開始的時候的那個問題嗎腻格?
那么是不是系統(tǒng)C函數(shù)可以hook啥繁,而自定義的C函數(shù)就絕對不能hook了呢?
很顯然旗闽,國內(nèi)外大神那么多酬核,肯定是能做到的,有興趣的讀者可以自行查閱Cydia Substrate愁茁。

這篇文章利用了一些LLDB命令行看了許多我們想看的內(nèi)容蚕钦,如image list鹅很,register read還有dis -s,在我們正向開發(fā)中罪帖,LLDB就是一把利器,而在我們玩逆向的時候整袁,LLDB就成為了我們某些是后的唯一途徑了!所以坐昙,在下一篇文章中绳匀,筆者將會對LLDB進行更加詳細的講解,讓大家看到LLBD的偉大炸客。


  • 關于道友AmazingYu的提問:

想問下 linkedit_base 地址與 Text 段的初始地址以及 Data 段的初始地址的關系疾棵,這三個段在內(nèi)存中是挨著的嗎,還有就是 linkedit_base 大概在進程內(nèi)存分布中的哪個地方痹仙?

在咨詢大佬請叫我Hank后是尔,得到最終答案,在下面問回答中有一些問題开仰,再此糾正一下拟枚!
linkedit地址(不是linkedit_base,末尾會介紹linkedit_base到底是什么) 與 Text 段的初始地址以及 Data 段確實是連續(xù)的众弓,他們的順序是:
先是Text 段恩溅,然后是Data 段,最后是linkedit_base 地址谓娃。從下面三幅圖的File Offset和File Size可以看出來暴匠,兩者相加就能得到下一段的地址:

TEXT段.png
DATA段.png
LINKEDIT.png
  • 幾個名詞(pFile 、offset 傻粘、File Offset)之前解釋的有點問題:
    1每窖、首先,這三個都是表示相對于MachO的內(nèi)存偏移弦悉,只不過其含義被細分了窒典。
    2、pFile 和 offset含義相近稽莉,不過offset更詳細瀑志,能夠對應上具體某一個符號(DATA? TEXT?)。比如文件里面有許多類,類里面有許多的屬性劈猪,pFile就代表各個類的偏移值昧甘,offset代表各個屬性的偏移值
    3、File Offset 這個存在于Segment的字段中战得。用于從Segment快速找到其代表的「表」真正的偏移值充边。

最后說一下linkedit_base:
linkedit_base其實代表的就是MachO的真實內(nèi)存地址!
可以從下圖得到驗證


linkedit_base==MachO.png

因為:

Linkedit虛擬地址 = PAGEZERO(64位下1G) + FileOffset 
MachO地址 = PAGEZERO + ASLR
// 上面兩個公式是已知的 所以可以得到下面這個公式
MachO地址 = Linkedit虛擬地址 - FileOffset + ASLR(slide)

也就是fishhook中的:

uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末常侦,一起剝皮案震驚了整個濱河市浇冰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聋亡,老刑警劉巖肘习,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坡倔,居然都是意外死亡漂佩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門罪塔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來投蝉,“玉大人,你說我怎么就攤上這事垢袱∧拱荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵请契,是天一觀的道長咳榜。 經(jīng)常有香客問我,道長爽锥,這世上最難降的妖魔是什么涌韩? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮氯夷,結果婚禮上臣樱,老公的妹妹穿的比我還像新娘。我一直安慰自己腮考,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布踩蔚。 她就那樣靜靜地躺著,像睡著了一般飘蚯。 火紅的嫁衣襯著肌膚如雪馍迄。 梳的紋絲不亂的頭發(fā)上攀圈,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天峦甩,我揣著相機與錄音,去河邊找鬼穴店。 笑死拿穴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的默色。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呕诉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甩挫?” 一聲冷哼從身側響起椿每,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亦渗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體法精,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡痴突,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帮碰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡收毫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出此再,到底是詐尸還是另有隱情,我是刑警寧澤输拇,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站逛裤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏带族。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一蝙砌、第九天 我趴在偏房一處隱蔽的房頂上張望跋理。 院中可真熱鬧,春花似錦前普、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至目木,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刽射,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工懈息, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辫继。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像姑宽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炮车,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

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