Hook的目的是為了對目標進程函數的替換和注入,Hook的危害是巨大的,Hook后的應用程序毫無安全可言。其實,自從PC時代起,Hook與反Hook一直就是一個曠日持久的戰爭。那么對于剛發展不久的Android操作系統安全方向而言,Hook的檢測與修復無疑是給Android安全研究人員帶來了巨大的挑戰。本節我們就具體地看看,就Android操作系統而言,如何檢測一個進程是否被Hook了,如何修復被Hook的進程消除其安全隱患。
#### **Hook檢測**
上面演示了很多的Hook例子,Hook后的應用程序注入與劫持危害是不可估量的。所以,如何識別應用程序被Hook了,如何去除Hook程序也成為了難題。我們先從Hook的原理上來分析,Hook就是在一個目標進程中通過改變函數方法的指向地址,加入一段自定義的代碼塊。那么,加入一些非本進程的代碼邏輯,進程會不會產生一些改變?帶著疑問我們直接使用本章之前在瀏覽器中注入廣告的例子查看一下。
**1.Java層Hook檢測**
首先我們使用ps命令查看一下瀏覽器應用(包名為:com.android.browser)的進程pid,在adb shell模式下輸入ps | busybox grep com.android.browser(busybox是一個擴展的命令工具),如圖8-28所示。
:-: 
我們這里看見瀏覽器應用對應的進程pid為5425。
熟悉Android操作系統的朋友應該清楚,Android操作系統繼承了Linux操作系統的優點,有一個虛擬文件系統也就是我們常訪問的/proc目錄,通過它可以使用一種新的方法在Android內核空間和用戶空間之間進行通信,即我們能夠看到當前進程的一些狀態信息。而其中的maps文件又能查看進程的虛擬地址空間是如何使用的。
現在思路已經很清晰了,我們使用命令:
~~~
cat /proc/5425/maps | busybox grep /data/dalvik-cache/data@app
~~~
查看地址空間中的對應的dex文件有哪些是非系統應用提供的(瀏覽器是系統應用),即過濾出/data@app(系統應用是/system@app)中的,如圖8-29所示。
:-: 
對應地輸出了Dalvik虛擬機載入的非系統應用的dex文件,如圖8-30所示。
:-: 
在圖 8-30 中我們清楚地看到,該進程確實被附加了很多非系統的 dex 文件,如 hookad-1. apk@classes.dex、loginhook-2.apk@classes.dex、substrate-1.apk@classes.dex等。如果沒有上面的Hook演示,這里我們很難確定這些被附加的代碼邏輯是做什么的,當然肯定也不是做什么好事。所以,我們得出結論,此應用已經被Hook,存在安全隱患。
**2.native層Hook檢測**
上面演示了如何檢測Java層應用是否被Hook了,對于native層的Hook檢測其實原理也是一樣的。這里我們對之前的演示的Hook后替換指定應用中的原函數例子做檢測(包名為:com.example.testndklib),我們使用
`ps | busybox grep com.example.testndklib`,如圖8-31所示。
:-: 
得到其對應的進程pid為15097,我們直接查看15097進程中的虛擬地址空間加載了哪些第三方的庫文件,即過濾處/data/data目錄中的,具體命令如圖8-32所示。
:-: 
得到的輸出結果如圖8-33所示,發現其中多了很多的/com.saurik.substrate下面的動態庫,說明該進程也已經被其注入了。所以我們判斷com.example.testndklib應用程序已經被Hook,存在不安全的隱患。
:-: 
同樣的方式,我們能夠查看到Zygote進程的運行情況,發現也是被注入了CydiaSubstrate框架的很多so庫文件,如圖8-34所示。
作為應用程序對自身的檢測,也只需要讀取對應的進程的虛擬地址空間目錄/proc/pid/maps文件,判斷當前進程空間中載入的代碼庫文件是否存在于自己白名單中的,即可判斷自身程序是否被Hook。但是,對于zygote進程來說如果沒有Root權限,我們是無法訪問其maps文件的,那么也就無法判斷Hook與否了。
:-: 
#### **Hook修復**
如何判斷一個進程是否被其他第三方函數庫Hook,我們已經知道了。為了讓我們的應用程序能夠在一個安全可靠的環境中運行,那么我們就必須將這些不速之客從應用程序的進程中剝離出去。
如上面我們演示的testndklib應用程序,我們在adb shell命令模式下查看其進程pid為30210,并根據進程pid查看其對應的進程虛擬地址空間。具體命令如圖8-35所示。
:-: 
從系統返回的具體結果中我們發現,已經被很多的第三方 Hook 庫所加載,這里都是以/com.saurik.substrate開頭的substrate框架的動態庫,如圖8-36所示。
:-: 
當然,我們希望除了自身包名(com.example.testndklib)下的其他動態鏈接全都給刪除關閉,且關閉后的應用程序還能夠正常地運行。因為所有的第三方庫都是通過dlopen后注入的方式附加到應用程序進程中的,這里我們很容易想到我們直接使用 dlclose 將其中的第三方函數挨個卸載關閉即可。
這樣一個程序思路就來了,首先掃描/proc//maps目錄下的所有so庫文件,并將自身的動態庫文件排除,對于非自身的動態鏈接庫我們全都卸載關閉。對于Java我們無法使用dlclose,所以這里我們還是采用了JNI的方式來完成,具體的操作函數如下所示。
~~~
/**
* 根據包名與進程pid,刪除非包名下的動態庫
* @parampid 進程pid
* @parampkg 包名
* @return
*/
public List<String> removeHooks(intpid, String pkg) {
List<String> hookLibFile = new ArrayList<>();
// 找到對應進程的虛擬地址空間文件
File file = newFile("/proc/" + pid + "/maps");
if(!file.exists()) {
return hookLibFile;
}
try{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader
(newFileInputStream(file)));
String lineString = null;
while((lineString = bufferedReader.readLine()) != null) {
String tempString = lineString.trim();
// 被hook注入的so動態庫
if(tempString.contains("/data/data") && !tempString.contains("/data/data/" + pkg)) {
int index = tempString.indexOf("/data/data");
String soPath = tempString.substring(index);
hookLibFile.add(soPath);
// 調用native方法刪除so動態庫
removeHookSo(soPath);
}
}
bufferedReader.close();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
return hookLibFile;
}
/**
* 卸載加載的so庫
* @paramsoPath so庫地址路徑
*/
public native void removeHookSo(String soPath);
// JNI中的removeHookSo卸載一個so的加載
void Java_com_example_testndklib_MainActivity_removeHookSo(JNIEnv* env,
jobject thiz, jstring path) {
const char* chars = env->GetStringUTFChars(path, 0);
void* handle = dlopen(chars, RTLD_NOW);
int count = 4;
int i = 0;
for(i = 0; i < count; i++) {
if(NULL != handle) {
dlclose(handle);
}
}
}
~~~
在需要卸載的應用程序中調用removeHooks(Process.myPid(), getPackageName())就能夠輕松地完成上述的功能。那么是否所有的動態庫都被卸載移除了?我們重新查看該應用程序的虛擬地址空間,得到結果如圖8-37所示。
:-: 
比較后大家都會發現,雖然卸載掉了大部分的so動態鏈接庫,但是還是殘余了少許沒有被卸載干凈,如我們這里剩余的libAndroidBootstrap0.so庫還是依然在加載中。對于dlclose函數讀者們應該都清楚,dlclose用于關閉指定句柄的動態鏈接庫 ,只有當此動態鏈接庫的使用計數為0時,才會真正被系統卸載。也就是說如果我們手動卸載動態鏈接庫之前,系統已經保持對其的應用的話,我們是無法卸載的。
Hook框架的動態庫什么時候加載如何加載我們都不能夠得知,所以對非本包的動態鏈接庫卸載也需要實時監測去卸載。且就算卸載也不能夠完全地保證系統就沒有對相關函數的引用,達到卸載干凈的目的。所以,我們得出結論,對于Hook后的應用程序修復在目前來說是一項暫無解決方案的工作。
#### **Hook檢測小結**
說到如何識別一個應用程序是否被Hook、修復Hook,我們會發現因為Android操作系統上沙箱(Sandbox)機制的存在,不管我們采用何種方案手段都沒有辦法完全避免程序被Hook。這個時候我們就需要將我們的目光轉到如何防止應用程序被Hook,預防于未然才是主要的解決方案。
#### **參考文章:**
[《Android安全技術揭秘與防范》—第8章8.5節Hook檢測/修復](https://yq.aliyun.com/articles/99886?spm=5176.100239.blogcont99909.22.6fd616140gXvb7)
- 前言
- Android系統的體系結構
- Dalvik VM 和 JVM 的比較
- Android 打包應用程序并安裝的過程
- Android ADB工具
- Android應用開發
- Android UI相關知識總結
- Android 中window 、view、 Activity的關系
- Android應用界面
- Android中的drawable和bitmap
- AndroidUI組件adapterView及其子類和Adapter的關系
- Android四大組件
- Android 數據存儲
- SharedPreference
- Android應用的資源
- 數組資源
- 使用Drawable資源
- Material Design
- Android 進程和線程
- 進程
- 線程
- Android Application類的介紹
- 意圖(Intent)
- Intent 和 Intent 過濾器(Google官網介紹)
- Android中關于任務棧的總結
- 任務和返回棧(官網譯文)
- 總結
- Android應用安全現狀與解決方案
- Android 安全開發
- HTTPS
- 安卓 代碼混淆與打包
- 動態注入技術(hook技術)
- 一、什么是hook技術
- 二、常用的Hook 工具
- Xposed源碼剖析——概述
- Xposed源碼剖析——app_process作用詳解
- Xposed源碼剖析——Xposed初始化
- Xposed源碼剖析——hook具體實現
- 無需Root也能Hook?——Depoxsed框架演示
- 三、HookAndroid應用
- 四、Hook原生應用程序
- 五、Hook 檢測/修復
- Android 應用的逆向與加固保護技術
- OpenCV在Android中的開發
- Android高級開發進階
- 高級UI
- UI繪制流程及原理
- Android新布局ConstraintLayout約束布局
- 關鍵幀動畫
- 幀動畫共享元素變換
- Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解
- Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
- 為什么 Android 要采用 Binder 作為 IPC 機制?
- JVM 中一個線程的 Java 棧和寄存器中分別放的是什么?
- Android源碼的Binder權限是如何控制?
- 如何詳解 Activity 的生命周期?
- 為什么Android的Handler采用管道而不使用Binder?
- ThreadLocal,你真的懂了嗎?
- Android屏幕刷新機制