fishhook的實現原理淺析

打開姿式很重要

 早些時候,iOS中一提到「黑魔法」、HOOK,不少人第一時間想到的就是 AOP RunTime MethodSwizzling 這些不明覺厲的東西,它們的基本用法其實都不難,真正難的是如何在合適的地方用好它們。git

 任何事物都有兩面性,越強大其可能帶來的隱患也越具備毀滅性。蘋果提供的運行時機制當然大有用處,但若是在項目中濫用(更不是用來當作面試提高逼格的),不少時候只會拔苗助長,詳細誤區請參考iOS界的毒瘤-MethodSwizzlinggithub

 關於 MethodSwizzling 的用法在以前的文章中也有過講解,請參考MethodSwizzling的幾種姿式。 該方式更多的用於性能監測、 crash 的兼容和上報、反破解防禦等一些工具的開發中,而在逆向中,在面對有相應安全防禦措施的應用時,其用武之地比較有限。面試

 無獨有偶,「黑魔法」可不僅有 RunTime ,今天咱們來聊聊在逆向中經常使用的另外一種HOOK方式:fishhook緩存

fishhook 背後的故事

(一)實現原理

fishhook 是 FaceBook 開源的能夠動態修改 MachO 符號表的工具。fishhook 的強大之處在於它能夠 HOOK 系統的靜態 C 函數。sass

 你們都知道 OC 的方法之因此能夠 HOOK 是由於它的運行時特性,OC 的方法調用在底層都是 msg_send(id,SEL)的形式,這爲咱們提供了交換方法實現(IMP)的機會,但 C 函數在編譯連接時就肯定了函數指針的地址偏移量(Offset),這個偏移量在編譯好的可執行文件中是固定的,而可執行文件每次被從新裝載到內存中時被系統分配的起始地址(在 lldb 中用命令image List獲取)是不斷變化的。運行中的靜態函數指針地址其實就等於上述 Offset + Mach0 文件在內存中的首地址: 安全

 既然 C 函數的指針地址是相對固定且不可修改的,那麼 fishhook 又是怎麼實現 對 C 函數的 HOOK 呢?其實內部/自定義的 C 函數 fishhook 也 HOOK 不了,它只能HOOK Mach-O 外部(共享緩存庫中)的函數。fishhook 利用了 MachO 的動態綁定機制(不清楚的同窗看這裏:MachO 文件結構詳解dyld背後的故事&源碼分析 ):蘋果的共享緩存庫不會被編譯進咱們的 MachO 文件,而是在動態連接時纔去從新綁定。蘋果採用了PIC(Position-independent code)技術成功讓 C 的底層也能有動態的表現:bash

  • 編譯時在 Mach-O 文件 _DATA 段的符號表中爲每個被引用的系統 C 函數創建一個指針(8字節的數據,放的全是0),這個指針用於動態綁定時重定位到共享庫中的函數實現。
  • 在運行時當系統 C 函數被第一次調用時會動態綁定一次,而後將 Mach-O 中的 _DATA 段符號表中對應的指針,指向外部函數(其在共享庫中的實際內存地址)。

fishhook 正是利用了 PIC 技術作了這麼兩個操做:app

  • 將指向系統方法(外部函數)的指針從新進行綁定指向內部函數/自定義 C 函數。
  • 將內部函數的指針在動態連接時指向系統方法的地址。

這樣就把系統方法與本身定義的方法進行了交換,達到 HOOK 系統 C 函數(共享庫中的)的目的。函數

(二)用匯編解析過程

 爲了更好的理解 fishhook 是如何 HOOK 系統的 C 函數,咱們以 HOOK NSLog 爲例,從彙編着手來一步步去分析,爲你們扒開 fishhook 實現 HOOK 系統 NSLog 的全過程。工具

注:對於非懶加載符號表,dyld 會在動態連接時就連接動態庫
    對於懶加載符號表,dyld 會在運行時函數第一次被調用時動態綁定一次
    NSLog 在懶加載表中
複製代碼

1.驗證系統的動態綁定:

 新建一個空工程,寫下這兩行代碼:

 編譯一下工程,在目錄Products下將 .app內的可執行文件拷出用 MachOView 打開:
