學(xué)習(xí)hook
灾茁,不是要攻擊別人窜觉,破壞別人的應(yīng)用場景,而是為了更好的防護北专,讓自己的應(yīng)用更堅固更安全禀挫。
一、動態(tài)庫注入回顧
在 《動態(tài)庫注入》中使用了
yololib
對自定義動態(tài)庫在WX
應(yīng)用中插入拓颓,既然能插入自定義庫语婴,我們就利用hook
技術(shù)做了小小的改動,替換了登錄按鈕的方法,并攔截了微信步數(shù)上傳的方法砰左,修改了步數(shù)匿醒。hook
是改變程序執(zhí)行流程的技術(shù),能夠修改其他應(yīng)用缠导,也能對自己應(yīng)用做防護廉羔。
通常替換方法有如下:
- 使用
method_exchange
交換sel
的imp
指針; - 通過
getImp
獲取原有函數(shù)指針僻造,通過setImp
設(shè)置原sel
指向的指針為自定義函數(shù)指針憋他; - 通過
getImp
獲取原有函數(shù)指針,通過replace
替換原sel
指向的指針為自定義函數(shù)指針髓削,通setImp
一樣竹挡。
自己項目中使用:
通常在自己項目中使用,建立分類立膛,在分類
load
中來替換方法揪罕,以達到監(jiān)聽方法調(diào)用的目的。
hook
視圖的出現(xiàn)消失:跟蹤頁面位置旧巾;
hook
析構(gòu)函數(shù)dealloc
(deinit
):跟蹤對象釋放狀況耸序;
hook
消息轉(zhuǎn)發(fā)方法(forword):避免調(diào)用未實現(xiàn)方法而產(chǎn)生崩潰;
hook
數(shù)組objectAtIndexedSubscript
方法:避免數(shù)組越界崩潰鲁猩;
……只要是
OC
的方法都可以根據(jù)業(yè)務(wù)需求進行監(jiān)聽修改或替換。
在其他應(yīng)用中使用:
通過動態(tài)庫注入的方式罢坝,來插入自己的
load
廓握,讓自己的代碼能和其他項目一起執(zhí)行,同樣使用我們常規(guī)的替換方法即可嘁酿。通過查看視圖頁面屬性確定要hook
的目標(biāo)對象隙券。
hook
微信步數(shù)獲取方法:實現(xiàn)微信步數(shù)修改;
hook
微信搶紅包相關(guān)方法:實現(xiàn)自動搶紅包功能闹司;
hook
微信消息撤回方法:實現(xiàn)拒絕消息撤回功能娱仔;
(ps:學(xué)習(xí)中,實戰(zhàn)不多游桩,有方法就有更多的可能)在其他應(yīng)用中是通過動態(tài)庫來進行
hook
的牲迫,因此在目標(biāo)對象所在類的方法列表中并沒有要替換的方法,回歸原有方法調(diào)用會出現(xiàn)崩潰借卧,這里可以使用獲取原有方法的函數(shù)地址盹憎,直接調(diào)用函數(shù)即可實現(xiàn)原有方法的調(diào)用。
以上是針對OC
的動態(tài)特性來hook
的铐刘,我們能夠hook
動態(tài)語言下的函數(shù)陪每,那么能不能直接hook
系統(tǒng)的靜態(tài)函數(shù)呢,下面來看一下fishhook
是如何hook
的。
二檩禾、fishhook概述
fishhook 是FaceBook
的開源庫挂签,能夠動態(tài)的修改MachO
的符號表,來hook
系統(tǒng)的C
函數(shù)盼产。 C
函數(shù)是在編譯時來確定函數(shù)地址在內(nèi)存中的偏移量饵婆,編譯后該偏移量是確定的,為什么說是偏移量是確定的呢辆飘?
在很多系統(tǒng)中都采用了 ASLR 技術(shù)啦辐,對堆、棧蜈项、共享庫等線性區(qū)布局進行隨機化芹关,來避免攻擊者直接獲取攻擊代碼位置。MachO
的首地址是隨機變化的紧卒,找到首地址就能找到對應(yīng)的函數(shù)地址侥衬,即 Offset + MachO
。
靜態(tài)語言:編譯時確定函數(shù)地址
動態(tài)語言:運行時確定函數(shù)地址
三跑芳、fishhook簡單使用
使用步驟:
1轴总、直接將.c、.h
加入到工程博个;
2怀樟、創(chuàng)建結(jié)構(gòu)體指明被替換的函數(shù)、替換的函數(shù)盆佣、接收原函數(shù)地址的指針往堡;
3、綁定符號表共耍。
使用庫函數(shù)交換系統(tǒng)NSLog
函數(shù):
#pragma mark - 交換NSLog
-(void)exchangeLog {
NSLog(@"我來了");
//hook NSLog函數(shù)
struct rebinding imp;
imp.name = "NSLog";
imp.replacement = my_NSLog;
imp.replaced = &sys_nslog;
//存放rebinding結(jié)構(gòu)體數(shù)組虑灰,一次可以交換多個函數(shù)
struct rebinding rebs[1] = {imp};
rebind_symbols(rebs, 1);
NSLog(@"點擊了屏幕");
}
//實現(xiàn)一個函數(shù)來替換原有函數(shù)-函數(shù)名稱即是函數(shù)的指針
void my_NSLog(NSString *format, ...) {
printf("攔截打印\n");
sys_nslog(format);
}
//定義指針來接收原始函數(shù)的指針
static void (*sys_nslog)(NSString *format,...);
打印如下:
攔截打印
點擊了屏幕
運行輸出,方法被攔截痹兜,說明NSLog
函數(shù)已被替換穆咐。
上面有提到,所有的函數(shù)地址在編譯后都是確定字旭,在OC
中能夠交換方法是因為有sel
和imp
的連接過程对湃,函數(shù)與用戶之間有一個中間者,那么fishhook
應(yīng)該也是如此谐算,修改了中間者的imp
指向熟尉,否則直接調(diào)用函數(shù),就沒有交換的可能洲脂,在MachO
中這個中間者叫符號斤儿。
動態(tài)替換:
user -> sel -> imp
靜態(tài)替換:user -> symbol -> imp
hook自定義函數(shù)
-(void)exchangeFun{
struct rebinding imp;
imp.name = "old_func";
imp.replacement = new_func;
imp.replaced = &sys_func;
struct rebinding rebs[1] = {imp};
rebind_symbols(rebs, 1);
old_func();
}
void (*sys_func);
void old_func(){
printf("old_func\n");
}
void new_func(){
printf("new_func\n");
}
打印如下:
old_func
打印還是原來的方法剧包,這里并沒有替換。這里可以猜想自定義函數(shù)調(diào)用沒有產(chǎn)生中間者往果,這里是直接調(diào)用的疆液,所以無法交換。
四陕贮、fishhook的實現(xiàn)原理
fishhook
主要利用了共享緩存功能和PIC
技術(shù)來實現(xiàn)hook
功能堕油。
動態(tài)共享緩存
iOS
系統(tǒng)為節(jié)省內(nèi)存資源,將系統(tǒng)的動態(tài)庫資源統(tǒng)一放在了系統(tǒng)的共享區(qū)域肮之,該區(qū)域其他應(yīng)用都可以訪問掉缺。
PIC技術(shù)
PIC
技術(shù)叫位置代碼獨立,在MachO
文件中會預(yù)留出一段空間戈擒,這一段空間叫做符號表眶明,在MachO
文件的數(shù)據(jù)段中。dyld
在加載MachO
文件到內(nèi)存中后筐高,會將共享區(qū)的系統(tǒng)函數(shù)地址綁定到對應(yīng)的符號上搜囱,并插入到符號表中。這樣在項目中調(diào)用相關(guān)系統(tǒng)函數(shù)時柑土,實際上調(diào)用的是對應(yīng)的符號蜀肘,通過符號來找到具體的系統(tǒng)函數(shù)地址。
到這里思路應(yīng)該清晰很多稽屏,fishhook
實現(xiàn)如下:
- 根據(jù)符號(字符)獲取系統(tǒng)函數(shù)地址扮宠;
- 替換符號指向的地址為用戶聲明的函數(shù)地址(符號綁定);
- 對外部聲明的指針進行系統(tǒng)函數(shù)地址賦值狐榔。
上面所提到的自定義函數(shù)無法hook
也就了然了涵卵,自定義函數(shù)地址確定在代碼段中,缺少dyld
的符號綁定階段荒叼,并且應(yīng)用中調(diào)用的是最原始的函數(shù)地址,因此無法交換典鸡。
data段:程序運行期間可讀可寫
代碼段:程序運行期間只讀
五被廓、fishhook符號綁定分析
這里使用 MachOView 工具,對以上給出的代碼生成的MachO
文件進行分析萝玷。
*獲取符號表地址
1嫁乘、machO
符號表中有懶加載表(_la_symbol_ptr
)和非懶加載表(_nl_symbol_ptr
)的_data
段,在表中存放著與外部綁定的函數(shù)指針球碉,在懶加載端有offset
地址蜓斧,如下:
- 這里都是我們熟悉的名稱,這些函數(shù)的使用是需要進行懶加載綁定的
- 提供了相對于
MachO
起始地址的偏移量offset
睁冬,實際地址即是系統(tǒng)函數(shù)所在的內(nèi)存地址
這里觀察NSLog
函數(shù)挎春,記住offset=0x4030
這個偏移量看疙。
2、在打印函數(shù)調(diào)用前下斷點直奋,執(zhí)行image list
獲取模塊列表能庆,如下:
- 這里的
image
就是一個個模塊,一個個MachO
文件
找到第一個模塊地址:0x00000001081f4000
脚线,該地址為應(yīng)用程序的起始地址搁胆,在程序運行期間是固定,重新啟動該地址則會隨機變化邮绿,即上面所提到的 ASLR 技術(shù)渠旁。
3、起始地址與偏移量相加:0x00000001081f4000 + 4030 = 0x1081F8030
(這里使用mac
計算器的編程器)船逮,便是符號表的地址顾腊,記住這個值。
讀內(nèi)存
1傻唾、讀取符號表地址
memory read 0x1081F8030
取第一行投慈,從右向左取8位數(shù)據(jù):0x01081f6984
,改地址即是系統(tǒng)函數(shù)NSLog
地址冠骄,使用命令:
dis -s 0x01081f6984
打印匯編伪煤,查看綁定情況。打印如下:
由于是查看懶加載表的偏移量凛辣,此時函數(shù)還沒有調(diào)用抱既,并沒有綁定。
2扁誓、斷點到rebind_symbols(rebs, 1)
處防泵,運行再打印符號表地址:
memory read 0x1081F8030
打印如下:
0x1081f8030: be e1 58 08 01 00 00 00 5d a5 57 08 01 00 00 00 ..X.....].W.....
0x1081f8040: 16 6b 29 0c 01 00 00 00 ca 69 1f 08 01 00 00 00 .k)......i......
同上從右向左取8位,再來查看匯編內(nèi)容:
dis -s 0x010858e1be
打印如下:
符號表有內(nèi)容了蝗敢,說明NSLog
為懶加載捷泞,運行后,將Foundation
中的NSLog
加入到符號表中寿谴。因此只要該函數(shù)被加載過锁右,就可以找到綁定的符號地址。
3讶泰、繼續(xù)執(zhí)行咏瑟,使用fishhook
進行hook,再查看符號表地址內(nèi)容:
memory read 0x1081F8030
打印如下:
由于fishhook
對系統(tǒng)NSLog
函數(shù)的替換痪署,以上地址發(fā)生了變化码泞,取地址查看內(nèi)容:
dis -s 0x01081f5c60
打印如下:
此時發(fā)現(xiàn)符號表綁定了fishhookDemo
的my_NSLog
,說明此處的符號綁定被替換了狼犯。
通過以上實驗?zāi)軌蛑溃?code>dyld會對系統(tǒng)函數(shù)進行符號綁定余寥,符號表在數(shù)據(jù)段领铐,可讀可寫,dyld
可以綁定劈狐,fishhook
也可以通過系統(tǒng)函數(shù)進行綁定罐孝。
通過符號表查找系統(tǒng)函數(shù)
回到MachO文件,來看看Lazy Symbol肥缔、Dynamic Symbol Table莲兢、Symbol Table、String Table
表關(guān)系:
1续膳、Lazy Symbol
和Dynamic Symbol Tabel
一一對應(yīng)(在數(shù)組的下標(biāo)一致)這兩個表包含了所有與動態(tài)庫相關(guān)的符號改艇;
2、Dynamic Symbol Tabel
和Symbol Table
關(guān)聯(lián)坟岔,Dynamic Symbol Table
中的Data
字段是Symbol Table
數(shù)組的下標(biāo)谒兄;
3、Symbol Table
中的data
字段地址 + String Table
表的起始地址社付,就是目標(biāo)函數(shù)對應(yīng)字符的位置承疲。
以NSLog為例驗證
- 上面兩張圖的下標(biāo)與
value
一一對應(yīng)
找到indirect Symbols
表中的_NSLog
項:
以NSLog
函數(shù)為例,找到Data
地址0x94鸥咖,等于十進制148燕鸽,即為Symbol Table
表中NSLog
對應(yīng)的下標(biāo),如下:
通過下標(biāo)找到了對應(yīng)的符號啼辣,主要上面的標(biāo)注0xBA為String Table
中的偏移量啊研,表的起始地址加上偏移量即是函數(shù)名所在的位置。
通過首地址獲取最終函數(shù)名鸥拧,0xBA + 0x6180 = 0x623A党远,找到0x623A地址處,如下:
最終找到了系統(tǒng)的函數(shù)富弦。
小結(jié):
OC
的運行時替換沟娱,和fishhook
對系統(tǒng)函數(shù)的替換,都是由于有中間者的存在腕柜,才有hook
的機會花沉。
六、fishhook源碼分析
……