Android Java層的anti-hooking技巧
原文:http://d3adend.org/blog/?p=589
0x00 前言
一個最近關於檢測native hook框架的方法讓我開始思考一個Android應用如何在Java層檢測Cydia Substrate或者Xposed框架。
宣告:
下文所有的anti-hooking技巧很容易就可以被有經驗的逆向人員繞過,這裡只是展示幾個檢測的方法。在最近DexGuard和GuardIT等工具中還沒有這類anti-hooking檢測功能,不過我相信不久就會增加這個功能。
0x01 檢測安裝的應用
一個最直接的想法就是檢測裝置上有沒有安裝Substrate或者Xposed框架,可以直接呼叫PackageManager顯示所有安裝的應用,然後看是否安裝了Substrate或者Xposed。
#!java
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")) {
Log.wtf("HookDetection", "Xposed found on the system.");
}
if(applicationInfo.packageName.equals("com.saurik.substrate")) {
Log.wtf("HookDetection", "Substrate found on the system.");
}
}
0x02 檢查呼叫棧裡的可疑方法
另一個想到的方法是檢查Java呼叫棧裡的可疑方法,主動丟擲一個異常,然後列印方法的呼叫棧。程式碼如下:
#!java
public class DoStuff {
public static String getSecret() {
try {
throw new Exception("blah");
}
catch(Exception e) {
for(StackTraceElement stackTraceElement : e.getStackTrace()) {
Log.wtf("HookDetection", stackTraceElement.getClassName() + "->" + stackTraceElement.getMethodName());
}
}
return "ChangeMePls!!!";
}
}
當應用沒有被hook的時候,正常的呼叫棧是這樣的:
#!bash
com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main
dalvik.system.NativeStart->main
但是假如有Xposed框架hook了com.example.hookdetection.DoStuff.getSecret方法,那麼呼叫棧會有2個變化:
- 在dalvik.system.NativeStart.main方法後出現de.robv.android.xposed.XposedBridge.main呼叫
- 如果Xposed hook了呼叫棧裡的一個方法,還會有de.robv.android.xposed.XposedBridge.handleHookedMethod 和de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative呼叫
所以如果hook了getSecret方法,呼叫棧就會如下:
#!bash
com.example.hookdetection.DoStuff->getSecret
de.robv.android.xposed.XposedBridge->invokeOriginalMethodNative
de.robv.android.xposed.XposedBridge->handleHookedMethod
com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main
de.robv.android.xposed.XposedBridge->main
dalvik.system.NativeStart->main
下面看下Substrate hook com.example.hookdetection.DoStuff.getSecret方法後,呼叫棧會有什麼變化:
- dalvik.system.NativeStart.main呼叫後會出現2次com.android.internal.os.ZygoteInit.main,而不是一次。
- 如果Substrate hook了呼叫棧裡的一個方法,還會出現com.saurik.substrate.MS$2.invoked,com.saurik.substrate.MS$MethodPointer.invoke還有跟Substrate擴充套件相關的方法(這裡是com.cigital.freak.Freak$1$1.invoked)。
所以如果hook了getSecret方法,呼叫棧就會如下:
#!bash
com.example.hookdetection.DoStuff->getSecret
com.saurik.substrate._MS$MethodPointer->invoke
com.saurik.substrate.MS$MethodPointer->invoke
com.cigital.freak.Freak$1$1->invoked
com.saurik.substrate.MS$2->invoked
com.example.hookdetection.DoStuff->getSecret
com.example.hookdetection.MainActivity->onCreate
android.app.Activity->performCreate
android.app.Instrumentation->callActivityOnCreate
android.app.ActivityThread->performLaunchActivity
android.app.ActivityThread->handleLaunchActivity
android.app.ActivityThread->access$800
android.app.ActivityThread$H->handleMessage
android.os.Handler->dispatchMessage
android.os.Looper->loop
android.app.ActivityThread->main
java.lang.reflect.Method->invokeNative
java.lang.reflect.Method->invoke
com.android.internal.os.ZygoteInit$MethodAndArgsCaller->run
com.android.internal.os.ZygoteInit->main
com.android.internal.os.ZygoteInit->main
dalvik.system.NativeStart->main
在知道了呼叫棧的變化之後,就可以在Java層寫程式碼進行檢測:
#!java
try {
throw new Exception("blah");
}
catch(Exception e) {
int zygoteInitCallCount = 0;
for(StackTraceElement stackTraceElement : e.getStackTrace()) {
if(stackTraceElement.getClassName().equals("com.android.internal.os.ZygoteInit")) {
zygoteInitCallCount++;
if(zygoteInitCallCount == 2) {
Log.wtf("HookDetection", "Substrate is active on the device.");
}
}
if(stackTraceElement.getClassName().equals("com.saurik.substrate.MS$2") &&
stackTraceElement.getMethodName().equals("invoked")) {
Log.wtf("HookDetection", "A method on the stack trace has been hooked using Substrate.");
}
if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") &&
stackTraceElement.getMethodName().equals("main")) {
Log.wtf("HookDetection", "Xposed is active on the device.");
}
if(stackTraceElement.getClassName().equals("de.robv.android.xposed.XposedBridge") &&
stackTraceElement.getMethodName().equals("handleHookedMethod")) {
Log.wtf("HookDetection", "A method on the stack trace has been hooked using Xposed.");
}
}
}
0x03 檢測並不應該native的native方法
Xposed框架會把hook的Java方法型別改為"native",然後把原來的方法替換成自己的程式碼(呼叫hookedMethodCallback)。可以檢視XposedBridge_hookMethodNative的實現,是修改後app_process裡的方法。
利用Xposed改變hook方法的這個特性(Substrate也使用類似的原理),就可以用來檢測是否被hook了。注意這不能用來檢測ART執行時的Xposed,因為沒必要把方法的型別改為native。
假設有下面這個方法:
#!java
public class DoStuff {
public static String getSecret() {
return "ChangeMePls!!!";
}
}
如果getSecret方法被hook了,在執行的時候就會像下面的定義:
#!java
public class DoStuff {
// calls hookedMethodCallback if hooked using Xposed
public native static String getSecret();
}
基於上面的原理,檢測的步驟如下:
- 定位到應用的DEX檔案
- 列舉所有的class
- 透過反射機制判斷執行時不應該是native的方法
下面的Java展示了這個技巧。這裡假設了應用本身沒有透過JNI呼叫原生程式碼,大多數應用都不需要呼叫本地方法。不過如果有JNI呼叫的話,只需要把這些native方法新增到一個白名單中即可。理論上這個方法也可以用於檢測Java庫或者第三方庫,不過需要把第三方庫的native方法新增到一個白名單。檢測程式碼如下:
#!java
for (ApplicationInfo applicationInfo : applicationInfoList) {
if (applicationInfo.processName.equals("com.example.hookdetection")) {
Set classes = new HashSet();
DexFile dex;
try {
dex = new DexFile(applicationInfo.sourceDir);
Enumeration entries = dex.entries();
while(entries.hasMoreElements()) {
String entry = entries.nextElement();
classes.add(entry);
}
dex.close();
}
catch (IOException e) {
Log.e("HookDetection", e.toString());
}
for(String className : classes) {
if(className.startsWith("com.example.hookdetection")) {
try {
Class clazz = HookDetection.class.forName(className);
for(Method method : clazz.getDeclaredMethods()) {
if(Modifier.isNative(method.getModifiers())){
Log.wtf("HookDetection", "Native function found (could be hooked by Substrate or Xposed): " + clazz.getCanonicalName() + "->" + method.getName());
}
}
}
catch(ClassNotFoundException e) {
Log.wtf("HookDetection", e.toString());
}
}
}
}
}
0x04 透過/proc/[pid]/maps檢測可疑的共享物件或者JAR
/proc/[pid]/maps記錄了記憶體對映的區域和訪問許可權,首先檢視Android應用的映像,第一列是起始地址和結束地址,第六列是對映檔案的路徑。
#!bash
#cat /proc/5584/maps
40027000-4002c000 r-xp 00000000 103:06 2114 /system/bin/app_process
4002c000-4002d000 r--p 00004000 103:06 2114 /system/bin/app_process
4002d000-4002e000 rw-p 00005000 103:06 2114 /system/bin/app_process
4002e000-4003d000 r-xp 00000000 103:06 246 /system/bin/linker
4003d000-4003e000 r--p 0000e000 103:06 246 /system/bin/linker
4003e000-4003f000 rw-p 0000f000 103:06 246 /system/bin/linker
4003f000-40042000 rw-p 00000000 00:00 0
40042000-40043000 r--p 00000000 00:00 0
40043000-40044000 rw-p 00000000 00:00 0
40044000-40047000 r-xp 00000000 103:06 1176 /system/lib/libNimsWrap.so
40047000-40048000 r--p 00002000 103:06 1176 /system/lib/libNimsWrap.so
40048000-40049000 rw-p 00003000 103:06 1176 /system/lib/libNimsWrap.so
40049000-40091000 r-xp 00000000 103:06 1237 /system/lib/libc.so
... Lots of other memory regions here ...
因此可以寫程式碼檢測載入到當前記憶體區域中的可疑檔案:
#!java
try {
Set libraries = new HashSet();
String mapsFilename = "/proc/" + android.os.Process.myPid() + "/maps";
BufferedReader reader = new BufferedReader(new FileReader(mapsFilename));
String line;
while((line = reader.readLine()) != null) {
if (line.endsWith(".so") || line.endsWith(".jar")) {
int n = line.lastIndexOf(" ");
libraries.add(line.substring(n + 1));
}
}
for (String library : libraries) {
if(library.contains("com.saurik.substrate")) {
Log.wtf("HookDetection", "Substrate shared object found: " + library);
}
if(library.contains("XposedBridge.jar")) {
Log.wtf("HookDetection", "Xposed JAR found: " + library);
}
}
reader.close();
}
catch (Exception e) {
Log.wtf("HookDetection", e.toString());
}
Substrate會用到幾個so:
#!bash
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidBootstrap0.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidCydia.cy.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libDalvikLoader.cy.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libsubstrate.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libsubstrate-dvm.so
Substrate shared object found: /data/app-lib/com.saurik.substrate-1/libAndroidLoader.so
Xposed會用到一個Jar:
#!bash
Xposed JAR found: /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar
0x05 繞過檢測的方法
上面討論了幾個anti-hooking的方法,不過相信也會有人提出繞過的方法,這裡對應每個檢測方法如下:
- hook PackageManager的getInstalledApplications,把Xposed或者Substrate的包名去掉
- hook Exception的getStackTrace,把自己的方法去掉
- hook getModifiers,把flag改成看起來不是native
- hook 開啟的檔案的操作,返回/dev/null或者修改的map檔案
相關文章
- 理解 Android Binder 機制(三):Java層2017-03-16AndroidJava
- Android訊息機制,從Java層到Native層剖析2019-02-22AndroidJava
- JAVA中Action層, Service層 ,model層 和 Dao層的功能區分2015-04-10Java
- Java的內層類和外層類(轉)2007-08-15Java
- 初識Frida--Android逆向之Java層hook (一)2018-06-11AndroidJavaHook
- 初識Frida--Android逆向之Java層hook (二)2018-06-12AndroidJavaHook
- Android 小技巧2020-08-11Android
- Ubuntu & Android 技巧2013-09-30UbuntuAndroid
- Java中的四層框架2024-08-15Java框架
- Delphi三層開發小技巧:TClientDataSet的Delta妙用2013-07-02client
- React Native 0.55.4 Android 原始碼分析(Java層原始碼解析)2018-09-12React NativeAndroid原始碼Java
- Android中Fiddler的使用技巧2017-12-28Android
- Android 提醒微技巧2016-10-21Android
- Android開發技巧2016-11-28Android
- Android小技巧(2)2015-01-30Android
- Android小技巧(1)2015-01-30Android
- Android小技巧(3)2015-04-13Android
- Android小技巧(4)2015-04-13Android
- Android小技巧(5)2015-04-13Android
- Binder Java層分析2018-08-26Java
- Android Framework層JNI的使用淺析2015-01-05AndroidFramework
- 使用Java API的5個技巧2017-10-07JavaAPI
- java中SimpleDateFormat的一點技巧2014-11-22JavaORM
- Android系統服務編寫例項-Binder(Java層AIDL)2018-06-14AndroidJavaAI
- android HAL層程式碼2019-07-19Android
- Android自定義遮罩層2018-11-28Android遮罩
- Java面試技巧分享2021-06-28Java面試
- Java深入-框架技巧2018-01-05Java框架
- Android 關於WebView的使用技巧小解2017-12-01AndroidWebView
- Android Studio 中的除錯技巧2017-11-12Android除錯
- Binder Java層的實現原理分析2019-01-26Java
- 多層代理下解決鏈路低延遲的技巧2020-08-19
- 惡意使用者識別?——Java 層反模擬器、反Hook、反多開技巧2018-01-30JavaHook
- Android全套動畫使用技巧2019-03-02Android動畫
- Android技巧拾取2014-02-26Android
- Android Bitmap實戰技巧2015-10-15Android
- 不同層級的Android開發者的不同行為2019-04-11Android
- RxCache 整合 Android 的持久層框架 greenDAO、Room2019-03-04Android框架OOM