fishhook-動態修改MachO檔案
學習hook
,不是要攻擊別人,破壞別人的應用場景,而是為了更好的防護,讓自己的應用更堅固更安全。
一、動態庫注入回顧
在 《動態庫注入》中使用了
yololib
對自定義動態庫在WX
應用中插入,既然能插入自定義庫,我們就利用hook
技術做了小小的改動,替換了登入按鈕的方法,並攔截了微信步數上傳的方法,修改了步數。hook
是改變程式執行流程的技術,能夠修改其他應用,也能對自己應用做防護。
通常替換方法有如下:
- 使用
method_exchange
交換sel
的imp
指標; - 通過
getImp
獲取原有函式指標,通過setImp
設定原sel
指向的指標為自定義函式指標; - 通過
getImp
獲取原有函式指標,通過replace
sel
指向的指標為自定義函式指標,通setImp
一樣。
自己專案中使用:
通常在自己專案中使用,建立分類,在分類
load
中來替換方法,以達到監聽方法呼叫的目的。
hook
檢視的出現消失:跟蹤頁面位置;hook
解構函式dealloc
(deinit
):跟蹤物件釋放狀況;hook
訊息轉發方法(forword):避免呼叫未實現方法而產生奔潰;hook
陣列objectAtIndexedSubscript
方法:避免陣列越界奔潰; ……只要是
OC
的方法都可以根據業務需求進行監聽修改或替換。
在其他應用中使用:
通過動態庫注入的方式,來插入自己的
load
,讓自己的程式碼能和其他專案一起執行,同樣使用我們常規的替換方法即可。通過檢視檢視頁面屬性確定要hook
的目標物件。
hook
微信步數獲取方法:實現微信步數修改;hook
微信搶紅包相關方法:實現自動搶紅包功能;hook
微信訊息撤回方法:實現拒絕訊息撤回功能; (ps:學習中,實戰不多,有方法就有更多的可能)在其他應用中是通過動態庫來進行
hook
的,因此在目標物件所在類的方法列表中並沒有要替換的方法,迴歸原有方法呼叫會出現崩潰,這裡可以使用獲取原有方法的函式地址,直接呼叫函式即可實現原有方法的呼叫。
以上是針對OC
的動態特性來hook
的,我們能夠hook
動態語言下的函式,那麼能不能直接hook
系統的靜態函式呢,下面來看一下fishhook
是如何hook
的。
二、fishhook概述
FaceBook
的開源庫,能夠動態的修改MachO
的符號表,來hook
系統的C
函式。 C
函式是在編譯時來確定函式地址在記憶體中的偏移量,編譯後該偏移量是確定的,為什麼說是偏移量是確定的呢?
在很多系統中都採用了 ASLR 技術,對堆、棧、共享庫等線性區佈局進行隨機化,來避免攻擊者直接獲取攻擊程式碼位置。Mach0
的首地址是隨機變化的,找到首地址就能找到對應的函式地址,即 Offset + Mach0
。
靜態語言:編譯時確定函式地址
動態語言:執行時確定函式地址
三、fishhook簡單使用
使用步驟:
1、直接將.c、.h
加入到工程;
2、建立結構體指明被替換的函式、替換的函式、接收原函式地址的指標;
3、繫結符號表。
使用庫函式交換系統NSLog
函式:
#pragma mark - 交換NSLog
-(void)exchangeLog {
NSLog(@"我來了");
//hook NSLog函式
struct rebinding imp;
imp.name = "NSLog";
imp.replacement = my_NSLog;
imp.replaced = &sys_nslog;
//存放rebinding結構體陣列,一次可以交換多個函式
struct rebinding rebs[1] = {imp};
rebind_symbols(rebs,1);
NSLog(@"點選了螢幕");
}
//實現一個函式來替換原有函式-函式名稱即是函式的指標
void my_NSLog(NSString *format,...) {
printf("攔截列印\n");
sys_nslog(format);
}
//定義指標來接收原始函式的指標
static void (*sys_nslog)(NSString *format,...);
複製程式碼
列印如下:
攔截列印
點選了螢幕
複製程式碼
執行輸出,方法被攔截,說明NSLog
函式已被替換。
上面有提到,所有的函式地址在編譯後都是確定,在OC
中能夠交換方法是因為有sel
和imp
的連線過程,函式與使用者之間有一箇中間者,那麼fishhook
應該也是如此,修改了中間者的imp
指向,否則直接呼叫函式,就沒有交換的可能,在MachO
中這個中間者叫符號。
動態替換:
user -> sel -> imp
靜態替換:
user -> symbol -> imp
hook自定義函式
-(void)exchangeFun{
struct rebinding imp;
imp.name = "old_func";
imp.replacement = new_func;
imp.replaced = &sys_func;
struct rebinding rebs[1] = {imp};
rebind_symbols(rebs,1);
old_func();
}
void (*sys_func);
void old_func(){
printf("old_func\n");
}
void new_func(){
printf("new_func\n");
}
複製程式碼
列印如下:
old_func
複製程式碼
列印還是原來的方法,這裡並沒有替換。這裡可以猜想自定義函式呼叫沒有產生中間者,這裡是直接呼叫的,所以無法交換。
四、fishhook的實現原理
fishhook
主要利用了共享快取功能和PIC
技術來實現hook
功能。
動態共享快取
iOS
系統為節省記憶體資源,將系統的動態庫資源統一放在了系統的共享區域,該區域其他應用都可以訪問。
PIC技術
PIC
技術叫位置程式碼獨立,在MachO
檔案中會預留出一段空間,這一段空間叫做符號表,在MachO
檔案的資料段中。dyld
在載入MachO
檔案到記憶體中後,會將共享區的系統函式地址繫結到對應的符號上,並插入到符號表中。這樣在專案中呼叫相關係統函式時,實際上呼叫的是對應的符號,通過符號來找到具體的系統函式地址。
到這裡思路應該清晰很多,fishhook
實現如下:
- 根據符號(字元)獲取系統函式地址;
- 替換符號指向的地址為使用者宣告的函式地址(符號繫結);
- 對外部宣告的指標進行系統函式地址賦值。
上面所提到的自定義函式無法hook
也就瞭然了,自定義函式地址確定在程式碼段中,缺少dyld
的符號繫結階段,並且應用中呼叫的是最原始的函式地址,因此無法交換。
data段:程式執行期間可讀可寫 程式碼段:程式執行期間只讀
五、fishhook符號繫結分析
這裡使用 MachOView 工具,對以上給出的程式碼生成的MachO
檔案進行分析。
*獲取符號表地址
1、machO
符號表中有懶載入表(_la_symbol_ptr
)和非懶載入表(_nl_symbol_ptr
)的_data
段,在表中存放著與外部繫結的函式指標,在懶載入端有offset
地址,如下:
- 這裡都是我們熟悉的名稱,這些函式的使用是需要進行懶載入繫結的
- 提供了相對於
MachO
起始地址的偏移量offset
,實際地址即是系統函式所在的記憶體地址
這裡觀察NSLog
函式,記住offset=0x4030
這個偏移量。
2、在列印函式呼叫前下斷點,執行image list
獲取模組列表,如下:
- 這裡的
image
就是一個個模組,一個個MachO
檔案
找到第一個模組地址:0x00000001081f4000
,該地址為應用程式的起始地址,在程式執行期間是固定,重新啟動該地址則會隨機變化,即上面所提到的 ASLR 技術。
3、起始地址與偏移量相加:0x00000001081f4000 + 4030 = 0x1081F8030
(這裡使用mac
計算器的程式設計器),便是符號表的地址,記住這個值。
讀記憶體
1、讀取符號表地址
memory read 0x1081F8030
複製程式碼
取第一行,從右向左取8位資料:0x01081f6984
,改地址即是系統函式NSLog
地址,使用命令:
dis -s 0x01081f6984
複製程式碼
列印彙編,檢視繫結情況。列印如下:
由於是檢視懶載入表的偏移量,此時函式還沒有呼叫,並沒有繫結。
2、斷點到rebind_symbols(rebs,1)
處,執行再列印符號表地址:
memory read 0x1081F8030
複製程式碼
列印如下:
0x1081f8030: be e1 58 08 01 00 00 00 5d a5 57 08 01 00 00 00 ..X.....].W.....
0x1081f8040: 16 6b 29 0c 01 00 00 00 ca 69 1f 08 01 00 00 00 .k)......i......
複製程式碼
同上從右向左取8位,再來檢視彙編內容:
dis -s 0x010858e1be
複製程式碼
列印如下:
符號表有內容了,說明NSLog
為懶載入,執行後,將Foundation
中的NSLog
加入到符號表中。因此只要該函式被載入過,就可以找到繫結的符號地址。
3、繼續執行,使用fishhook
進行hook,再檢視符號表地址內容:
memory read 0x1081F8030
複製程式碼
列印如下:
由於fishhook
對系統NSLog
函式的替換,以上地址發生了變化,取地址檢視內容:
dis -s 0x01081f5c60
複製程式碼
列印如下:
此時發現符號表綁定了fishhookDemo
的my_NSLog
,說明此處的符號繫結被替換了。
通過以上實驗能夠知道,dyld
會對系統函式進行符號繫結,符號表在資料段,可讀可寫,dyld
可以繫結,fishhook
也可以通過系統函式進行繫結。
通過符號表查詢系統函式
回到MachO檔案,來看看Lazy Symbol、Dynamic Symbol Table、Symbol Table、String Table
表關係:
1、Lazy Symbol
和Dynamic Symbol Tabel
一一對應(在陣列的下標一致)這兩個表包含了所有與動態庫相關的符號;
2、Dynamic Symbol Tabel
和Symbol Table
關聯,Dynamic Symbol Table
中的Data
欄位是Symbol Table
陣列的下標;
3、Symbol Table
中的data
欄位地址 + String Table
表的起始地址,就是目標函式對應字元的位置。
以NSLog為例驗證
- 上面兩張圖的下標與
value
一一對應
找到indirect Symbols
表中的_NSLog
項:
以NSLog
函式為例,找到Data
地址0x94,等於十進位制148,即為Symbol Table
表中NSLog
對應的下標,如下:
通過下標找到了對應的符號,主要上面的標註0xBA為String Table
中的偏移量,表的起始地址加上偏移量即是函式名所在的位置。
通過首地址獲取最終函式名,0xBA + 0x6180 = 0x623A,找到0x623A地址處,如下:
最終找到了系統的函式。
小結:
OC
的執行時替換,和fishhook
對系統函式的替換,都是由於有中間者的存在,才有hook
的機會。
六、fishhook原始碼分析
……