iOS逆向(6)-從fishhook看Runtime,Hook系統函數

在上篇文章不知MachO怎敢說本身懂DYLD中已經詳細介紹了MachO,而且由MachO引出了dyld,再由dyld講述了App的啓動流程,而在App的啓動流程中又說到了一些關鍵的名稱如:LC_LOAD_DYLINKERLC_LOAD_DYLIB以及objc的回調函數_dyld_objc_notify_register等等。而且在末尾提出了MachO中還有一些符號表,而有哪些符號表,這些符號表又有些什麼用呢?筆者在這篇文章就將一一道來。git

老規矩,片頭先上福利:點擊下載demo,demo中有筆者給fishhook每句代碼加的詳細註釋!!! 這篇文章會用到的工具備:github

在開始正文以前,假設面試官問了一個問題:
都知道Objective-C最大的特性就是runtime,你們能夠用使用runtime對OC的方法進行hook,那麼C函數能不能hook?面試

有興趣回答的朋友能夠先行在評論區回答,答完以後再繼續閱讀或者預先偷窺一下文末的答案,看看這被炒了無數次冷飯的runtime本身是否真的瞭然於胸。緩存

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

  • Runtime的Hook原理
  • 爲何C不能hook
  • 如何利用MachO「玩壞」系統C函數
  • fishhook源碼分析
  • 綁定系統C函數過程驗證

1、Runtime的Hook原理

Runtime,從名稱上就知道是運行時,也是它造就了OC運行時的特性,而要想完全明白什麼是運行時,那麼就須要將之與C語言有相比較。
今天我們就從彙編的角度看一看OC和C在調用方法(函數)上有什麼區別。app

注:筆者使用的是iPhone 7徵集調試,全部一下彙編都是基於arm64,因此如下全部彙編默認爲基於arm64。框架

新建一個工程取名爲:FishhookDemo
敲入兩個OC方法mylogmylog2,掛上斷點,如圖:
ide

OC方法.png

開啓彙編斷點,如圖:
函數

設置彙編斷點.png

運行工程,會跳轉到以下圖的彙編斷點: 工具

OC彙編斷點.png

從上圖能夠看的出來調用了兩個objc_msgSend,這兩個很像是 咱們的mylogmylog2,但如今還不能肯定。
想想objc_msgSend的定義:

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

第一個參數是self,第二個參數是SEL,因此能夠知道SEL是放在x1的寄存器裏面(什麼是x1?繼續關注做者,以後的文章會有相關的彙編的專門篇章)。

快馬加鞭,掛上兩個彙編斷點,查看一下兩個x1中存放的究竟是什麼,如圖:

mylog1.png

mylog2.png

這也就驗證了我們OC方法都是消息轉發(objc_msgSend)。而同一個C函數的地址又都是同樣的(筆者此次運行的地址就是0x1026ce130) 。

因此在每次調用OC方法的時候就讓咱們有了一次改變消息轉發「目標」的機會。

這裏稍微提一下runtime的源碼分析流程:
Step 一、方法查找
① 彙編快速查找緩存
② C/C++慢速查找:self->super->NSObject->找到換緩存起來
Step 二、動態方法解析: _class_resolveMethod
_class_resolveInstanceMethod
_class_resolveClassMethod
Step 三、消息轉發
_forwardingTargetForSelector
_methodSignatureForSelector
_forwardInvocation
_doesNotRecognizeSelector

2、爲何C不能hook

一樣咱們從彙編的角度切入。
敲入代碼一些C函數,掛上斷點,如圖:

C函數.png

運行工程:
會看到斷點斷到以下彙編:

彙編斷點.png

能夠看到每一個NSLog對應跳轉的地址都是0x10000a010,每一個printf對應跳轉的地址都是0x10000a184,也就是說每一個C的函數都是一一對應着一個真實的地址空間。每次在調用一個C函數的時候都是執行一句彙編bl 0xXXXXXXXX

因此上面講述到的消息轉發的機會沒有了,也就是沒有了利用runtime來Hook的機會了。

3、如何利用MachO「玩壞」系統C函數

既然如此,那麼是否C函數就真的那麼牢不可破,沒法對他進行Hook呢?
答案確定是否認的!
想要從根上理解這個問題,首先要了解:咱們的C函數分爲系統C函數和咱們自定義的C函數。

一、自定義的C函數

在上面的步驟中咱們已經瞭解到全部C函數的調用都是跳轉到一個「固定的地址」,那麼就能夠推斷得出這個「固定的地址」實際上是在編譯期已經被生成好了,因此才能快速、直接的跳轉到這個地址,實現函數調用。
C語言被稱之爲是靜態語言也就是這麼個理。

二、系統的C函數

在上篇文章不知MachO怎敢說本身懂DYLD已經提到了在dyld啓動app的第二個步驟就是加載共享緩存庫,共享緩存庫包括Foundation框架,NSLog是被包含在Foundation框架的。那麼就能夠肯定一件事情,在咱們將本身工程打包出的MachO文件中是不可能預先肯定NSLog的地址的。

可是又由於C語言是靜態的特性,無法在運行的時候實時獲取共享緩存庫中NSLog的地址。而共享緩存庫的存在好處太大,既能節省大量內存,又能加快啓動速度提高性能,不能棄之而不用。

