今天我們將繼續講解逆向開發工程另一個重要內容--Hook原理講解。Hook,可以中文譯為“掛鈎”或者“鈎子”,逆向開發中改變程序運行的一種技術。按照如下過程進行講解
- Hook概述
- Hook技術方式
- fishhook原理及實例
- 符號表查看函數名稱
- 總結
一、Hook概述
在逆向開發中是指改變程序運行流程的技術,通過Hook可以讓自己的代碼運行在別人的程序中。需要了解其Hook原理,這樣就能夠對惡意代碼攻擊進行有效的防護。
二、Hook技術方式
2.1 Method Swizzle方式
Method Swizzle 上次已經講到,是利用OC的Runtime的特性,去動態改變SEL(方法編號)與IMP(方法實現)的對應關系,達到OC方法調用流程更改的目的。也是主要用於OC方法。
2.2 Cydia Substrate方式
Cydia Substrate 原名叫做Mobile SubStrate,主要作用為針對C函數,OC函數以及函數的地址進行Hook操作。並且有個很大的優勢,Cydia Substrate 並不是僅僅是針對iOS設計,Andriod一樣也可以使用。
2.2.1
Cydia Substrate定義了一系列的函數和宏,底層調用了objc的runtime和fishHook來替代目標函數或者系統方法。
其中有兩個函數
- MSHookMessageEx主要用於OC方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
- MSHookFunction主要用於C++和C函數
void MSHookFunction(voidfunction,void* replacement,void** p_original)
2.2.2 MobileLoader
MobileLoader主要用於加載第三方dylib運行的應用程序中。啟動時MobileLoader會根據指定的第三方動態庫加載進去,第三方動態庫也是我們寫的破解程序。
2.2.3 safe mode
破解程序的本質在於dylib,寄生於別人程序進程中。但是系統進程一旦出現錯誤,可能會導致整個進程崩潰,也可能會導致iOS程序崩潰。在Cydia Substrate 中引入了安全模式,如果一旦錯誤,三方的dylib會被禁用,便於查錯和修復。
2.3 fishHook
fishHook是Facebook提供一種動態修改鏈接Mach-O文件的工具。此利用Mach-O文件加載原理,通過修改非懶加載和懶加載兩個表的指針達到C函數的Hook的目的。
今天我們主要講解第三種方式fishHook達到更改程序的目的。
三、fishhook原理及實例
3.1 概述
fishhook的源碼地址為https://github.com/facebook/fishhook
fishhook的主要方法有兩個還有一個結構體
查看代碼結構為,將紅色圈起來部分移入到代碼中,即可使用fishhook來hook代碼。
3.2 實例
3.2.1 Demo1實例1
// rebinding 結構體的定義 // struct rebinding { // const char *name; // 需要 HOOK 的函數名稱,字符串 // void *replacement; // 替換的新函數(函數指針,也就是函數名稱) // void **replaced; // 保存原始函數指針變量/地址的指針(它是一個二級指針!) // }; // C 語言傳參是值/址傳遞的,把它的值/址穿過去,就可以在函數內部修改函數指針變量的值 - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"123"); //rebinding結構體 struct rebinding nslog; nslog.name = "NSLog";// 函數名稱 nslog.replacement = myNslog; // 新的函數指針 nslog.replaced = (void *)&sys_nslog;// 保存原始函數地址的變量的指針 //rebinding結構體數組 struct rebinding rebs[1] = {nslog}; /** * 存放rebinding結構體的數組 * 數組的長度 */ rebind_symbols(rebs, 1); } //---------------------------------更改NSLog----------- //函數指針,用來保存原始的函數地址 (C 語言語法,函數指針類型變量) static void(*sys_nslog)(NSString * format,...); //定義一個新的函數 void myNslog(NSString * format,...){ format = [format stringByAppendingString:@"勾上了!\n"]; //調用原始的 sys_nslog(format); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"點擊了屏幕!!"); }
上面的代碼運行結果如下:
3.2.2 Demo2實例2
void func(const char * str){ NSLog(@"%s",str); } - (void)viewDidLoad { [super viewDidLoad]; //rebinding結構體 struct rebinding nslog; nslog.name = "func"; nslog.replacement = new_func; nslog.replaced = (void *)&old_func; //rebinding結構體數組 struct rebinding rebs[1] = {nslog}; /** * 存放rebinding結構體的數組 * 數組的長度 */ rebind_symbols(rebs, 1); } //---------------------------------更改NSLog----------- //函數指針 static void(*old_func)(const char * str); //定義一個新的函數 void new_func(const char * str){ NSLog(@"%s + 1",str); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { func("哈哈"); }
運行結果如下:
從上面可以看出自定義的交換方法為什么交換不了呢?首先可以肯定的是代碼是OK的,下面我們講解原理,為什么自定義的方法不行呢?
3.3 原理探究
Mach-O文件是如何加載的?
Dyld工具動態加載,加載MachO文件完成后,開始加載依賴的動態庫,也就是通過上篇博客的image List 可看到相關的類庫。
PIC(Promrammable Interrupt Controller)位置代碼獨立,由外設發出中斷請求需要中斷控制器來處理。
Mach-O文件內部調用系統函數時:
- Mach-O _data段建立了一個指針(也就是符號,實現指向內部的函數調用,指向了外部的函數地址),指向了外部函數(dyld),可讀可寫,當Mach-O被加載進去,就會指向所指的函數。
- Dyld會動態的綁定,將Mach-O中的data 段中指針指向了外部的函數,也是Dyld為什么叫做動態綁定的原因。
這也回答了上面的問題,為什么內部/自定義的函數不能修改,只能修改Mach-O文件的外部函數,如果是另外一個動態庫或者需要動態符號綁定的就可以(符號表中能找到才可以實現)
下面我們是真實查看內容,通過實例
利用第一個Demo來測試,運行起來,然后查看可執行文件,通過MachoView工具
從圖2看出offset偏移地址為3028,也就是NSLog函數文件的偏移地址,懶加載此表時在Mach-O文件偏移地址+函數偏移的地址。
下面以Demo1查看,在Demo1打斷點,查看Mach-O函數偏移地址,通過指令image list 第一個就是Mach-O內容和地址(本人上篇博客地址即可)
Mach-O在內存的偏移地址也就是Mach-O的真實地址,發現為 0x000000010a9c5000
通過上面紅色加重算法,計算Mach-O文件Data段的函數指針
發現執行完只有就會被綁定。NSLog函數文件就會被綁定。
下面再看一下,對於屏幕點擊的,hook如下
前提是我們去除ViewDidLoad方法里面的NSLog(@“123”)這句代碼,運行代碼,最后將斷點斷在touchesBegan里面,此時開始看地址和內容
截圖的前兩次打印是程序運行時,但是未曾點擊touchesBegan,后兩次是點擊屏幕時斷點進入到了里面,再看內容,打印的對象是NSLog還是myNslog,通過上面發現是myNslog,說明Hook成功。
通過上面可看出,fishhook能夠Hook c函數,是因為Mach-O文件特點,PIC位置代碼獨立造就了靜態語言C也有動態的部分,之后通過Dyld進行動態綁定的時機,在這其中我們就可以做手腳,替換自定義的方法。
fishhook是根據方法字符串的名字“NSLog”,它是怎么找到的呢?下面將講解利用符號表查看函數名稱字符串。
四、符號表查看函數名稱
再次查看Mach-O文件,查看懶加載表中的NSLog函數
懶加載表是和動態符號表是一一對應關系,通過上面發現NSLog函數時第一個,而對應的Dynamic Symbol table也是第一個,打開Dynamic Symbol table
查看Dynamic Symbol Table 第一個也是NSLog,查看Data值為7A,對應的十進制為122,然后到Symbols Table里面查看122,如下:
查看Symbols Table的data值為0000009B,然后在String Table Index去看函數偏移值為0000009B的內容,如下:
為什么選擇00004F94查看NSLog呢,我們從上面得知Symbols Table的data值為0000009B,然后加上String Table的函數第一個地址為00004F04,然后將0000009B + 00004F04 = 0X4F9F,最后看00004F94里面包含了0X4F9F,藍色內容看出是NSLog內容,也就是找到啦。完美!!!
以上過程可以在fishhook中github上有說明圖:
上面的說明圖也就是通過符號表查看函數名稱以及反過來也可以逆查的過程。配上說明圖,方便大家熟悉流程。
五、總結
上面講述了Hook的幾種技術方式以及fishhook的原理探究,以及如何讓別人的app實現自己的代碼。下面我們對此總結一下,寫了一個本篇博客的整個過程便於大家整理,希望對大家有所幫助加深理解。