Hook && Anti-Hook

0x0 零

Hook是我們常用的一種技術(shù)手段,在開發(fā)中,也經(jīng)??吹絊elf-Hook等操作,而在逆向分析中,Hook也是用的不少。民間也流傳著很多Hook框架,比如Android上的Xposed,IOS上的Tweak,以及Substrate、Frida等等。那么作為Hook框架,他們之間有啥異同?

0x01 壹

說到Hook,不得不提一下注入,想要Hook其他進(jìn)程的內(nèi)容就得先注入別的進(jìn)程。在Android上,進(jìn)程注入一般有兩種方式,一是ELF文件感染,通過添加各種信息(比如新增section信息、新增依賴庫(kù)等),當(dāng)被感染的ELF文件加載、執(zhí)行時(shí),便可達(dá)到進(jìn)程注入的目的;第二種方式比較常用,就是老生常談的ptrace注入,具體怎么注入的,見這個(gè)小demo。

0x02 貳

Android上常見的Hook Native方式根據(jù)原理不同大致分為三種:異常Hook、導(dǎo)入表Hook、inline Hook。

Inline Hook

三種Hook方式各有各的優(yōu)缺點(diǎn),那么先說說最常見的inline hook,這種方式是最暴力、最直接的方式。簡(jiǎn)單說,inline hook 就是進(jìn)程注入后通過修改你要hook的那行指令為跳轉(zhuǎn)指令,并且目的地址是你自己的代碼區(qū)域,以此來獲取到程序的控制權(quán)。

inline hook

那么在跳轉(zhuǎn)到我們的代碼領(lǐng)空之后,我們要做的第一件事就是保存當(dāng)前的寄存器環(huán)境,以免等會(huì)跳轉(zhuǎn)回去執(zhí)行LDR R3,[R2,R3](以及之后的指令)時(shí)報(bào)錯(cuò)。接下來,就可以執(zhí)行我們的代碼,執(zhí)行結(jié)束后,第一步當(dāng)然是要恢復(fù)剛才的寄存器環(huán)境,除此之外,還有一件重要的事情,就是執(zhí)行被改成跳轉(zhuǎn)指令的那條無辜的指令,對(duì)應(yīng)上圖中的指令是Mov R0,R2。完事后,跳轉(zhuǎn)回剛才被修改過的指令的下一條指令地址,對(duì)應(yīng)上圖中的就是LDR R3,[R2,R3]指令的地址。

inline hook stub

這樣一來,我們就悄無聲息的把注入代碼給執(zhí)行了,并且也把控制權(quán)歸還給了原始代碼流程。了解了inline hook的大致流程,我們?cè)撛趺慈シ乐惯@種hook方式呢?講道理,其實(shí)是防不住的,最多也就算給逆向工作者添加一些難度系數(shù)。我們常??吹接袡z測(cè)注入模塊的方式去防止hook和注入,也有根據(jù)匹配某些特征去防的。但是他們都比較容易誤報(bào),我們這次來一點(diǎn)高精度的防護(hù)!想想看,一般hook都是針對(duì)function來的,也就是說,它們通常會(huì)去修改一個(gè)function的第一條指令為跳轉(zhuǎn)指令(對(duì)于一個(gè)正常的function來說,第一條指令基本不可能會(huì)是跳轉(zhuǎn)指令)。那么我們就從這里下手,去檢測(cè)某一個(gè)關(guān)鍵函數(shù)的頭幾個(gè)字節(jié)是不是跳轉(zhuǎn)指令(好捉急的方法%>_<%)。

我們這里針對(duì)frida來做inline hook的防御。先自己寫幾個(gè)腳本去hook某個(gè)function,同時(shí)對(duì)這個(gè)function的頭幾個(gè)字節(jié)做個(gè)內(nèi)存快照,最后同步到IDA里,我們會(huì)發(fā)現(xiàn)frida使用的跳轉(zhuǎn)指令在ARM和ARM64下由B系列或LDR構(gòu)成(下圖為我自己手動(dòng)構(gòu)造,請(qǐng)只關(guān)注指令,不需在意后面的目的地址)。

frida-inline hook BL
frida-inline hook LDR

