HOOK概述
HOOK
,中文譯為“掛鉤”或“鉤子”涌穆。在iOS
逆向中是指改變程序運行流程的一種技術(shù)。通過HOOK
可以讓別人的程序執(zhí)行自己所寫的代碼雀久。在逆向中經(jīng)常使用這種技術(shù)宿稀。所以在學(xué)習(xí)過程中,我們重點要了解其原理赖捌,這樣能夠?qū)阂獯a進行有效的防護
HOOK
示意圖
iOS
中HOOK
技術(shù)的幾種方式:
Method Swizzle
:主要用于OC
方法祝沸,利用OC
的Runtime
特性,動態(tài)改變SEL
(方法編號)和IMP
(方法實現(xiàn))的對應(yīng)關(guān)系越庇,達到OC
方法調(diào)用流程改變的目的fishhook
:MachO
文件的工具罩锐。利用MachO
文件加載原理,通過修改懶加載和非懶加載兩個表的指針卤唉,達到對C函數(shù)
進行HOOK
的目的Cydia Substrate
:原名為Mobile Substrate
涩惑,它的主要作用是針對OC
方法、C函數(shù)
以及函數(shù)地址進行HOOK
操作桑驱。當(dāng)然它并不是僅僅針對iOS
而設(shè)計的竭恬,安卓一樣可以使用。官方地址
Method Swizzle
利用OC
的Runtime
特性熬的,動態(tài)改變SEL
(方法編號)和IMP
(方法實現(xiàn))的對應(yīng)關(guān)系痊硕,達到OC
方法調(diào)用流程改變的目的。主要用于OC
方法押框。
在
OC
中岔绸,SEL
和IMP
之間的關(guān)系,就好像一本書的“目錄
”
SEL
是方法編號橡伞,就像“標(biāo)題
”一樣IMP
是方法實現(xiàn)的真實地址盒揉,就像“頁碼
”一樣- 它們是一一對應(yīng)的關(guān)系
Runtime
提供了交換兩個SEL
和IMP
對應(yīng)關(guān)系的函數(shù)OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
通過這個函數(shù)交換兩個
SEL
和IMP
對應(yīng)關(guān)系的技術(shù),我們稱之為Method Swizzle
(方法欺騙)
多種
HOOK
方式
class_addMethod
方式:讓原始方法可以被調(diào)用骑歹,不至于因為找不到SEL
而崩潰class_replaceMethod
方式:直接給原始的方法替換IMP
method_setImplementation
方式:直接重新賦值新的IMP
Cydia Substrate
MobileHooker
顧名思義用于
HOOK
预烙。它定義一系列的宏和函數(shù)墨微,底層調(diào)用objc
的runtime
和fishhook
來替換系統(tǒng)或者目標(biāo)應(yīng)用的函數(shù)其中有兩個函數(shù):
MSHookMessageEx
:主要作用于Objective-C
方法void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
MSHookFunction
:主要作用于C
和C++
函數(shù)道媚,Logos
語法的%hook
就是對以下函數(shù)做了一層封裝void MSHookFunction(void function, void* replacement, void** p_original)
MobileLoader
MobileLoader
用于加載第三方dylib
在運行的應(yīng)用程序中。啟動時MobileLoader
會根據(jù)規(guī)則把指定目錄的第三方的動態(tài)庫加載進去翘县,第三方的動態(tài)庫也就是我們寫的破解程序
safe mode
破解程序本質(zhì)是
dylib
最域,寄生在別人進程里。 系統(tǒng)進程一旦出錯锈麸,可能導(dǎo)致整個進程崩潰镀脂,崩潰后就會造成iOS
癱瘓。所以Cydia Substrate
引入了安全模式忘伞,在安全模式下所有基于Cydia Substratede
的三方dylib
都會被禁用薄翅,便于查錯與修復(fù)
fishHook
獲取代碼
git clone https://github.com/facebook/fishhook.git
關(guān)鍵函數(shù)
FISHHOOK_VISIBILITY int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
rebindings
:存放rebinding
結(jié)構(gòu)體的數(shù)組沙兰,可以同時交換多個函數(shù)rebindings_nel
:數(shù)組的長度
rebinding
結(jié)構(gòu)體struct rebinding { const char *name; void *replacement; void **replaced; };
name
:需要HOOK
的函數(shù)名稱,C
字符串replacement
:新函數(shù)的地址replaced
:原始函數(shù)地址的指針
案例1:
HOOK
系統(tǒng)NSLog
函數(shù)打開
ViewController.m
文件翘魄,定義新函數(shù)void my_NSLog(NSString *format, ...) { format = [format stringByAppendingString:@"~~~??????????"]; sys_NSLog(format); }
定義函數(shù)指針鼎天,用于保存
NSLog
系統(tǒng)函數(shù)地址static void (*sys_NSLog)(NSString *format, ...);
viewDidLoad
方法中,寫入以下代碼:- (void)viewDidLoad { [super viewDidLoad]; struct rebinding reb; reb.name="NSLog"; reb.replacement=my_NSLog; reb.replaced=(void *)&sys_NSLog; struct rebinding rebs[] = { reb }; rebind_symbols(rebs, 1); }
添加
touchesBegan
方法-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"hahaha"); }
真機運行項目暑竟,點擊屏幕斋射,輸出以下內(nèi)容:
fishhookDemo[2072:399007] hahaha~~~??????????
fishHook
可以HOOK
我們的C函數(shù)
,但是我們知道函數(shù)是靜態(tài)的但荤,也就是說在編譯的時候罗岖,編譯器就知道了它的實現(xiàn)地址,這也是為什么C函數(shù)
只寫函數(shù)聲明調(diào)用時會報錯腹躁。那么為什么fishHook
還能夠改變C函數(shù)
的調(diào)用呢桑包?難道函數(shù)也有動態(tài)的特性存在?
案例2:
HOOK
自定義函數(shù)打開
ViewController.m
文件纺非,定義自定義函數(shù)void test(NSString *format, ...){ NSLog(@"hahaha"); }
定義新函數(shù)
void my_test(NSString *format, ...){ format = [format stringByAppendingString:@"~~~??????????"]; sys_test(format); }
定義函數(shù)指針捡多,用于保存
test
自定義函數(shù)地址static void (*sys_test)(NSString *format, ...);
viewDidLoad
方法中,寫入以下代碼:- (void)viewDidLoad { [super viewDidLoad]; struct rebinding reb; reb.name="test"; reb.replacement=my_test; reb.replaced=(void *)&sys_test; struct rebinding rebs[] = { reb }; rebind_symbols(rebs, 1); }
添加
touchesBegan
方法-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { test(@"hahaha"); }
真機運行項目铐炫,點擊屏幕垒手,輸出以下內(nèi)容:
fishhookDemo[2117:410009] hahaha
在
案例2
中,HOOK
自定義test
函數(shù)并沒有效果倒信。NSLog
是系統(tǒng)函數(shù)科贬,而test
是當(dāng)前MachO
中的函數(shù),它們之間有什么區(qū)別鳖悠?
fishHook原理探究
一般來說榜掌,靜態(tài)語言通過地址直接訪問,例如
案例2
中的test
函數(shù)乘综。但對于NSLog
系統(tǒng)函數(shù)來說憎账,它屬于外部調(diào)用函數(shù),編譯時并不能找到它的真實地址
NSLog
存儲在Fundation
框架中卡辰,App
運行在不同系統(tǒng)版本的不同設(shè)備上胞皱,每個設(shè)備中NSLog
的函數(shù)地址都各不相同再有,當(dāng)
App
啟動時九妈,會涉及ASLR
(地址空間配置隨機加載)反砌,導(dǎo)致程序運行它的虛擬內(nèi)存地址都不一樣這些原因,都會造成外部函數(shù)的真實地址萌朱,在編譯時期是無法確定的
那外部函數(shù)是如何被調(diào)用的宴树?
當(dāng)
App
啟動時,dyld
讀取主程序MachO
文件晶疼,會加載共享緩存中的系統(tǒng)庫酒贬,將用到的函數(shù)真實地址告訴MachO
對于
MachO
中的代碼段來說又憨,它是只讀的。在運行時锭吨,無法直接修改外部函數(shù)的真實地址對于上述情況竟块,蘋果采用
PIC
技術(shù)(位置獨立代碼),在MachO
調(diào)用外部函數(shù)時耐齐,在可讀可寫的數(shù)據(jù)段浪秘,定義符號,占8字節(jié)
埠况,用來存放外部函數(shù)的地址耸携。編譯時,暫存占位地址辕翰。運行時夺衍,dyld
將符號綁定真實函數(shù)地址。對于代碼段來說喜命,并沒有任何改變
故此沟沙,外部調(diào)用函數(shù),并不是直接地址訪問壁榕,而是通過符號找到地址矛紫。這跟
OC
中SEL
與IMP
的對應(yīng)關(guān)系非常相似。這種機制牌里,可以讓開發(fā)者動態(tài)HOOK
外部調(diào)用函數(shù)在
OC
中動態(tài)改變SEL
與IMP
的對應(yīng)關(guān)系颊咬,對于外部調(diào)用函數(shù),動態(tài)改變的是符號和地址的對應(yīng)關(guān)系牡辽,上述操作稱為:符號表重綁定
案例1
查看
NSLog
加載前的占位地址查看
MachO
文件
NSLog
函數(shù)喳篇,存儲在懶加載符號表中。dyld
加載MachO
時态辛,綁定非懶加載符號和弱引用符號麸澜,而懶加載符號,則是在首次使用時動態(tài)綁定打開
ViewController.m
文件奏黑,修改viewDidLoad
代碼- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"begin"); struct rebinding reb; reb.name="NSLog"; reb.replacement=my_NSLog; reb.replaced=(void *)&sys_NSLog; struct rebinding rebs[] = { reb }; rebind_symbols(rebs, 1); NSLog(@"end"); }
在
begin
和end
設(shè)置兩處斷點炊邦,真機運行項目
使用
image list
命令,找到程序虛擬內(nèi)存的首地址
- 首地址:
0x102c48000
ASLR
:0x2c48000
查看
NSLog
在MachO
中的偏移地址和占位地址
- 偏移地址:
0xC000
- 占位地址:
0x1000064C0
在
lldb
中攀涵,使用程序首地址 + 偏移地址
铣耘,找到NSLog
加載前的占位地址
- 在
lldb
中洽沟,獲取的NSLog
占位地址為0x102c4e4c0
以故,和MachO
中看到的0x1000064C0
并不一樣因為
MachO
中的占位地址,還要加上程序啟動時的ASLR
隨機偏移地址
占位地址 + ASLR = 0x102c4e4c0
裆操,和lldb
中讀取出的地址一致
案例2
查看
NSLog
加載后的真實地址承接
案例1
怒详,斷點向下執(zhí)行
在
lldb
中炉媒,使用程序首地址 + 偏移地址
,找到NSLog
加載后的真實地址
- 之前的占位地址
0x102c4e4c0
昆烁,被真實地址0x19d9327f0
替換使用
dis -s 0x19d9327f0
命令吊骤,讀取地址中的代碼dis -s 0x000000019d9327f0 ------------------------- Foundation`NSLog: 0x19d9327f0 <+0>: sub sp, sp, #0x20 ; =0x20 0x19d9327f4 <+4>: stp x29, x30, [sp, #0x10] 0x19d9327f8 <+8>: add x29, sp, #0x10 ; =0x10 0x19d9327fc <+12>: adrp x8, 311278 0x19d932800 <+16>: ldr x8, [x8, #0xb70] 0x19d932804 <+20>: ldr x8, [x8] 0x19d932808 <+24>: str x8, [sp, #0x8] 0x19d93280c <+28>: add x8, x29, #0x10 ; =0x10
- 指向
NSLog
代碼
案例3
查看交換后的函數(shù)地址
承接
案例2
,斷點向下執(zhí)行
在
lldb
中静尼,使用程序首地址 + 偏移地址
白粉,找到NSLog
交換后的函數(shù)地址
- 原本
NSLog
的真實地址為0x19d9327f0
,交換后變?yōu)?code>0x102c4d57c使用
dis -s 0x102c4d57c
命令鼠渺,讀取地址中的代碼dis -s 0x102c4d57c ------------------------- fishhookDemo`my_NSLog: 0x102c4d57c <+0>: sub sp, sp, #0x30 ; =0x30 0x102c4d580 <+4>: stp x29, x30, [sp, #0x20] 0x102c4d584 <+8>: add x29, sp, #0x20 ; =0x20 0x102c4d588 <+12>: sub x8, x29, #0x8 ; =0x8 0x102c4d58c <+16>: mov x9, #0x0 0x102c4d590 <+20>: stur x9, [x29, #-0x8] 0x102c4d594 <+24>: str x0, [sp, #0x10] 0x102c4d598 <+28>: mov x0, x8
指向
my_NSLog
代碼由此可見鸭巴,
HOOK
外部的C函數(shù)
,本質(zhì)就是在修改符號和地址的對應(yīng)關(guān)系
符號綁定的過程
Symbol Table
:符號表拦盹,?來保存符號String Table
:字符串表鹃祖,?來保存符號的名稱Indirect Symbol Table
:間接符號表,保存使?的外部符號普舆。更準(zhǔn)確?點就是使?的外部動態(tài)庫的符號恬口。是Symbol Table
的子集代碼中的函數(shù)名、變量名沼侣、方法名祖能,在項目編譯后,都會生成一張符號表蛾洛。符號之間也有差別芯杀,分為內(nèi)部符號和外部符號
內(nèi)部符號是當(dāng)前
MachO
中的符號,而外部符號又稱為間接符號雅潭,例如:系統(tǒng)庫揭厚、動態(tài)庫中的符號
符號按可見性劃分,分為全局符號和本地符號
- 全局符號對整個項目可見
- 本地符號僅對當(dāng)前文件可見
案例1:
符號綁定的過程
打開
ViewController.m
文件扶供,寫入以下代碼:#import "ViewController.h" @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"第一次外部函數(shù)的調(diào)用!"); NSLog(@"第二次外部函數(shù)的調(diào)用!"); } @end
真機運行項目筛圆,來到
viewDidLoad
方法:
- 兩次
NSLog
的指令都是bl 0x1022ba4d8
使用
image list
命令,找到程序虛擬內(nèi)存的首地址
- 首地址:
0x1022b4000
使用
NSLog地址 - 首地址
計算偏移地址
- 偏移地址:
0x64d8
打開
MachO
文件椿浓,找到偏移地址為0x64d8
的位置
- 處于
__TEXT,__stubs
中__stubs
:符號樁太援,本質(zhì)上就是一段代碼,用于跳轉(zhuǎn)到懶加載符號表中扳碍,找到對應(yīng)符號的值0x64d8
即是NSLog
的樁在
fishHook
原理探究中提岔,多次提到跳轉(zhuǎn)到占位地址,這種說法并不準(zhǔn)確笋敞。真實的情況是碱蒙,會跳轉(zhuǎn)到外部符號的樁,本質(zhì)上就是一段代碼
案例2:
查看樁對應(yīng)的代碼
查看
MachO
文件,找到樁對應(yīng)的值
1F2003D530D9025800021FD6
:以二進制指令的形式存儲的代碼單步調(diào)試赛惩,來到
NSLog
函數(shù)
使用
x 0x1022ba4d8
命令哀墓,讀取地址中的值,以二進制形式顯示
1F2003D530D9025800021FD6
喷兼,和MachO
中對應(yīng)的指令一致有此可見篮绰,
1F2003D530D9025800021FD6
對應(yīng)的就是上述三句匯編代碼
案例3:
三句匯編代碼的含義?
斷點執(zhí)行到
br x16
指令
br
指令:跳轉(zhuǎn)到x16
寄存器存儲的地址查看
x16
寄存器存儲的地址季惯,再減去ASLR
偏移地址
- 得到地址:
0x10000658c
查看
MachO
文件吠各,找到0x10000658c
地址
- 指向懶加載符號表中
NSLog
符號的值三句匯編代碼的含義:找到懶加載符號表中的地址去執(zhí)行
案例4:
懶加載符號表中的地址,指向的代碼是什么勉抓?
在
MachO
中走孽,找到偏移地址0x658c
,指向__TEXT,__stubs_helper
中的代碼
b
指令琳状,跳轉(zhuǎn)到偏移地址為0x6574
的位置執(zhí)行代碼在
MachO
中磕瓷,找到偏移地址為0x6574
的位置
x16
寄存器,偏移地址為0x8000
br
指令念逞,跳轉(zhuǎn)到偏移地址為0x8000
位置執(zhí)行代碼在
MachO
中困食,找到偏移地址為0x8000
的位置
- 處于非懶加載符號表中
- 執(zhí)行
dyld_stub_binder
函數(shù),用于符號綁定懶加載符號表中的地址翎承,指向?qū)ふ也?zhí)行
dyld_stub_binder
函數(shù)的代碼
案例5:
dyld_stub_binder
也是外部函數(shù)硕盹,它的地址是如何找到的?在
MachO
中叨咖,可以看到dyld_stub_binder
函數(shù)的偏移地址為0x8000
瘩例,但全是0
,說明此時還沒有值
dyld_stub_binder
函數(shù)甸各,同樣是外部函數(shù)垛贤,存儲在非懶加載表中當(dāng)
dyld
加載主程序時,會綁定非懶加載符號和弱引用符號趣倾,所以dyld_stub_binder
函數(shù)的值聘惦,在程序啟動時被dyld
直接綁定真機運行項目,讀取
首地址 + 0x8000
地址中的值
使用
dis -s 0x19c2e6f94
命令儒恋,讀取地址中的代碼libdyld.dylib`dyld_stub_binder: 0x19c2e6f94 <+0>: stp x29, x30, [sp, #-0x10]! 0x19c2e6f98 <+4>: mov x29, sp 0x19c2e6f9c <+8>: sub sp, sp, #0xf0 ; =0xf0 0x19c2e6fa0 <+12>: stp x0, x1, [x29, #-0x10] 0x19c2e6fa4 <+16>: stp x2, x3, [x29, #-0x20] 0x19c2e6fa8 <+20>: stp x4, x5, [x29, #-0x30] 0x19c2e6fac <+24>: stp x6, x7, [x29, #-0x40] 0x19c2e6fb0 <+28>: stp x8, x9, [x29, #-0x50]
案例6:
NSLog
函數(shù)善绎,首次加載和非首次加載的區(qū)別真機運行項目,首次加載
NSLog
函數(shù)
- 先找到樁里面的代碼
- 找到懶加載符號表中的地址去執(zhí)行
- 指向
__stubs_helper
中的代碼- 尋找并執(zhí)行
dyld_stub_binder
函數(shù)- 符號表重綁定
再次加載
NSLog
函數(shù)
- 找到樁里面的代碼
- 找到懶加載符號表中的地址去執(zhí)行
- 首次加載
NSLog
函數(shù)诫尽,懶加載符號表中的地址禀酱,因重綁定而修改- 修改后的值,直接指向
NSLog
函數(shù)的真實地址
通過符號找到字符串
fishHook
提供的rebinding
結(jié)構(gòu)體牧嫉,其中name
為需要HOOK
的函數(shù)名稱作用:當(dāng)找到相應(yīng)的符號剂跟,再通過符號找到字符串,然后和
name
進行字符串比較,如果匹配成功浩聋,則替換函數(shù)指針
案例1:
通過懶加載符號表中的符號观蜗,找到間接符號表中的相同符號臊恋,再找到符號對應(yīng)的值
__la_symbol_ptr
懶加載符號表中的符號和順序衣洁,跟間接符號表一致
在
Dynamic Symbol Table
中,找到NSLog
的值
案例2:
間接符號表中
NSLog
的值為0xB8
抖仅,轉(zhuǎn)為10進制
為184
184
對應(yīng)的是此符號在符號總表中的角標(biāo)在
Symbol Table
中坊夫,通過角標(biāo)找到符號凹髓,再找到String Table Index
的值
案例3:
符號表中
NSLog
的String Table Index
值為0xCE
廉丽,表示在字符串表中的偏移地址在
String Table
中,找到首地址
首地址
:0x11230
通過
首地址 + 偏移值 = 0x112FE
萝挤,找到對應(yīng)字符串
_
是函數(shù)的開始放吩,.
是分隔符 智听。5F
從_
開始,往后讀取_NSLog
渡紫,遇到分隔符結(jié)束
總結(jié)
HOOK
- 鉤子到推,改變程序執(zhí)行流程的一種技術(shù)
Method Swizzle
利用OC
運行時的特性,修改SEL
和IMP
的對應(yīng)關(guān)系惕澎,達到對OC
方法HOOK
的目的
IMP
莉测,本質(zhì)上是函數(shù)指針method_exchangeImplementations
:交互兩個IMP
class_replaceMethod
:替換某個SEL
的IMP
,如果沒有該方法唧喉,使用class_addMethod
添加method_getImplementation
捣卤、method_setImplementation
:獲取和設(shè)置某個方法的IMP
,很多三方框架都使用這種方式
fishHook
MachO
文件的加載原理董朝,動態(tài)修改懶加載和非懶加載兩張符號表- 可以
HOOK
系統(tǒng)函數(shù),但是無法HOOK
自定義函數(shù)
fishHook
原理解析
- 共享緩存:
iOS
系統(tǒng)有一塊特殊的位置干跛,存放公用動態(tài)庫益涧。即:動態(tài)庫共享緩存(dyld shared cache
)PIC
技術(shù):由于外部函數(shù)調(diào)用,在編譯時期無法確定內(nèi)存地址驯鳖。蘋果采用PIC
技術(shù)(位置獨立代碼)闲询,在MachO
文件的DATA
段,建立懶加載和非懶加載兩張符號表浅辙,里面存放執(zhí)行外部函數(shù)的指針符號綁定過程
- 外部函數(shù)調(diào)用扭弧,先找到樁里面的代碼,
__TEXT,__stubs
- 找到懶加載符號表中的地址去執(zhí)行
外部函數(shù)记舆,首次加載
- 懶加載符號表中的地址鸽捻,指向
__TEXT,__stubs_helper
中的代碼- 通過代碼尋找并執(zhí)行
dyld_stub_binder
函數(shù)- 作用:符號表重綁定
外部函數(shù),非首次加載
- 懶加載符號表中的地址,因重綁定而修改
- 修改后的值御蒲,直接指向外部函數(shù)的真實地址
dyld_stub_binder
函數(shù)
- 存儲在非懶加載符號表中
- 當(dāng)
dyld
加載主程序時衣赶,符號被dyld
直接綁定通過符號找到字符串
fishHook
利用Lazy Symbol Table
→Indirect Symbol Table
→Symbol Table
→String Table
,通過重綁定修改指針的值達到HOOK
目的