FishHook
fishHook是Facebook提供的一個動態修改鏈接mach-O文件的工具。利用MachO文件加載原理,通過修改懶加載表(Lazy Symbol Pointers)和非懶加載表(Non-Lazy Symbol Pointers)這兩個表的指針達到C函數HOOK的目的。
在逆向中經常使用fishHook這個工具。所以在學習過程中,我們重點要了解其原理,這樣能夠對惡意代碼進行有效的防護。
FishHook的簡單使用
-
Xcode新建項目工程,將fishHook拖入工程
-
寫代碼Hook系統函數NSLog
具體可以下載FishhooKDemo,具體代碼具體代碼如下:
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//使用rebinding 結構體
struct rebinding nslog;
nslog.name = "NSLog";//要hook的函數名稱
nslog.replacement = myNSLog;//這裏是函數的指針,也就是函數名稱
nslog.replaced = &sys_nslog;
// rebinding 結構體數組
struct rebinding rebs[1] = {nslog};
/***
存放rebinding 結構體數組
數組長度
*/
rebind_symbols(rebs, 1);
// Do any additional setup after loading the view.
}
//---------修改的NSLog---------
//NSLog函數指針
static void(*sys_nslog)(NSString * format,...);
//定義一個新函數
void myNSLog(NSString *format,...){
format = [format stringByAppendingString:@"~~~~~hook 到了!"];
sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"點擊了屏幕");
}
當點擊屏幕後可以看到輸出結果:
可以看到HOOK到了系統函數NSLog了;這裏注意fishHOOK 不能hook自己寫的C語言函數;
fishHook原理
共享緩存庫
在iOS or Mac系統中,幾乎所有的程序都會用到動態庫,而動態庫在加載的時候都需要用dyld(位於/usr/lib/dyld)程序進行鏈接。很多系統庫幾乎都是每個程序都要用到的,如果在每個程序運行的時候在一個一個將這些動態庫都加載進來,不僅耗費內存,而且耗時。爲了降低內存,提高性能,蘋果引入了共享緩存庫,用來存儲系統的庫。
Mac下的共享緩存庫位置:/private/var/db/dyld/
iOS下的共享緩存庫位置:/System/Library/Caches/com.apple.dyld/”
文件名都是以“dyld_shared_cache_”開頭,再加上這個dyld緩存文件所支持的指令集。
PIC技術(位置獨立代碼)
我們都知道C語言是靜態的,也就是說,在編譯的時候就已經確定了函數的地址。而系統的函數由於共享緩存庫的存在,必須是dyld加載的時候(運行時)才能確定,這明顯存在矛盾。爲了解決這個問題,蘋果針對Mach-O文件提供了一種PIC技術,即在MatchO的_Data段中添加懶加載表(Lazy Symbol Pointers)和非懶加載表(Non-Lazy Symbol Pointers)這兩個表,讓系統的函數在編譯的時候先指向懶加載表(Lazy Symbol Pointers)或非懶加載表(Non-Lazy Symbol Pointers)中的符號地址,這兩個表中的符號的地址的指向在編譯的時候並沒有指向任何地方,app啓動,被dyld加載到內存,就進行鏈接, 給這2個表賦值動態緩存庫的地址進行符號綁定。
在fishHOOK 之前加上一個打印;並打上斷點
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"viewDidLoad");
//使用rebinding 結構體
struct rebinding nslog;
nslog.name = "NSLog";//要hook的函數名稱
nslog.replacement = myNSLog;//這裏是函數的指針,也就是函數名稱
nslog.replaced = &sys_nslog;
// rebinding 結構體數組
struct rebinding rebs[1] = {nslog};
/***
存放rebinding 結構體數組
數組長度
*/
rebind_symbols(rebs, 1);
// Do any additional setup after loading the view.
}
//---------修改的NSLog---------
//NSLog函數指針
static void(*sys_nslog)(NSString * format,...);
//定義一個新函數
void myNSLog(NSString *format,...){
format = [format stringByAppendingString:@"~~~~~hook 到了!"];
sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"點擊了屏幕");
}
@end
首先使用image list指令,查看MatchO的首地址,也就是ASLR地址,偏移地址;
何爲ASLR技術?
地址空間佈局隨機化。它會讓 Mach-O 文件加載的時候是隨機地址。有了這個技術,Mach-O 文件每次加載進內存的時候地址都是不一樣的。主要是爲了防止逆向技術。
Mach-O 文件裏只有我們自己寫的函數,系統的動態庫的函數是不在 Mach-O 文件裏的。也就是說每次啓動從 Mach-O 文件到系統動態庫函數的偏移地址都是變化的。
使用MatchOView查看MatchO懶加載表中的NSLog符號在文件中的偏移值
查看符號表綁定的地址,這個地址其實就是指向外部函數的指針的地址,也就是動態緩存區裏面 NSLog 的真實函數地址。這一步是找到了 NSLog 的符號表(Symbols)。因爲iOS爲小端模式這裏地址應該是00007fff25762dfa
這個真實的函數地址是什麼時候保存進去的呢?並不是 Mach-O 文件加載進內存的時候保存的。由於 NSLog 在懶加載符號表裏面,所有它是在整個 Mach-O 文件啓動之後,代碼第一次運行 NSLog 時,由 DYLD 綁定該 NSLog 符號指向真實的 NSLog 的地址。
這個時候,我們需要通過反彙編看一下地址的值:dis -s 0x00007fff25762dfa
以上是HOOK之前的地址和綁定函數;我們通過下斷點;在點擊屏幕處下斷點來進行查看下
首先查看下地址中存儲
可以看到存儲的和之前的不一樣了:0x000000010ba965d0 反編譯下看看
總結: fishHook其實就是修改懶加載表(Lazy Symbol Pointers)、非懶加載表(Non-Lazy Symbol Pointers)中的符號地址的指向,從而達到hook的目的
fishhook 是如何通過字符串來找到我們的函數的呢?
首先,我們從懶加載符號表(Lazy Symbol Pointers)開始入手。懶加載符號表裏面第一個符號是 NSLog 的指針。這個懶加載符號表有一個與之一一對應的符號表(Indirect Symbols)。
這裏的Data 值,是一個真正的符號表的下標。這個符號表是對應着字條串的。比如:NSLog 的 Data 值爲0xA1,換成十進制就是161。也就是說 NSLog 這個符號在我們的字符符號表裏面的 index 值爲161。接着就需要到符號表(Symbols)裏面找第161個。這個時候還沒到字符串。
這個時候,NSLog 在真正的字符串裏面是在哪個地方呢?注意,上圖有一個偏移0xD7,就是在字符串表(String Table)裏面的一個index。也就是說這個 NSLog 在 String Table 裏面的偏移地址是0xD7。
String Table 是從0x82D8開始的,所以開始地址0x82D8 + 偏移地址0xD7 = 0x83AF,就是字符串 NSLog 的位置。
_ 是函數的開始,. 是分隔符 。5F是從 _開始,往後依次 _NSLog ;
接下來,附上 fishhook 官方文檔的在懶加載和非懶加載符號表裏查找一個給定入口的名字的過程。