爲了解決這個問題,Apple使用了PIC(Position-independent code)技術,在第一次使用對應函數(NSLog)的時候,從系統內存中將對函數(NSLog)的內存地址取出,綁定到APP中對應函數(NSLog)上,就能夠實現正常的C函數(NSLog)調用了。

既然有這麼個過程,iOS系統能夠動態的綁定系統C函數的地址,那麼我們就也能。

4、fishhook源碼分析

一、fishhook的整體思路

Facebook的開源庫fishhook就能夠完美的實現這個任務。
先上一張官網原理圖:

fishhook原理圖.png

整體來講,步驟是這樣的:

  • 先找到四張表Lazy Symbol Pointer Table、Indirect Symbol Table、Symbol Table、String Table。
  • MachO有個規律:Lazy Symbol Pointer Table中第index行表明的函數和Indirect Symbol Table中第index行表明的函數是同樣的。
  • Indirect Symbol Table中value值表示Symbol Table的index。
  • 找到Symbol Table的中對應index的對象,其data表明String Table的偏移值。
  • 用String Table的基值,也就是第一行的pFile值,加上Symbol Table的中取到的偏移值,就能獲得Indirect Symbol Table中value(這個value表明函數的偏移值)表明的函數名了。

二、驗證NSLog地址

下面就來驗證一下在NSLog的地址是否是真的就存在Indirect Symbol Table中。 一樣在NSLog處下好斷點,打開彙編斷點,運行代碼。會發現斷點斷在以下入位置:

NSLog斷點.png
注:筆者的工程從新build了,MachO也從新生成,因此此處的截圖和上文中斷住NSLog的截圖的地址不同,這是正常狀況。

能夠發現NSLog的地址是0x104d36010,先記住這個值。

而後查看咱們APP在內存中的偏移值。
利用image list命令列出全部image,第一個image就是咱們APP的偏移值,也就是內存地址。

APP在內存中的偏移值.png

能夠看到APP在內存中的偏移值爲0x104d30000
接着打開MachOView查看MachO中的Indirect Symbol Table中的value,如圖:

函數偏移地址.png

其值爲0x100006010,去除最高位獲得的0x6010就是NSLog在MachO中的偏移值。 最後將NSLog在MachO中的偏移值於APP在內存中的偏移值相加就獲得NSLog真實的內存地址:
0x6010+0x104d30000=0x104d36010

最終證實,在Indirect Symbol Table的value中的值就是其對應的函數的地址!!!

三、根據MachO的表查找對應的函數名和函數地址

我們仍是用NSLog來距離查找。

Step一、Indirect Symbol Table

取出其data值0000010A,用10進製表示,結果爲266,如圖:

Indirect Symbols Table.png

Step二、Symbol Table

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

Symbols Table.png

Step三、String Table

將在Symbols中獲得的偏移值0x124加上String Table的首個地址DC6C,獲得值DD90,而後找到pFile爲DD90的值,以下兩圖:

String Table 1.png

String Table 2.png

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

四、源碼分析

fishhook的源碼總共只有250行左右,因此結合MachO慢慢看,其實一點也不費勁,在筆者的demo中有對其每一句函數的詳細註釋。固然也有對fishhook使用的demo。

因此筆者就不在此處對fishhook作太過詳細的介紹了。只對其中一些關鍵參數和關鍵函數作介紹。

  • fishhook爲維護一個鏈表,用來儲存須要hook的全部函數
// 給須要rebinding的方法結構體開闢出對應的空間
// 生成對應的鏈表結構(rebindings_entry),並將新的entry插入頭部
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel)
複製代碼
  • 根據linkedit的基值,找到對應的三張表:symbol_table、string_table和indirect_symtab :
// 找到linkedit的頭地址
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);
複製代碼
  • 最核心的一個步驟,查找而且替換目標函數:
// 在四張表(section,symtab,strtab,indirect_symtab)中循環查找
// 直到找到對應的rebindings->name,將原先的函數複製給新的地址,將新的函數地址賦值給原先的函數
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)
複製代碼

5、綁定系統C函數過程驗證

上面說了這麼多,那麼我們來驗證一下系統C函數是否是真的會這樣被綁定起來,而且看一看,是在何時綁定的。

一樣,在第一次敲入NSLog函數的地方加上斷點,在第二個NSLog處也加上斷點:

兩個NSLog斷點.png

運行工程後,使用dis -s命令查看該函數的彙編代碼,而且繼續查看其中第一次b指令,也就是函數調用的彙編,如圖:

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

從上圖就能夠看到,在咱們第一次調用NSLog的時候,系統確實會默認的調用dyld_stub_binder函數對NSLog進行綁定。

繼續跳過這個斷點,進入下一個NSLog的彙編斷點處,一樣利用dis -s命令查看該彙編:

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

獲得答案:
系統確實會在第一次調用系統C函數的時候對其進行綁定!

還記得正文開始的時候的那個問題嗎?
那麼是否是系統C函數能夠hook,而自定義的C函數就絕對不能hook了呢?
很顯然,國內外大神那麼多,確定是能作到的,有興趣的讀者能夠自行查閱Cydia Substrate。

這篇文章利用了一些LLDB命令行看了許多咱們想看的內容,如image listregister read還有dis -s,在咱們正向開發中,LLDB就是一把利器,而在咱們玩逆向的時候,LLDB就成爲了咱們某些是後的惟一途徑了!因此,在下一篇文章中,筆者將會對LLDB進行更加詳細的講解,讓你們看到LLBD的偉大。

相關文章
相關標籤/搜索