趣探 Mach-O:FishHook 解析

Martin_wjl發表於2017-02-13

閱讀 FishHook原始碼之前,你可能需要對以下知識有個簡單的瞭解

本文的闡述順序按照函式呼叫過程來進行

Fishhook 可以做什麼

在此借用阿里百川的一張分析圖,可以比較清晰的瞭解FishHook發揮了哪些作用

852671-57cff46c8aabbe36
阿里百川

FishHook在這裡是對動態連結庫起作用,修改對應的函式實現

對於動態連結庫裡面的C函式,第一次呼叫的時候,我們會得到函式和實現地址的對應關係,函式的實現地址存放在一個叫la_symbol_ptr的地方,第二次呼叫的時候,直接通過la_symbol_ptr找到函式地址就可以,不再需要繁瑣的獲取函式地址的過程。(具體通過哪些過程,可以參考剛才的連結:Mach-O 的動態連結過程

那麼,上圖的含義就很明瞭了

在程式執行時,動態連結的 C 函式dynamic(...)地址記錄在DATA segment下的la_symbol_ptr中;初始時,程式只知道dynamic函式的符號名而不知道函式的實現地址;首次呼叫時,程式通過TEXT segment中的stub_helper取得繫結資訊,通過dyld_stub_binder來更新la_symbol_ptr中的符號實現地址;這樣,再次呼叫時,就可以通過la_symbol_ptr直接找到dynamic函式的實現;如果我們需要替換dynamic函式的實現,只需要修改__la_symbol_ptr即可,也就是我們要談的Fishhook

Fishhook 的實現

通過fishhook的官方文件可以知道,Fishhook的使用方法大致如下:

先從函式的入口,rebind_symbols開始談起吧,rebind_symbols主要是使用_dyld_register_func_for_add_image來註冊回撥函式,在載入動態庫的時候執行一些操作

對於prepend_rebindings的程式碼如下

基礎結構解釋

Dl_info

我們一會經過 dladdr()處理後的有效資訊都會放進這個結構體中

  • fname:路徑名,例如

  • dli_fbase:共享物件的的起始地址(Base address of shared object,比如上面的 CoreFoundation)
  • dli_saddr :符號的地址
  • dli_sname:符號的名字,即下面的第四列的函式資訊

LC_SYMTAB

主要是提供符號表的偏移量,以及元素個數,還有字串表的偏移和其長度。符號表在 Mach-O目標檔案中的地址可以通過LC_SYMTAB載入命令指定的 symoff找到,對應的符號名稱在stroff,總共有nsyms條符號資訊

LC_DYSYMTAB

這個陣列結構有些複雜,有興趣的可以閱讀loader.h檔案,內部標示了動態符號表的偏移量和符號個數

_rebind_symbols_for_image

對於關鍵的程式碼 _rebind_symbols_for_image 如下

為什麼要尋找這個幾個LoadCommand的資訊呢?就如上面介紹的__LINKEDITLC_DYSYMTABLC_SYMTAB都提供了重要的資訊。

__LINKEDIT段 含有為動態連結庫使用的原始資料,比如符號,字串,重定位表條目等等

閱讀下面的程式碼之前,先來看一個計算公式

連結時程式的基址 = __LINKEDIT.VM_Address__LINKEDIT.File_Offset + silde的改變值

這裡出現了一個 slide,那麼slide是啥呢?先看一下ASLR

ASLR:Address space layout randomization,將可執行程式隨機裝載到記憶體中,這裡的隨機只是偏移,而不是打亂,具體做法就是通過核心將 Mach-O的段“平移”某個隨機係數。slide 正是ASLR引入的偏移

也就是說程式的基址等於__LINKEDIT的地址減去偏移量,然後再加上ASLR造成的偏移

符號表中的元素都是nlist_t結構體,nlist_t中有很多學問,這裡先看一下他的基礎結構

然後再次遍歷loadcommands,尋找__DATA__DATA_CONSTsection,並對對__nl_symbol_ptr以及__la_symbol_ptr進行rebind

perform_rebinding_with_section

nl_symbol_ptrla_symbol_ptrsection中的reserved1欄位指明對應的indirect symbol table起始的index

For the two relevant sections, the section headers (struct sections from ) provide an offset (in the reserved1 field) into what is known as the indirect symbol table. The indirect symbol table, which is located in the LINKEDIT segment of the binary, is just an array of indexes into the symbol table (also in LINKEDIT) whose order is identical to that of the pointers in the non-lazy and lazy symbol sections

So, given struct section nl_symbol_ptr, the corresponding index in the symbol table of the first address in that section is indirect_symbol_table[nl_symbol_ptr->reserved1]. The symbol table itself is an array of struct nlists (see ), and each nlist contains an index into the string table in LINKEDIT which where the actual symbol names are stored. So, for each pointer nl_symbol_ptr and __la_symbol_ptr, we are able to find the corresponding symbol and then the corresponding string to compare against the requested symbol names, and if there is a match, we replace the pointer in the section with the replacement.

結合英文,看下面的程式碼就很容易理解

上面的程式碼其實就可以用官方的一個圖片很直觀的表示

 852671-2e85e580334655a7

走到這裡是找到了字串表對應的符號(字串)

如何替換實現

遍歷 rebindings 陣列,符號進行比較,相同的符號就進行實現替換,這裡的程式碼比較清晰,直接貼出

參考連結

相關文章