目錄
- 一、符號表之間的關(guān)聯(lián)
- 二、去符號
- 三、恢復(fù)符號
- 四、使用fishhook HOOK系統(tǒng)MethodSwizzle方法,以檢測APP是否被HOOK
- 五、使用MonkeyDev進行重新簽名和代碼注入
- 六、總結(jié)
一、符號表之間的關(guān)聯(lián)
懶加載符號表、間接符號表、符號表、字符串表之間的關(guān)聯(lián)




懶加載符號表和間接符號表index一一對應(yīng)
接符號表保存了在符號表中的index
符號表中保存了在字符串表中的index
上面的流程和fishhook查找符號的流程圖一致:The process of looking up the name of a given entry in the lazy or non-lazy pointer tables looks like this:
二、去符號
一般情況下我們的APP會去掉所有符號(不包括間接符號)、動態(tài)庫會保留全局符號供外部調(diào)用。
Xcode 默認是去掉所有符號的,但是Deployment Postprocessing 需要設(shè)置為Yes,否者只有在打包時才會去掉所有符號。

符號表中只有間接符號了,本地符號和全局符號都去掉了

添加如下代碼:

運行APP后斷點是不起作用的,因為去掉了所有符號。添加NSLog符號斷點可以斷住,但是去掉符號后函數(shù)名會變?yōu)?code>___lldb_unnamed_symbol2

如果調(diào)用的是OC方法可以添加符號斷點并通過參數(shù)寄存器查看方法名和參數(shù)
還可以通過地址斷點(ASLR+地址偏移值)、恢復(fù)符號表等方法查看方法名
三、恢復(fù)符號
- (void)differtest {
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"第一次外部函數(shù)的調(diào)用!");
NSLog(@"第二次外部函數(shù)的調(diào)用!");
}
- (void)test1 {
NSLog(@"第一次外部函數(shù)的調(diào)用!");
}
- (void)test {
[self test1];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self test];
}

雖然去掉所有符號后符號表中沒有符號了,但是類的方法列表中還是存在。因此通過方法列表,類名列表等信息可以恢復(fù)出相關(guān)的符號
通過restore-symbol工具恢復(fù)符號(C函數(shù)、靜態(tài)函數(shù)不能恢復(fù),只能恢復(fù)Runtime相關(guān)的)

執(zhí)行代碼./restore-symbol -o symbolDemo2 symbolDemo后即可恢復(fù)符號,恢復(fù)符號的MachO還需要重新簽名打包
四、使用fishhook HOOK系統(tǒng)MethodSwizzle方法,以檢測APP是否被HOOK
目標:APP中可以MethodSwizzle方法,破解APP時直接調(diào)用MethodSwizzle方法會失敗。
4.1 新建項目AntiHook模擬我們自己的APP、添加兩個按鈕并關(guān)聯(lián)方法
代碼如下:
- (IBAction)btnClick1:(id)sender {
NSLog(@"按鈕1調(diào)用!");
}
- (IBAction)btnClick2:(id)sender {
NSLog(@"按鈕2調(diào)用了!");
}
防護代碼需要寫在Framework中,因為Framework加載比主工程中的代碼加載更快,別人注入的HOOK代碼在Framework加載之后、在主工程之前。
添加主Framework

// AntiHookCode.m
#import "fishhook.h"
@implementation AntiHookCode
+ (void)load {
//exchange方法防護
struct rebinding exchange;
exchange.name = "method_exchangeImplementations";
exchange.replacement = my_exchange;
exchange.replaced = (void *)&exchangeP;
//set、get方法防護
//setIMP
struct rebinding setIMP;
setIMP.name = "method_setImplementation";
setIMP.replacement = my_exchange;
setIMP.replaced = (void *)&setIMP_p;
//getIMP
struct rebinding getIMP;
getIMP.name = "method_getImplementation";
getIMP.replacement = my_exchange;
getIMP.replaced = (void *)&getIMP_p;
struct rebinding bds[] = {exchange,setIMP,getIMP};
rebind_symbols(bds, 3);
}
//指針!這個可以暴露給外接!我自己的工程使用!!
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
IMP _Nonnull (*setIMP_p)(Method _Nonnull m, IMP _Nonnull imp);
IMP _Nonnull (*getIMP_p)(Method _Nonnull m);
void my_exchange(Method _Nonnull m1, Method _Nonnull m2){
NSLog(@"檢測到了HOOK!");
}
@end
HookMgr.h中暴露AntiHookCode.h頭文件