記下0x3028這個偏移值,這就是用於重定向到共享庫中的那個指針相對於 MachO文件的偏移量。

在兩個 NSLog 處分別加上斷點,將工程 Run 起來,把 Debug -> Debug Workflow -> Always Show Disassembly 勾選上,用於查看彙編信息,斷點斷住後獲取 MachO 在內存中的首地址:

0x3028+0x000000010b0f7000 就是用於重定向到共享庫中的那個指針的內存地址。此時咱們查看該地址是否已經被重定向:

  1. 拿到該指針當前保存的值,iOS 的 CPU 是小端序,當前機型爲 64 位 CPU,因此倒序讀 8 個字節就是指針的值:0x010b0f89a0
  2. dis -s 是反彙編命令,咱們發現此時該指針指向的函數正在調用系統動態綁定的函數
  3. 進一步查看調用函數詳細信息:libdyld.dylib`dyld_stub_binder

這是在幹嗎?沒錯,這就是第一次調用 NSLog 時系統去從新綁定位懶加載符號表中 NSLog 對應的指針所指向的位置。

接下來咱們過掉第一次斷點,讓斷點斷在第二個 NSLog 處,再次查看符號表中該指針(依然是 0x3028+0x000000010b0f7000 這個地址)所指向的地址,

 咱們發現,它指向的地址由以前的 0x010b0f89a0 變爲 0x010b491276 了,對應的函數也由以前的 dyld_stub_binder 變爲 NSLog ,這意味着該函數的動態綁定已經完成。以上,咱們驗證了 iOS 的動態綁定全過程。

2.驗證 fishhook 的重綁定:

 咱們將 fishhook文件拖入工程,並添加一個簡單的綁定:

注意:修改文件後從新編譯的 MachO 文件,符號表裏的指針偏移值可能會改變,從新運行的程序內存首地址也會發生變化,須要你從新拿到它們計算得出指針新的內存地址。

咱們運行起來以後點擊屏幕進入上圖所示斷點,查看符號表中本來指向系統 NSLog 的指針指向:

此時該指針的指向被修改成咱們自定義的函數 myNslog 了,而將系統重定位的外部函數地址保存到了另外一個自定義函數指針 sys_nslog 中:
以上,咱們經過斷點分析彙編信息,驗證了 fishhook 實現 HOOK 系統外部函數的思路。接下來咱們結合 fishhook 的官方說明看它是如何根據字符串(方法名)找到對應指針在符號表中的偏移值的。

(三)fishhook 是如何根據字符串對應在符號表中的指針,找到其在共享庫的函數實現的?

fishhook 官方給了這張圖:

這張圖主要在描述如何由一個字符串(好比 "NSLog"),跟着它在 MachO 文件的懶加載表中對應的指針,一步步的找到該指針指向的函數實現地址,大體步驟以下:

  1. 在 Lazy Symbol Pointers 中該字符串的順序就是其在 Dynamic Symbols Table -> Indirect Symbols 表中的位置(這裏是第一個)
    在實際計算地址中用到了 Load Commands 中對應頭信息的 Reserved1 的 value (section基地址+ 偏移量 value = 其在 Indirect Symbols 中對應的 offset):下篇源碼分析有詳細說明。
  2. Dynamic Symbols Table -> Indirect Symbols 表中的第一個對應的 Data 值(0x7A=122)就是其在Symbols Table -> Symbols 中的索引。
  3. 在 Symbols Table -> Symbols 中索引爲 122 的位置對應的 Data = 0x9B:
  4. 上表中的 Data(0x9B) + String Table的起始地址(0x4F04)就是目標函數實現的地址:

總結

 今天咱們結合 iOS 的共享緩存庫中採用的 PIC 技術,介紹了 fishhook 對系統外部函數實現 HOOK 基本原理和具體過程,並經過反彙編命令一一驗證了 iOS 的動態綁定過程和 fishhook 的從新綁定機制,最後把 fishhook 在符號表中根據符號指針尋找函數實現的步驟作了演示。 願你有所收穫! 水平有限,請多指教~

 鑑於篇幅過長會影響你們的閱讀體驗,fishhook 的源碼分析與應用場景以及安全防禦的分享,咱們這裏繼續

相關文章
相關標籤/搜索