1. 程式人生 > IOS開發 >fishhook-動態修改MachO檔案

fishhook-動態修改MachO檔案

學習hook,不是要攻擊別人,破壞別人的應用場景,而是為了更好的防護,讓自己的應用更堅固更安全。

一、動態庫注入回顧

《動態庫注入》中使用了yololib對自定義動態庫在WX應用中插入,既然能插入自定義庫,我們就利用hook技術做了小小的改動,替換了登入按鈕的方法,並攔截了微信步數上傳的方法,修改了步數。hook是改變程式執行流程的技術,能夠修改其他應用,也能對自己應用做防護。

通常替換方法有如下:

  1. 使用method_exchange交換selimp指標;
  2. 通過getImp獲取原有函式指標,通過setImp設定原sel指向的指標為自定義函式指標;
  3. 通過getImp獲取原有函式指標,通過replace
    替換原sel指向的指標為自定義函式指標,通setImp一樣。

自己專案中使用:

通常在自己專案中使用,建立分類,在分類load中來替換方法,以達到監聽方法呼叫的目的。

hook檢視的出現消失:跟蹤頁面位置; hook解構函式deallocdeinit):跟蹤物件釋放狀況; hook訊息轉發方法(forword):避免呼叫未實現方法而產生奔潰; hook陣列objectAtIndexedSubscript方法:避免陣列越界奔潰; ……

只要是OC的方法都可以根據業務需求進行監聽修改或替換。

在其他應用中使用:

通過動態庫注入的方式,來插入自己的load,讓自己的程式碼能和其他專案一起執行,同樣使用我們常規的替換方法即可。通過檢視檢視頁面屬性確定要hook

的目標物件。

hook微信步數獲取方法:實現微信步數修改; hook微信搶紅包相關方法:實現自動搶紅包功能; hook微信訊息撤回方法:實現拒絕訊息撤回功能; (ps:學習中,實戰不多,有方法就有更多的可能)

在其他應用中是通過動態庫來進行hook的,因此在目標物件所在類的方法列表中並沒有要替換的方法,迴歸原有方法呼叫會出現崩潰,這裡可以使用獲取原有方法的函式地址,直接呼叫函式即可實現原有方法的呼叫。

以上是針對OC的動態特性來hook的,我們能夠hook動態語言下的函式,那麼能不能直接hook系統的靜態函式呢,下面來看一下fishhook是如何hook的。

二、fishhook概述

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中能夠交換方法是因為有selimp的連線過程,函式與使用者之間有一箇中間者,那麼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檔案到記憶體中後,會將共享區的系統函式地址繫結到對應的符號上,並插入到符號表中。這樣在專案中呼叫相關係統函式時,實際上呼叫的是對應的符號,通過符號來找到具體的系統函式地址。

symbol.png

到這裡思路應該清晰很多,fishhook實現如下:

  1. 根據符號(字元)獲取系統函式地址;
  2. 替換符號指向的地址為使用者宣告的函式地址(符號繫結);
  3. 對外部宣告的指標進行系統函式地址賦值。

上面所提到的自定義函式無法hook也就瞭然了,自定義函式地址確定在程式碼段中,缺少dyld的符號繫結階段,並且應用中呼叫的是最原始的函式地址,因此無法交換。

data段:程式執行期間可讀可寫 程式碼段:程式執行期間只讀

五、fishhook符號繫結分析

這裡使用 MachOView 工具,對以上給出的程式碼生成的MachO檔案進行分析。

*獲取符號表地址

1、machO符號表中有懶載入表(_la_symbol_ptr)和非懶載入表(_nl_symbol_ptr)的_data段,在表中存放著與外部繫結的函式指標,在懶載入端有offset地址,如下:

lazy_symbol.png

  • 這裡都是我們熟悉的名稱,這些函式的使用是需要進行懶載入繫結的
  • 提供了相對於MachO起始地址的偏移量offset,實際地址即是系統函式所在的記憶體地址

這裡觀察NSLog函式,記住offset=0x4030這個偏移量。

2、在列印函式呼叫前下斷點,執行image list獲取模組列表,如下:

image.png

  • 這裡的image就是一個個模組,一個個MachO檔案

找到第一個模組地址:0x00000001081f4000,該地址為應用程式的起始地址,在程式執行期間是固定,重新啟動該地址則會隨機變化,即上面所提到的 ASLR 技術。

3、起始地址與偏移量相加:0x00000001081f4000 + 4030 = 0x1081F8030(這裡使用mac計算器的程式設計器),便是符號表的地址,記住這個值。

讀記憶體

1、讀取符號表地址

memory read 0x1081F8030
複製程式碼

offset.png

取第一行,從右向左取8位資料:0x01081f6984,改地址即是系統函式NSLog地址,使用命令:

dis -s 0x01081f6984
複製程式碼

列印彙編,檢視繫結情況。列印如下:

dis.png

由於是檢視懶載入表的偏移量,此時函式還沒有呼叫,並沒有繫結。

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
複製程式碼

列印如下:

func.png

符號表有內容了,說明NSLog為懶載入,執行後,將Foundation中的NSLog加入到符號表中。因此只要該函式被載入過,就可以找到繫結的符號地址。

3、繼續執行,使用fishhook進行hook,再檢視符號表地址內容:

memory read 0x1081F8030
複製程式碼

列印如下:

hook_func.png

由於fishhook對系統NSLog函式的替換,以上地址發生了變化,取地址檢視內容:

dis -s 0x01081f5c60
複製程式碼

列印如下:

replace.png

此時發現符號表綁定了fishhookDemomy_NSLog,說明此處的符號繫結被替換了。

通過以上實驗能夠知道,dyld會對系統函式進行符號繫結,符號表在資料段,可讀可寫,dyld可以繫結,fishhook也可以通過系統函式進行繫結。

通過符號表查詢系統函式

回到MachO檔案,來看看Lazy Symbol、Dynamic Symbol Table、Symbol Table、String Table表關係:

1、Lazy SymbolDynamic Symbol Tabel一一對應(在陣列的下標一致)這兩個表包含了所有與動態庫相關的符號;

2、Dynamic Symbol TabelSymbol Table關聯,Dynamic Symbol Table中的Data欄位是Symbol Table陣列的下標;

3、Symbol Table中的data欄位地址 + String Table表的起始地址,就是目標函式對應字元的位置。

以NSLog為例驗證

lazy.png

indirect.png

  • 上面兩張圖的下標與value一一對應

找到indirect Symbols表中的_NSLog項:

indirect_data.png

NSLog函式為例,找到Data地址0x94,等於十進位制148,即為Symbol Table表中NSLog對應的下標,如下:

symbol.png

通過下標找到了對應的符號,主要上面的標註0xBA為String Table中的偏移量,表的起始地址加上偏移量即是函式名所在的位置。

string_table.png

通過首地址獲取最終函式名,0xBA + 0x6180 = 0x623A,找到0x623A地址處,如下:

string.png

最終找到了系統的函式。

小結:

OC的執行時替換,和fishhook對系統函式的替換,都是由於有中間者的存在,才有hook的機會。

六、fishhook原始碼分析

……