再看看frida的源碼(這里是arm的,這里是thumb的,這里是arm64的

frida-arm-writer
frida-arm-writer

frida通過B[..] XXXXXX或者LDR[..] PC, XXXXXX跳轉(zhuǎn)到我們的功能函數(shù)里??吹竭@里,兩個(gè)問題:1.為啥要用兩種指令?2.既然有兩種方式跳轉(zhuǎn),為啥不加一個(gè)MOV PC,XXXXX這種跳轉(zhuǎn)?

第一個(gè)問題,先看看B系列指令的字節(jié)碼構(gòu)成:

B系列指令

offset是一個(gè)有符號(hào)的24bit數(shù)據(jù),所以大小在-8Mb到8Mb之間,在實(shí)際跳轉(zhuǎn)過程中,offset會(huì)左移2位(乘4,和ARM指令長(zhǎng)度有關(guān)),并且B系列是一種相對(duì)跳轉(zhuǎn)指令,所以算下來,跳轉(zhuǎn)范圍在(PC-32Mb,PC+32Mb)之間。而LDR跳轉(zhuǎn)就不存在范圍的問題了,后面的立即數(shù)是多少就跳多遠(yuǎn),所以當(dāng)范圍過大的時(shí)候(通常在ARM64下)會(huì)選擇使用LDR PC,XXXX來跳轉(zhuǎn)。

第二個(gè)問題,MOV指令,它后面的立即數(shù)必須滿足一個(gè)條件,能由8bit連續(xù)有效位通過偶數(shù)次移位能得到。比如0x1100、0xAF000000等,如果目的地址是0xEE0014這種地址,MOV PC,XXXXX就無法跳轉(zhuǎn)過去。

好了,話說回來,根據(jù)上述情況,針對(duì)某些函數(shù)防inline hook,就可以通過一個(gè)宏去檢測(cè)函數(shù)頭部字節(jié)是否匹配跳轉(zhuǎn)指令(opcode+異常的目的地址)。

繞過方式嘛....太多了

導(dǎo)入表Hook

我們知道在一個(gè)SO文件的.GOT表會(huì)在內(nèi)存中存儲(chǔ)導(dǎo)入函數(shù)的地址,那么導(dǎo)入表Hook原理就是去替換.GOT表里的那些地址,比如你把strcmp函數(shù)的地址替換成你的helloWorld函數(shù)地址,那么程序每次調(diào)用strcmp時(shí),你都可以在控制臺(tái)看到“Hello World”輸出。

原理很簡(jiǎn)單,操作時(shí)第一步就是定位.GOT表的地址,通過遍歷ELF文件的SectionHeader,然后獲取其sh_name的值,這個(gè)值就是.shstrtab內(nèi)容中的偏移值,取[.shstrtab_addr + Elf32_SectionHeader.sh_name]的字符串,如果是“.got”,就恭黑勒啦;第二步就是在.GOT表中找到strcmp的地址,然后替換成你helloWorld的地址就ok了(很好找,4字節(jié)對(duì)齊)。

防這種hook很容易,把編譯好的SO文件的section信息抹去就行了,反正SO文件執(zhí)行的時(shí)候不需要這些信息。繞過呢,看這里吧。

在IOS上有一種Hook方式和這種導(dǎo)入表方式有那么點(diǎn)點(diǎn)相似,都是替換的地址,叫做Method Swizzling。在編寫Tweak插件的時(shí)候,對(duì)于OC的函數(shù)的Hook,就是通過替換函數(shù)的IMP實(shí)現(xiàn)的。那么IOS上怎么防這種Hook方式呢?不可能把Mach-O文件的dyld_info_command結(jié)構(gòu)體給抹了吧。我是這樣考慮的,既然地址被替換了,那么該函數(shù)與同模塊的其他函數(shù)的相對(duì)偏移也就改變了,而且變化后的偏移肯定大的離譜。那么給個(gè)閾值,稍微檢測(cè)一下,應(yīng)該就可以了吧?繞過呢。。。

異常Hook

異常Hook,顧名思義,就是弄個(gè)異常,然后在異常處理的時(shí)候執(zhí)行我們的功能函數(shù)。程序在運(yùn)行時(shí),如果遇到異常,系統(tǒng)會(huì)使用程序已注冊(cè)的異常處理函數(shù)去處理拋出來的異常。那么各指令集對(duì)應(yīng)的異常指令如下:

異常指令

具體實(shí)施,就是注入后,把你要Hook的地址的指令給改成異常指令,然后注冊(cè)你的異常處理函數(shù),在異常處理時(shí),修復(fù)異常指令,執(zhí)行自己的功能函數(shù)。這里有個(gè)問題不知道大家發(fā)現(xiàn)沒,就是異常指令被修復(fù)后,下次執(zhí)行到這里的時(shí)候,就沒辦法觸發(fā)我們的異常處理函數(shù)了,導(dǎo)致我們的Hook掛上之后執(zhí)行一次就掉了。怎么確保每次執(zhí)行到呢?我們需要在異常處理函數(shù)里將接下來要執(zhí)行的那行指令也替換成異常指令,并且在第二處異常觸發(fā)的異常處理函數(shù)里將第一處被修復(fù)的異常指令再次替換成異常指令,并且再修復(fù)第二處異常指令就ok了(好拗口)。

問題來了,怎么防?檢測(cè)異常代碼?異常處理函數(shù)白名單? 自定義異常處理函數(shù)陷阱?

0x3 叁

Android上最常見的Hook方式應(yīng)該屬于Xposed,它是針對(duì)java方法的一個(gè)Hook方式。簡(jiǎn)單說,Xposed的原理就是將一個(gè)java方法給修改成Native方法,方法對(duì)應(yīng)Method結(jié)構(gòu)體里的insns指針就指向的是你在Xposed里注冊(cè)的功能函數(shù),如果還要執(zhí)行原始方法,Xposed會(huì)在insns里的代碼執(zhí)行結(jié)束后再去反射調(diào)用原始的方法。

Method結(jié)構(gòu)體insns指針
DVM設(shè)置NativeFunction

怎么防?檢測(cè)方法的屬性,是否該為Java方法的變成了Native的;反射獲取Xposed的一些屬性,檢測(cè)注冊(cè)了關(guān)于你的函數(shù)的鉤子;more。

慢慢更~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容