Android Hook技术防范漫谈
背景
當(dāng)下,數(shù)據(jù)就像水、電、空氣一樣無(wú)處不在,說(shuō)它是“21世紀(jì)的生產(chǎn)資料”一點(diǎn)都不夸張,由此帶來(lái)的是,各行業(yè)對(duì)于數(shù)據(jù)的爭(zhēng)奪熱火朝天。隨著互聯(lián)網(wǎng)和數(shù)據(jù)的思維深入人心,一些灰色產(chǎn)業(yè)悄然興起,數(shù)據(jù)販子、爬蟲、外掛軟件等等也接踵而來(lái),互聯(lián)網(wǎng)行業(yè)中各公司競(jìng)爭(zhēng)對(duì)手之間不僅業(yè)務(wù)競(jìng)爭(zhēng)十分激烈,黑科技的比拼也越發(fā)重要。隨著移動(dòng)互聯(lián)網(wǎng)的興起,爬蟲和外掛也從單一的網(wǎng)頁(yè)轉(zhuǎn)向了App,其中利用Android平臺(tái)下Dalvik模式中的Xposed Installer和Cydia Substrate框架對(duì)App的函數(shù)進(jìn)行Hook這一招,堪稱老牌經(jīng)典。
接下來(lái),本文將分別介紹針對(duì)這兩種框架的防護(hù)技術(shù)。
Xposed Installer
原理
Zygote
在Android系統(tǒng)中App進(jìn)程都是由Zygote進(jìn)程“孵化”出來(lái)的。Zygote進(jìn)程在啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè)虛擬機(jī)實(shí)例,每當(dāng)它“孵化”一個(gè)新的應(yīng)用程序進(jìn)程時(shí),都會(huì)將這個(gè)Dalvik虛擬機(jī)實(shí)例復(fù)制到新的App進(jìn)程里面去,從而使每個(gè)App進(jìn)程都有一個(gè)獨(dú)立的Dalvik虛擬機(jī)實(shí)例。
Zygote進(jìn)程在啟動(dòng)的過程中,除了會(huì)創(chuàng)建一個(gè)虛擬機(jī)實(shí)例之外還會(huì)將Java Rumtime加載到進(jìn)程中并注冊(cè)一些Android核心類的JNI(Java Native Interface,Java本地接口)方法。一個(gè)App進(jìn)程被Zygote進(jìn)程孵化出來(lái)的時(shí)候,不僅會(huì)獲得Zygote進(jìn)程中的虛擬機(jī)實(shí)例拷貝,還會(huì)與Zygote進(jìn)程一起共享Java Rumtime,也就是可以將XposedBridge.jar這個(gè)Jar包加載到每一個(gè)Android App進(jìn)程中去。安裝Xposed Installer之后,系統(tǒng)app_process將被替換,然后利用Java的Reflection機(jī)制覆寫內(nèi)置方法,實(shí)現(xiàn)功能劫持。下面我們來(lái)看一下細(xì)節(jié)。
Hook和Replace
Xposed Installer框架中真正起作用的是對(duì)方法的Hook和Replace。在Android系統(tǒng)啟動(dòng)的時(shí)候,Zygote進(jìn)程加載XposedBridge.jar,將所有需要替換的Method通過JNI方法hookMethodNative指向Native方法xposedCallHandler,這個(gè)方法再通過調(diào)用handleHookedMethod這個(gè)Java方法來(lái)調(diào)用被劫持的方法轉(zhuǎn)入Hook邏輯。
上面提到的hookMethodNative是XposedBridge.jar中的私有的本地方法,它將一個(gè)方法對(duì)象作為傳入?yún)?shù)并修改Dalvik虛擬機(jī)中對(duì)于該方法的定義,把該方法的類型改變?yōu)镹ative并將其實(shí)現(xiàn)指向另外一個(gè)B方法。
換言之,當(dāng)調(diào)用那個(gè)被Hook的A方法時(shí),其實(shí)調(diào)用的是B方法,調(diào)用者是不知道的。在hookMethodNative的實(shí)現(xiàn)中,會(huì)調(diào)用XposedBridge.jar中的handleHookedMethod這個(gè)方法來(lái)傳遞參數(shù)。handleHookedMethod這個(gè)方法類似于一個(gè)統(tǒng)一調(diào)度的Dispatch例程,其對(duì)應(yīng)的底層的C++函數(shù)是xposedCallHandler。而handleHookedMethod實(shí)現(xiàn)里面會(huì)根據(jù)一個(gè)全局結(jié)構(gòu)hookedMethodCallbacks來(lái)選擇相應(yīng)的Hook函數(shù)并調(diào)用他們的before和after函數(shù),當(dāng)多模塊同時(shí)Hook一個(gè)方法的時(shí)候Xposed會(huì)自動(dòng)根據(jù)Module的優(yōu)先級(jí)來(lái)排序。
調(diào)用順序如下:A.before -> B.before -> original method -> B.after -> A.after。
檢測(cè)
在做Android App的安全防御中檢測(cè)點(diǎn)眾多,Xposed Installer檢測(cè)是必不可少的一環(huán)。對(duì)于Xposed框架的防御總體上分為兩層:Java層和Native層。
Java層檢測(cè)
需要說(shuō)明的是,Java層的檢測(cè)基本只能檢測(cè)出基礎(chǔ)的Xposed Installer框架,而不能防護(hù)其對(duì)App內(nèi)方法的Hook,如果框架中帶有反檢測(cè)則Java層檢測(cè)大多不起作用。
下面列出Java層的檢測(cè)點(diǎn),僅供參考。
① 通過PackageManager查看安裝列表
最簡(jiǎn)單的檢測(cè),我們調(diào)用Android提供的PackageManager的API來(lái)遍歷系統(tǒng)中App的安裝情況來(lái)辨別是否有安裝Xposed Installer相關(guān)的軟件包。
PackageManager packageManager = context.getPackageManager(); List applicationInfoList = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); for (ApplicationInfo applicationInfo: applicationInfoList) {if (applicationInfo.packageName.equals("de.robv.android.xposed.installer")) {// is Xposed TODO... }}通常情況下使用Xposed Installer框架都會(huì)屏蔽對(duì)其的檢測(cè),即Hook掉PackageManager的getInstalledApplications方法的返回值,以便過濾掉de.robv.android.xposed.installer來(lái)躲避這種檢測(cè)。
② 自造異常讀取棧
Xposed Installer框架對(duì)每個(gè)由Zygote孵化的App進(jìn)程都會(huì)介入,因此在程序方法異常棧中就會(huì)出現(xiàn)Xposed相關(guān)的“身影”,我們可以通過自造異常Catch來(lái)讀取異常堆棧的形式,用以檢查其中是否存在Xposed的調(diào)用方法。
try {throw new Exception("blah"); } catch(Exception e) {for (StackTraceElement stackTraceElement: e.getStackTrace()) {// stackTraceElement.getClassName() stackTraceElement.getMethodName() 是否存 在Xposed} } E/GEnvironment: no such table: preference (code 1): while compiling: SELECT keyguard_show_livewallpaper FROM preference ... at com.meituan.test.extpackage.ExtPackageManager.checkUpdate(ExtPackageManager.java:127) at com.meituan.test.MiFGService$1.run(MiFGService.java:41) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5072) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) ... at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609) at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132) //發(fā)現(xiàn)Xposed模塊 at dalvik.system.NativeStart.main(Native Method)③ 檢查關(guān)鍵Java方法被變?yōu)镹ative JNI方法
當(dāng)一個(gè)Android App中的Java方法被莫名其妙地變成了Native JNI方法,則非常有可能被Xposed Hook了。由此可得,檢查關(guān)鍵方法是不是變成Native JNI方法,也可以檢測(cè)是否被Hook。
通過反射調(diào)用Modifier.isNative(method.getModifiers())方法可以校驗(yàn)方法是不是Native JNI方法,Xposed同樣可以篡改isNative這個(gè)方法的返回值。
④ 反射讀取XposedHelper類字段
通過反射遍歷XposedHelper類中的fieldCache、methodCache、constructorCache變量,讀取HashMap緩存字段,如字段項(xiàng)的key中包含App中唯一或敏感方法等,即可認(rèn)為有Xposed注入。
boolean methodCache = CheckHook(clsXposedHelper, "methodCache", keyWord);private static boolean CheckHook(Object cls, String filedName, String str) {boolean result = false;String interName;Set keySet;try {Field filed = cls.getClass().getDeclaredField(filedName);filed.setAccessible(true);keySet = filed.get(cls)).keySet();if (!keySet.isEmpty()) {for (Object aKeySet: keySet) {interName = aKeySet.toString().toLowerCase();if (interName.contains("meituan") || interName.contains("dianping") ) {result = true;break;} }}...return result; }Native層檢測(cè)
由上文可知,無(wú)論在Java層做何種檢測(cè),Xposed都可以通過Hook相關(guān)的API并返回指定的結(jié)果來(lái)繞過檢測(cè),只要有方法就可以被Hook。如果僅在Java層檢測(cè)就顯得很徒勞,為了有效提搞檢測(cè)準(zhǔn)確率,就須做到Java和Native層同時(shí)檢測(cè)。每個(gè)App在系統(tǒng)中都有對(duì)應(yīng)的加載庫(kù)列表,這些加載庫(kù)列表在/proc/下對(duì)應(yīng)的pid/maps文件中描述,在Native層讀取/proc/self/maps文件不失為檢測(cè)Xposed Installer的有效辦法之一。由于Xposed Installer通常只能Hook Java層,因此在Native層使用C來(lái)解析/proc/self/maps文件,搜檢App自身加載的庫(kù)中是否存在XposedBridge.jar、相關(guān)的Dex、Jar和So庫(kù)等文件。
bool is_xposed() {bool rel = false;FILE *fp = NULL;char* filepath = "/proc/self/maps";...string xp_name = "XposedBridge.jar";fp = fopen(filepath,"r")) while (!feof(fp)) {fgets(strLine,BUFFER_SIZE,fp); origin_str = strLine;str = trim(origin_str);if (contain(str,xp_name)){rel = true; //檢測(cè)到Xposed模塊break;}}... }Cydia Substrate
原理
Cydia Substrate注入Hook的一個(gè)典型流程如下圖所示,在Java層配置注入的關(guān)鍵So庫(kù)libsubstrate.so和libsubstratedvm.so。考慮到Java層檢測(cè)強(qiáng)度太低,Substrate的檢測(cè)主要在Native層來(lái)實(shí)現(xiàn)。
檢測(cè)
動(dòng)態(tài)加載式檢測(cè)
讀取/proc/self/maps,列出了App中所有加載的文件。
上圖為Cydia Substrate在Android 4.4上注入后的進(jìn)程maps表,其中l(wèi)ibsubstrate.so和libsubstrate-dvm.so兩個(gè)文件為Substrate必載入文件。通過IDA Pro分析對(duì)其分析。
先來(lái)看libsubstrate-dvm.so的導(dǎo)出表,共有9個(gè)函數(shù)導(dǎo)出。
當(dāng)進(jìn)程maps表中出現(xiàn)libsubstrate-dvm.so,可以嘗試去load該so文件并調(diào)用MSJavaHookMethod方法,它會(huì)返回該方法的地址即判定為惡意模塊(第三方程序)。
void* lookup_symbol(char* libraryname,char* symbolname) {void *imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);if (imagehandle != NULL){void * sym = dlsym(imagehandle, symbolname);if (sym != NULL){return sym; //發(fā)現(xiàn)Cydia Substrate相關(guān)模塊}... }該方式基于載入庫(kù)文件的文件名或文件路徑和導(dǎo)出函數(shù)來(lái)判斷是否為惡意模塊,如果完全依賴此方式來(lái)判斷可能會(huì)誤判,但也不失為檢測(cè)方式的一個(gè)點(diǎn)。
基于方法特征碼檢測(cè)
特征碼即用來(lái)判斷某段數(shù)據(jù)屬于哪個(gè)計(jì)算機(jī)字段。在非Root環(huán)境下一般一個(gè)正常App在啟動(dòng)時(shí)候,系統(tǒng)會(huì)調(diào)度相關(guān)大小的內(nèi)存、空間給App使用,此時(shí)App的運(yùn)行環(huán)境內(nèi)產(chǎn)生的數(shù)據(jù)、內(nèi)存、存儲(chǔ)等是獨(dú)立于其它App的(即獨(dú)立運(yùn)行在沙箱中)。因?yàn)樘幱谶\(yùn)行沙箱環(huán)境中的進(jìn)程對(duì)沙箱的內(nèi)存有最高讀寫權(quán)限,當(dāng)我們的App進(jìn)程被惡意模塊附加或注入時(shí),就可以通過對(duì)當(dāng)前進(jìn)程的PID所對(duì)應(yīng)的maps中加載的模塊進(jìn)行合法校驗(yàn)。這里的模塊校驗(yàn)我們可以采取對(duì)單個(gè)模塊內(nèi)容取樣來(lái)判斷是否為惡意模塊,這種方式被定義為“基于方法的特征碼檢測(cè)”。
下面對(duì)一段程序段中OpcodeSample方法來(lái)提取特征碼。
方法原型:
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, fmt, ##args) void OpcodeSample(int a ,int b){ int c,d,e;c = a + b;d = a * b;e = a / b;LOGD("Hello It's c !%s\n", c); LOGD("Hello It's d !%s\n", d); LOGD("Hello It's e !%s\n", e); return; }通過IDA Pro對(duì)其分析。
左側(cè)紅色方框代表為OpcodeSample方法的操作碼,右邊為操作碼對(duì)應(yīng)ARM平臺(tái)的指令集。我們要在左側(cè)的操作碼中取出一段作為OpcodeSample的定位特征碼,選用__android_log_print方法調(diào)用指令集上下文,來(lái)確定特征碼。
第一次取樣:"03 20 31 46 42 46 FF F7 ?? EA"通過第一次取樣,查找結(jié)果有三處相似,再進(jìn)一步分析。這次我們加入一個(gè)常量取樣:
第二次取樣:"7E 44 ?? ?? F8 44 03 20 31 46 42 46 FF F7 ?? EA"繼而得出唯一特征碼,到此,我們對(duì)特征碼方法取樣有了初步的了解。下面來(lái)把它轉(zhuǎn)為實(shí)用的技能——?jiǎng)討B(tài)加載式檢測(cè)+特征碼結(jié)合。
我們對(duì)libsubstrate-dvm.so中導(dǎo)出函數(shù)MSJavaHookMethod來(lái)精準(zhǔn)定位。
IDA PRO導(dǎo)出函數(shù)表如圖:
第三次取樣:"55 57 56 53 E8 CC 14 ?? ?? 81 C3 DB ?? ?? ?? 8D 64 ?? ?? 8B 83 F4 ?? ?? ??"以上即為對(duì)Cydia Substrate的注入檢測(cè)識(shí)別,通過檢測(cè)/proc/self/maps下的加載so庫(kù)列表得到各個(gè)庫(kù)文件絕度路徑,通過fopen函數(shù)將so庫(kù)的內(nèi)容以16進(jìn)制讀進(jìn)來(lái)放在內(nèi)存里面進(jìn)行規(guī)則比對(duì),采用字符串模糊查找來(lái)檢測(cè)是否命中黑名單中的方法特征碼。
總結(jié)
在安全對(duì)抗領(lǐng)域,相比攻擊方,防守方歷來(lái)處于弱勢(shì)的一方。上文所提到的Xposed Installer和Cydia Substrate的檢測(cè)也僅僅是保障App安全的手段之一。App安全的防御不應(yīng)僅僅依賴于此,應(yīng)該構(gòu)建起整體的安全防御閉環(huán),盡可能在所有已知的可能攻擊點(diǎn)都追加檢測(cè),再配合代碼加固,將防御代碼隱藏。遺憾的是App防御代碼隱藏再深也終究會(huì)被破解,僅僅依賴于客戶端的防御顯然是不足的。移動(dòng)互聯(lián)網(wǎng)領(lǐng)域的整體安全防御應(yīng)該是走端云結(jié)合協(xié)作之道,共同防御,方能在攻防對(duì)抗中占據(jù)優(yōu)勢(shì)地位。
作者簡(jiǎn)介
- 禮贊,美團(tuán)安全工程師,2016年11月加入美團(tuán)。專注于二進(jìn)制、移動(dòng)端攻防相關(guān)工作,現(xiàn)負(fù)責(zé)美團(tuán)Android移動(dòng)安全組件的建設(shè)工作。
- 毅然,美團(tuán)技術(shù)專家,2016年初加入美團(tuán)。致力于美團(tuán)配送App組的Android App crash解決工作、Android App性能優(yōu)化、Android App反外掛、反爬蟲。目前主導(dǎo)負(fù)責(zé)美團(tuán)配送Android App移動(dòng)安全相關(guān)建設(shè)。
招聘信息
美團(tuán)集團(tuán)安全部正在招募Web&二進(jìn)制攻防、后臺(tái)&系統(tǒng)開發(fā)、機(jī)器學(xué)習(xí)&算法等各路小伙伴。
我們想做的事情:
構(gòu)建一套基于海量IDC環(huán)境下,橫跨網(wǎng)絡(luò)層、虛擬化層、Server 軟件層(內(nèi)核態(tài)/用戶態(tài))、語(yǔ)言執(zhí)行虛擬機(jī)層(JVM/Zend/JavaScript V8)、Web應(yīng)用層、數(shù)據(jù)訪問層(DAL)的,基于大數(shù)據(jù)+機(jī)器學(xué)習(xí)的全自動(dòng)人機(jī)識(shí)別與安全事件感知系統(tǒng)。規(guī)模上對(duì)應(yīng)美團(tuán)全線業(yè)務(wù)的服務(wù)器,技術(shù)棧覆蓋了幾乎大多數(shù)云環(huán)境下的互聯(lián)網(wǎng)應(yīng)用,數(shù)據(jù)規(guī)模也將是很大的挑戰(zhàn)。
此外我們還關(guān)注全球互聯(lián)網(wǎng)領(lǐng)域在企業(yè)安全建設(shè)方面的最佳實(shí)踐,努力構(gòu)建類似于Google的內(nèi)置式安全架構(gòu)和縱深防御體系,對(duì)在安全和工程技術(shù)領(lǐng)域有所追求的同學(xué)來(lái)說(shuō)應(yīng)該是一個(gè)很好的機(jī)會(huì)。
如果你想加入我們,歡迎將簡(jiǎn)歷發(fā)至郵箱zhaoyan17#meituan.com,具體職位信息點(diǎn)擊“崗位招聘”查看。
另外友情打個(gè)招聘:美團(tuán)配送App團(tuán)隊(duì),負(fù)責(zé)美團(tuán)騎手、美團(tuán)眾包、美團(tuán)跑腿等配送相關(guān)App的研發(fā),涉及技術(shù)領(lǐng)域包括但不限于App的穩(wěn)定性建設(shè)、App性能監(jiān)控和優(yōu)化、App動(dòng)態(tài)化。對(duì)上述領(lǐng)域感興趣的請(qǐng)聯(lián)系 yulei10#meituan.com 。
總結(jié)
以上是生活随笔為你收集整理的Android Hook技术防范漫谈的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简化Swagger使用的自制Starte
- 下一篇: 构建时预渲染:网页首帧优化实践