HookMgr.h設(shè)置為Public供外界使用

ViewController.m:自己項目交換按鈕2的實現(xiàn)
#import <HookMgr/HookMgr.h>
- (void)viewDidLoad {
[super viewDidLoad];
//HOOK
exchangeP(class_getInstanceMethod(self.class, @selector(btnClick2:)), class_getInstanceMethod(self.class, @selector(test)));
}
- (void)test {
NSLog(@"本工程的HOOK!很順利!!!");
}
4.2 新建項目HookDemo用來HOOK我們的APP
將編譯好的AntiHook拷貝到HookDemo項目中的APP/Payload目錄中(真機編譯)

終端進入APP目錄
zip -ry WeChat.ipa Payload (ipa的名字這里沿用之前的)
刪除Payload目錄
HookDemo項目編譯運行到手機中
添加注入代碼:模擬別人HOOK按鈕1

添加腳本執(zhí)行

運行項目將APP安裝到手機、控制臺會直接輸出:檢測到了HOOK!
點擊按鈕1也不會執(zhí)行test方法。這樣就達到了HOOK APP方法失敗的目的
點擊按鈕2會顯示本工程的HOOK!很順利!!!

五、使用MonkeyDev進行重新簽名和代碼注入
安裝好后重新打開Xcode就有如下功能(安裝MonkeyDev后Xcode如果打開崩潰,刪除Xcode重裝一次就可以了)

5.1 重簽名
- 選擇MonkeyApp模板創(chuàng)建項目MonkeyDemo并
運行安裝APP - 將需要重簽名的ipa or app 拖到TargetApp目錄中
- 運行項目MonkeyDemo到手機即可進行重簽名

5.2 代碼注入:OC方法HOOK
修改MonkeyDemoDylib.xm文件的type

HOOK APP中ViewController的BtnClick1方法

Monkey實際上使用的是MethodSwizzle 中的method_getImplementation/method_setImplementation方法
六、總結(jié)
- HOOK:鉤子,改變程序的執(zhí)行流程的一種技術(shù)
- MethodSwizzle
- 利用OC的運行時(Runtime) 特性修改SEL和IMP (函數(shù)指針) 對應(yīng)關(guān)系。達到HOOK OC方法的目的
-
method_exchangelMP...交換兩個IMP -
class_replaceMethod替換某個SEL的IMP (如果沒有該方法,就添加。相當于替換掉這個方法) -
method_getlmplementation、method_setlmplementation獲取和設(shè)置某個方法的IMP (很多三方框架都使用)
- fishhook
- Facebook提供的一個工具,利用MachO文件的加載原理,動態(tài)修改懶加載和非懶加載兩個符號表!
- Cydia Substrate
- 一個強大的框架。
- fishhook
- 可以HOOK
系統(tǒng)的函數(shù),但是無法HOOK自定義的函數(shù) - 原理:
- 共享緩存
- iOS系統(tǒng)有一塊特殊的位置,存放公用動態(tài)庫。動態(tài)庫共享緩存(dyld shared cache)
- PIC技術(shù)
- 由于外部的函數(shù)調(diào)用, 在我們編譯時刻是沒法確定其內(nèi)存地址的。
- 蘋果就采用了
PIC技術(shù)(位置無關(guān)代碼)。在MachO文件DATA段,建立兩張表,懶加載和非懶加載表,里面存放執(zhí)行外部函數(shù)的指針! - 首次調(diào)用懶加載函數(shù), 會去找
樁執(zhí)行代碼。首次執(zhí)行會執(zhí)行binder函數(shù)!
- 共享緩存
- 通過字符找到懶加載表
- fshhook利用
stringTable-->Symbols-->indirect Symbols-->懶加載符號表之間的對應(yīng)關(guān)系。通過重綁定修改指針的值達到HOOK的目的。
- fshhook利用
- 反H0OK基本防護
- 利用fishhook修改Methodswizzle 相關(guān)函數(shù)來檢測是否被HOOK
- 防護代碼要最先被加載,否則先HOOK就修改完畢了,防護無效。
- 原始工程編寫的Framework庫會優(yōu)先于注入庫加載。所以適合寫防護代碼。