《iOS底層原理文章匯總》
上一篇文章《iOS-逆向13-Dyld》介紹了Dyld的加載過程轰坊,本文介紹Hook原理
1.HOOK
HOOK,中文譯為“掛鉤”或“鉤子”。在iOS逆向中是指改變程序運行流程的一種技術(shù)景用。通過hook可以讓別人的程序執(zhí)行自己所寫的代碼术吝。在逆向中經(jīng)常使用這種技術(shù)转捕。所以在學(xué)習(xí)過程中园细,我們重點要了解其原理胳螟,這樣能夠?qū)阂獯a進行有效的防護昔馋。
2.iOS中HOOK技術(shù)的幾種方式
1、Method Swizzle
利用OC的Runtime特性糖耸,動態(tài)改變SEL(方法編號)和IMP(方法實現(xiàn))的對應(yīng)關(guān)系秘遏,達到OC方法調(diào)用流程改變的目的。主要用于OC方法嘉竟。
2邦危、fishhook
它是Facebook提供的一個動態(tài)修改鏈接mach-O文件的工具洋侨。利用MachO文件加載原理,通過修改懶加載和非懶加載兩個表的指針達到C函數(shù)HOOK的目的倦蚪。
3凰兑、Cydia Substrate
???Cydia Substrate 原名為 Mobile Substrate ,它的主要作用是針對OC方法审丘、C函數(shù)以及函數(shù)地址進行HOOK操作吏够。當(dāng)然它并不是僅僅針對iOS而設(shè)計的,安卓一樣可以用滩报。官方地址:http://www.cydiasubstrate.com/
???
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ù)
void MSHookFunction(voidfunction,void* replacement,void** p_original) , Logos語法的%hook 就是對此函數(shù)做了一層封裝
MobileLoader
??MobileLoader用于加載第三方dylib在運行的應(yīng)用程序中脓钾。啟動時MobileLoader會根據(jù)規(guī)則把指定目錄的第三方的動態(tài)庫加載進去售睹,第三方的動態(tài)庫也就是我們寫的破解程序.
safe mode
???破解程序本質(zhì)是dylib,寄生在別人進程里可训。 系統(tǒng)進程一旦出錯昌妹,可能導(dǎo)致整個進程崩潰,崩潰后就會造成iOS癱瘓。所以CydiaSubstrate引入了安全模式,在安全模 式下所有基于CydiaSubstratede 的三方dylib都會被禁用握截,便于查錯與修復(fù)飞崖。
3.fishhook的簡單實用
fishHOOK可以HOOK我們的C函數(shù),但是我們知道函數(shù)是靜態(tài)的谨胞,也就是說在編譯的時候固歪,編譯器就知道了它的實現(xiàn)地址,這也是為什么C函數(shù)只寫函數(shù)聲明調(diào)用時會報錯胯努。那么為什么fishhook還能夠改變C函數(shù)的調(diào)用呢牢裳?難道函數(shù)也有動態(tài)的特性存在?我們一起來探究它的原理…
I.Hook系統(tǒng)函數(shù)NSLog
OC的動態(tài)特性:不是直接調(diào)用方法實現(xiàn)的地址叶沛,是通過SEL和IMP之間的對應(yīng)關(guān)系蒲讯,通過SEL尋找IMP,存在一張映射關(guān)系的表灰署,若將此表改掉判帮,達到hook目的
C靜態(tài)語言:直接通過地址訪問,匯編bl 0x123456,編譯之后會存在內(nèi)存中
將fishhook.h和fishhook.c文件拖入工程中
fishhook.h中的三個屬性
struct rebinding {
const char *name;//需要HOOK的函數(shù)名稱氓侧,C字符串
void *replacement;//新函數(shù)的地址
void **replaced;//原始函數(shù)地址的指針脊另!
};
name是需要HOOK的函數(shù)名稱导狡,replacement是新函數(shù)的地址约巷,replaced是原始函數(shù)地址的指針
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建rebinding結(jié)構(gòu)體
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = my_NSLog;
nslog.replaced = (void *)&sys_nslog;
//需求:HOOK NSLog
struct rebinding bds[] = {nslog};
rebind_symbols(bds, 1);
}
//函數(shù)指針void NSLog(NSString *format, ...)
static void (*sys_nslog)(NSString *format, ...);
//新函數(shù)
void my_NSLog(NSString *format, ...){
format = [format stringByAppendingString:@"\n我hook到了!"];
//走到系統(tǒng)的NSLog里面去
sys_nslog(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"NSLog");//指向了my_NSLog
}
如上系統(tǒng)的NSLog被成功hook到。
II.Hook C函數(shù)
C是靜態(tài)語言旱捧,直接通過地址訪問
同樣的方式hook C函數(shù)
void func(const char *str){
NSLog(@"%s",str);
}
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建rebinding 結(jié)構(gòu)體
struct rebinding func;
func.name = "func";
func.replacement = my_func;
//保存C函數(shù)func地址的指針!
func.replaced = (void *)&func_p;
//需求:HOOK NSLog
struct rebinding bds[] = {func};
rebind_symbols(bds, 1);
}
static void (*func_p)(const char *str);
void my_func(const char *str){
NSLog(@"Hook了!!");
func_p(str);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
func("hello");
}
并不能hook成功独郎,為什么呢踩麦???
4.FishHook的原理
系統(tǒng)的函數(shù)NSLog能hook到氓癌,自定義的函數(shù)func無法hook到谓谦,編譯的時候無法知道NSLog的真實地址
App通過LLVM編譯成可執(zhí)行文件MachO時,其中有調(diào)用NSLog贪婉,此時的NSLog并沒有地址反粥,預(yù)留占位符
待程序運行到內(nèi)存中的時候,加載共享緩存UIKit疲迂、Foundation等才顿,dyld用Foudation中NSLog的真實地址將其替換,
從而能準(zhǔn)確調(diào)用尤蒿,MachO文件中存在Text段郑气,即代碼段可讀可執(zhí)行,Data段可讀可寫可執(zhí)行腰池,
NSLog的符號放入Data段中尾组,存在一張映射表,方便運行時dyld修改綁定真實的NSLog地址示弓,
綁定真實地址后才能運行調(diào)用
MachO文件中的NSLog
符號讳侨,dyld告知其在
共享緩存Foundation中
的地址進行符號綁定,
在內(nèi)存中執(zhí)行時奏属,能準(zhǔn)確
找到NSLog的地址
I.符號和地址存在一張映射表符號(SEL)--->地址(IMP)
HOOK時爷耀,替換的是MachO文件中符號表中NSLog的符號地址,將其地址綁定為了自定義的my_nslog的符號地址
rebind_symbols(bds, 1)重新綁定符號拍皮,通過NSLog字符串找到符號表歹叮,修改符號表中對應(yīng)地址的映射關(guān)系,將NSLog符號的真實地址修改為了my_nslog的地址
II.符號綁定地址替換過程
前文探究了符號地址綁定替換的過程铆帽,現(xiàn)在真實運行程序驗證符號地址替換過程
dyld應(yīng)用程序加載咆耿,鏈接主程序后會進行非懶加載符號的綁定,程序啟動會綁定非懶加載的符號爹橱,懶加載是運行調(diào)用的時候采取綁定萨螺,NSLog只有調(diào)用的時候采取綁定符號,并不是程序啟動的時候就綁定了
A.綁定前的地址
NSLog的符號地址在懶加載(運行調(diào)用到了才去綁定符號)Lazy Symbol Pointers符號表中愧驱,將可執(zhí)行文件MachO拖入MachOView中查看
NSLog符號綁定的地址值是00000001000064D4
相對于程序入口起始地址的偏移值00008010
上圖中的NSLog的地址是未綁定的地址慰技,是一個占位符實質(zhì)也是一段代碼的地址,我們在程序中斷點調(diào)試,得到系統(tǒng)隨機分配的主程序的起始地址也就是ASLR0x00000001041e0000组砚,前面的1是PAGEZERO,加上偏移值offset00008010
讀取ASLR+offset內(nèi)存地址中的值吻商,也是地址值
此地址值減去ASLR(程序的起始地址)等于MachOView中
NSLog綁定的地址值,此地址為NSLog在沒有被調(diào)用前的地址
MachOView中的是沒有ASLR偏移地址的地址糟红,但是有PAGEZERO艾帐,所以內(nèi)存中的地址0x00000001041e64d4減去ASLR0x00000000041e0000得到編譯后NSLog符號綁定的地址0x00000001000064d4
NSLog符號綁定的地址為0x00000001000064d4乌叶,此地址是NSLog沒有被調(diào)用前,編譯器LLVM綁定的地址
B.綁定后的地址
NSLog執(zhí)行后柒爸,符號NSLog綁定的地址發(fā)生變化准浴,變?yōu)镹SLog的真實地址,F(xiàn)oundation中的NSLog在進程內(nèi)存中的地址
懶加載符號表里面綁定的地址已經(jīng)變了
C.FishHook后的符號地址
執(zhí)行rebind_symbols(bds,1)后捎稚,符號NSLog綁定的地址再次發(fā)生變化乐横,變?yōu)樽远x的my_NSLog的地址
懶加載符號表里面綁定的地址再次變了
5.符號綁定過程
iOS中函數(shù)名、變量名今野、方法名晰奖、編譯完成后會生成一張符號表
符號與符號之間存在差別,一個是內(nèi)部符號腥泥,一個是外部符號
I.內(nèi)部符號:內(nèi)部函數(shù)匾南,方法名稱,如ViewDidLoad
內(nèi)部符號又分為本地和全局
本地:我自己內(nèi)部使用的
全局:外部也可以使用
定義函數(shù)時,全局符號蛔外,如自己寫一個動態(tài)庫
//全局符號蛆楞,可以暴露給外界
void test(){
}
本地符號:App在上架時會去符號,去的是本地符號
//本地符號 作用域相當(dāng)于本文件
static void test1(){
NSLog(@"test1");
}
編譯后通過命令行查看符號objdump --macho -t Symbol
II.外部符號(間接符號表):MachO文件中調(diào)用外部方法名稱夹厌,如NSLog豹爹,LLVM編譯時期并不知道外部(MachO文件以外)方法的地址
符號表Symbols:包含所有的符號,本地符號矛纹,全局符號臂聋,間接符號
間接符號有個專門的符號表Indirect Symbols,用到了外部的NSLog或南,編譯時會生成一個符號
匯編中跳入的地址0x100caa4e4減去ASLR0x0000000100ca4000得到的地址0x64e4孩等,在MachOView中查詢0x64e4的地址
編譯時,會先讓NSLog跳入樁0x64e4中,樁的值1F2003D510D9005800021FD6采够,是一串二進制指令肄方,代碼
前文bl -> 占位符(符號表里面保存的地址),其實不是這樣的蹬癌。實際是bl -> 樁(一塊代碼,去符號表里面的一塊代碼執(zhí)行) -> 符號表
依據(jù)此权她,此時應(yīng)該跳入懶加載符號表中的地址
所以樁的代碼就是去執(zhí)行符號表里面所指向的地址,接下來在MachO中尋找000000010000658C地址逝薪,發(fā)現(xiàn)會跳向00006574隅要,Lazy Symbols中的其他符號如NSStringFromClass符號表中對應(yīng)地址為0000000100006598,故開頭的這一段代碼共用董济,用作符號綁定步清,Lazy Symbols中的所有符號都會執(zhí)行本地的一段代碼,也就是開頭的共用代碼來進行符號綁定
加上ASLR0xca4000即和運行時的地址一模一樣感局,0x100006574+0xca400=0x100caa574,b跳轉(zhuǎn)的地址一致
此時我的電腦屏幕黑掉了尼啡,關(guān)機一會重啟暂衡,重新運行ASLR地址變?yōu)?x0000000104f8c000询微,所以ASLR是系統(tǒng)分配的隨機值崖瞭,每次都不一樣
斷點繼續(xù)調(diào)試,進入符號綁定函數(shù)dyld_stub_binder撑毛,懶加載符號表里面執(zhí)行的都是符號綁定函數(shù)0x100006594+0x4f8c000=0x104f92594,b跳轉(zhuǎn)的地址為0x100006574+0x4f8c000=0x104f92574
dyld_stub_binder此函數(shù)是內(nèi)部的還是外部的呢???外部的
既然是外部的书聚,那什么時候進行的dyld的綁定呢?去找dyld的dyld_stub_binder函數(shù)藻雌,通過找非懶加載的符號表,br0x100008000,在0x100008000的位置指向的地址去執(zhí)行
對于dyld中的符號dyld_stub_binder也有一個符號雌续,在非懶加載里面,在程序運行的時候就綁定了
dyld_stub_binder的地址為ASLR+0x8000,驗證如下x16寄存器里面的值就是非懶加載符號表里面所指向的值胯杭,默認全是0驯杜,程序啟動就綁定了dyld_stub_binder
(屏幕又黑掉,重新運行接著調(diào)試,ASLR再次變化為0x0000000102bbc000)程序繼續(xù)往下執(zhí)行進入dyld_stub_binder函數(shù)做个,此間接路徑有點長鸽心,bind完畢后,此時點擊step up 跳出居暖,第一次外部符號就調(diào)用了顽频,此時懶加載符號表里面的地址就變了
第二次執(zhí)行NSLog,此時還是先找樁太闺,樁里面的代碼沒有變化糯景,去Lazy Symbols Pointers里面取Data執(zhí)行,而此時NSLog對應(yīng)的Lazy Symbols Pointers符號表里面的Data已經(jīng)變化了省骂,不會再走綁定蟀淮,符號表里面的內(nèi)容變化了,指向了NSLog的真實地址钞澳,此時執(zhí)行跳入Foundation`NSLog中
此時直接通過樁就跳入了真實地址了C鸫!!外部函數(shù)的執(zhí)行都是先找樁柿祈,樁里面的代碼就是去符號表里面執(zhí)行地址颤芬,符號表里面的地址只要變化,執(zhí)行就發(fā)生變化
為什么要有樁的存在呢览妖?符號表里面是數(shù)據(jù)段,數(shù)據(jù)段是保存數(shù)據(jù)揽祥,地址讽膏,從一個數(shù)據(jù)段找到拿出一個地址執(zhí)行,需要代碼,這段代碼放哪里呢拄丰?這段代碼就放在樁里面Text段府树,和直接執(zhí)行沒什么兩樣俐末,只不過在編譯成匯編時需要有三行代碼,這三句代碼是去查表奄侠,叫樁
總結(jié):符號綁定過程
PIC技術(shù)卓箫,外部符號不確定,編譯時搞一個符號表專門去保存外部符號的地址垄潮,運行時刻得到
1.外部函數(shù)調(diào)用是執(zhí)行樁里面的代碼!(TEXT,stubs)
2.通過懶加載符號表里面的代碼去執(zhí)行
3.懶加載符號表里面默認保存的是尋找binder的代碼烹卒,綁定成功改變了懶加載符號表里面的值
4.binder函數(shù)在非懶加載符號表里面(程序運行就綁定好了)
第一次:懶加載符號表中查找地址執(zhí)行進入綁定過程
第二次:懶加載符號表中地址已經(jīng)變?yōu)镹SLog真實地址,直接執(zhí)行
動態(tài)庫和靜態(tài)庫弯洗,編譯靜態(tài)庫體積更小旅急,因為動態(tài)庫還會保留一些外部符號,給外部調(diào)用牡整,靜態(tài)庫合并成一個文件藐吮,不需要符號了,靜態(tài)庫沒有符號更小一點
符號綁定只改懶加載表和非懶加載符號表不會改符號表Symbols
Symbols逃贝,Indirect Symbols,String Table谣辞,Lazy Symbols Pointers這幾個表之間會有關(guān)聯(lián),并不是Symbols包含了所有,是幾張表之間的關(guān)聯(lián)秋泳,fishhook通過什么找到NSLog的潦闲,通過字符串“NSLog”,通過字符表能找到NSLog,怎么通過NSLog找到Lazy Symbols的呢?
內(nèi)部函數(shù)跳轉(zhuǎn)沒有查找符號流程迫皱,直接變成地址bl了