盤點(diǎn)Android常用Hook技術(shù)
Android平臺開發(fā)測試過程中,Hook技術(shù)是每個開發(fā)人員都常用的技術(shù)。可以用于繞過系統(tǒng)限制、修改別人發(fā)布的代碼、動態(tài)化、調(diào)用隱藏API、插件化、組件化、自動化測試、沙箱等等。
Hook如果要跨進(jìn)程修改,則需先提權(quán)注入目標(biāo)進(jìn)程中。本文主要盤點(diǎn)已經(jīng)有Android進(jìn)程權(quán)限后去如何hook修改運(yùn)行時環(huán)境。例如:修改自己的進(jìn)程。
Hook相關(guān)技術(shù)名詞很多,如:Xposed、inline hook、GOT、Native hook等等,但是這些hook技術(shù)的適用范圍和優(yōu)缺點(diǎn),想必很多人還不能解釋的清楚。本文盤點(diǎn)這些技術(shù)的適用范圍、優(yōu)缺點(diǎn)、及基本原理。
為方便閱讀,先給出結(jié)論如下表,后面再一一展開。

Android進(jìn)程結(jié)構(gòu)
要了解hook技術(shù),首先得了解Android進(jìn)程的基本結(jié)構(gòu),進(jìn)程中不同區(qū)域得用不同的hook技術(shù)去解決。

構(gòu)成如上圖,Android本質(zhì)是Linux系統(tǒng),Android進(jìn)程本質(zhì)就是一個Linux進(jìn)程。
進(jìn)程由外層到里拆解:
首先,最外層是一個linux進(jìn)程,動態(tài)鏈接了一堆動態(tài)鏈接庫so,其中有名如:libc標(biāo)準(zhǔn)C函數(shù)庫、webkit、openGL等。
Linux進(jìn)程中主要運(yùn)行的是ART/Davlik虛擬機(jī),虛擬機(jī)包含Classloader、對象管理(內(nèi)存管理)、線程調(diào)度等。虛擬機(jī)為JAVA提供運(yùn)行時環(huán)境。
如果應(yīng)用還包含navtive代碼,native代碼會和虛擬機(jī)一起在Linux進(jìn)程中運(yùn)行,兩者是平行的關(guān)系
虛擬機(jī)中,包含JAVA FrameWork和應(yīng)用JAVA代碼兩部分。
Android進(jìn)程和其他進(jìn)程交換數(shù)據(jù),依賴于linux內(nèi)核提供進(jìn)程通信接口(驅(qū)動),如:Binder通信、Socket通信等等
hook就是去修改Android進(jìn)程中的這些組件,達(dá)到某預(yù)期目的的過程。
Android進(jìn)程Hook:
反射/動態(tài)代理?
如圖中A點(diǎn),作用于Java層。反射/動態(tài)代理是虛擬機(jī)提供的標(biāo)準(zhǔn)編程接口,可靠性較高。反射API可以幫助我們們訪問到private屬性并修改,動態(tài)代理可以直接從Interface中動態(tài)的構(gòu)造出代理對象,并去監(jiān)控這個對象。
常見的用法是,用動態(tài)代理構(gòu)造出一個代理對象,然后用反射API去替換進(jìn)程中的對象,從而達(dá)到hook的目的。如:對Java Framework API的修改常用這種方法,修改ActivityThread、修當(dāng)前進(jìn)程的系統(tǒng)調(diào)用等。
缺點(diǎn):只在java層,只能通過替換對象達(dá)到目的,適用范圍較小
優(yōu)點(diǎn):穩(wěn)定性好,調(diào)用反射和動態(tài)代理并不存在適配問題,技術(shù)門檻低
JNI Hook
如圖中B點(diǎn),java代碼和native之間的調(diào)用是通過JNI接口調(diào)用的,所有JNI接口的函數(shù)指針都會被保存在虛擬機(jī)的一張表中。所以,java和native之間調(diào)用可以通過修改函數(shù)指針達(dá)到。
優(yōu)點(diǎn):穩(wěn)定性高
缺點(diǎn):只能hook Java和Native之間的native接口函數(shù)
ClassLoader
如圖中C點(diǎn),java代碼的執(zhí)行都是靠虛擬機(jī)的類加載器ClassLoader去加載,ClassLoader默認(rèn)的雙親委派機(jī)制保證了ClassLoader總是從父類優(yōu)先去加載java class。所以一類hook方案就是通過修改ClassLoader加載java class的Path路徑達(dá)到目的。常見的應(yīng)用場景有一些熱修復(fù)技術(shù)。
優(yōu)點(diǎn):穩(wěn)定性高
缺點(diǎn):需要提前編譯好修改后的class去替換,靈活性降低了
Xposed相關(guān)
如圖中D點(diǎn),這類hook技術(shù)的原理都是去修改ART/Dalvik虛擬機(jī),虛擬機(jī)為java提供運(yùn)行時環(huán)境,所有的java method都保存在虛擬機(jī)一張Map維護(hù),每個Java Method都有個是否是JNI函數(shù)的標(biāo)志位,如果是JNI函數(shù)則去查找對應(yīng)的native函數(shù)。所以,一個hook方案是通過把要hook的函數(shù)修改為JNI函數(shù),然后實(shí)現(xiàn)一個對應(yīng)的native函數(shù)從而達(dá)到hook。
大量的一些自動化測試、動態(tài)調(diào)試都采用這個方法
優(yōu)點(diǎn):java層所有的class都可以修改,Activity等都可以注入。靈活性極高。
缺點(diǎn):ART/Dalvik每次Android系統(tǒng)發(fā)布大版本都會被大改,導(dǎo)致每個Android版本都要去適配配。穩(wěn)定性變差。
前面hook技術(shù)都是去修改虛擬機(jī)中的java層,如果一個應(yīng)用還包含Native code話,則得使用不同hook技術(shù)
GOT動態(tài)鏈接庫hook
如圖中E點(diǎn),Android進(jìn)程(linux進(jìn)程)加載動態(tài)鏈接庫的時候,都是通過dlopen()函數(shù)去把SO讀入到當(dāng)前進(jìn)程中的一個內(nèi)存區(qū)域中。當(dāng)調(diào)用so代碼時,直接跳轉(zhuǎn)到so的內(nèi)存區(qū)域去執(zhí)行。so對外提供的函數(shù)表及函數(shù)地址也都在這塊內(nèi)存中。所以,一個hook方法是,修改這塊函數(shù)地址,從而達(dá)到hook的目的。
例如native層的IO重定向,就可以通過這個技術(shù)hook libc的open read write等函數(shù)實(shí)現(xiàn)。
有大量的GOT注入庫可以使用
優(yōu)點(diǎn):所有so的入口函數(shù)都可以被hook,穩(wěn)定性高
缺點(diǎn):替換后的函數(shù)簽名要保持一致,只能hook so入口,無法hook so內(nèi)部代碼邏輯,且so的調(diào)用出現(xiàn)內(nèi)聯(lián)調(diào)用時(不查表直接跳函數(shù)地址)無法hook。
Inline hook
如圖中F點(diǎn),因?yàn)镚OT技術(shù)的局限性,有些場景需要hook so內(nèi)部函數(shù)。這就要用到inline hook技術(shù)?;驹硎窃谀繕?biāo)函數(shù)執(zhí)行區(qū)域中插入Jump指令,使得cpu跳轉(zhuǎn)到我們的hook函數(shù)(shellcode)中。如果我們的hook函數(shù)和原目標(biāo)函數(shù)的簽名不一致,還需額外保存寄存器信息,跳轉(zhuǎn)回原函數(shù)時恢復(fù)寄存器信息。inline hook原理雖簡單,但是實(shí)現(xiàn)起來需要處理的細(xì)節(jié)很多,因?yàn)槭侵苯尤ジ膕o,所以和指令平臺強(qiáng)相關(guān),armv7,armv8,arm64,x86, Thumb指令集都需要去針對性的實(shí)現(xiàn)。github中有大量的開源hook框架可參考使用,但穩(wěn)定性值得考究。
Android進(jìn)程通信hook
Android進(jìn)程和其他進(jìn)程交換數(shù)據(jù)主要依賴于linux內(nèi)核提供的進(jìn)程通信接口。如:socket、Binder等等。所以,還存在一類hook這些通信接口的技術(shù)方案。
Binder進(jìn)程通信hook

Binder進(jìn)程通信結(jié)構(gòu)如圖,是一種典型的Proxy代理接口。Client端通過Proxy向服務(wù)端Imp發(fā)送消息。Proxy和Imp實(shí)現(xiàn)同樣的Interface。所以Binder通信都是可以利用動態(tài)代理技術(shù)去替換Proxy或Imp來達(dá)到監(jiān)控Binder通信的目的。常見的如:hook AMS、WMS、IMS等服務(wù)。沙箱技術(shù)如VirtualApp、一些自動化監(jiān)測技術(shù)常常用到。且穩(wěn)定性較高
Socket通信hook
Socket通信提供幾個hook思路:
只hook Java層的調(diào)用,用Xposed類方案hook socket相關(guān)class就可做到
如果socket是client端,或者支持重新建立連接,某些場景可以構(gòu)造自己的socket去conect,從而達(dá)到hook的目的
native層則要用戶GOT hook
IO重定向
思路:
簡單的hook可以通過反射等手段修改Path達(dá)到
java層可以用xposed,但因xposed的緣故,穩(wěn)定性欠佳
通用的方案是用GOT hook Libc達(dá)到重定向,java層和native都可以解決
Hook技術(shù)使用心得
hook方案的選擇是一門學(xué)問,幾個心得原則是:
從簡,能用簡單方案解決的用簡單方案,切勿輕易增加復(fù)雜度。否則穩(wěn)定性和后期的維護(hù)都可能得不償失。
并不是越底層的方案越好,越底層的hook技術(shù)可能反而引入局限性。例如:Xposed修改所有的Activity很簡單,但是只修改某個Activity就變得復(fù)雜,因?yàn)闉榱硕ㄎ怀鲞@個特殊Activiy會引入一堆復(fù)雜度。
以上內(nèi)容來自工作中的總結(jié),如有描述的不對地方還請指正探討。每種技術(shù)的實(shí)現(xiàn)細(xì)節(jié),網(wǎng)上有大量資料,需深入的同學(xué)可自行搜索。
