原文是英文,乌云上的是中文翻译,原文地址 Android Anti-Hooking Techniques in Java
0x00 前言
一个最近关于检测native hook框架的方法让我开始思考一个Android应用如何在Java层检测Cydia Substrate或者Xposed框架。
声明:下文所有的anti-hooking技巧很容易就可以被有经验的逆向人员绕过,这里只是展示几个检测的方法。在最近DexGuard和GuardIT等工具中还没有这类anti-hooking检测功能,不过我相信不久就会增加这个功能。
0x01 检测安装的应用
一个最直接的想法就是检测设备上有没有安装Substrate或者Xposed框架,可以直接调用PackageManager显示所有安装的应用,然后看是否安装了Substrate或者Xposed。
|
|
0x02 检查调用栈里的可疑方法
另一个想到的方法是检查Java调用栈里的可疑方法,主动抛出一个异常,然后打印方法的调用栈。代码如下:
|
|
当应用没有被hook的时候,正常的调用栈是这样的:
|
|
但是假如有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方法,调用栈就会如下:
|
|
下面看下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方法,调用栈就会如下:
|
|
在知道了调用栈的变化之后,就可以在Java层写代码进行检测:
|
|
0x03 检测并不应该native的native方法
Xposed框架会把hook的Java方法类型改为”native”,然后把原来的方法替换成自己的代码(调用hookedMethodCallback)。可以查看 XposedBridge_hookMethodNative的实现,是修改后app_process
里的方法。
利用Xposed改变hook方法的这个特性(Substrate也使用类似的原理),就可以用来检测是否被hook了。注意这不能用来检测ART运行时的Xposed,因为没必要把方法的类型改为native。
假设有下面这个方法:
|
|
如果getSecret方法被hook了,在运行的时候就会像下面的定义:
|
|
基于上面的原理,检测的步骤如下:
- 定位到应用的DEX文件
- 枚举所有的class
- 通过反射机制判断运行时不应该是native的方法
下面的Java展示了这个技巧。这里假设了应用本身没有通过JNI调用本地代码,大多数应用都不需要调用本地方法。不过如果有JNI调用的话,只需要把这些native方法添加到一个白名单中即可。理论上这个方法也可以用于检测Java库或者第三方库,不过需要把第三方库的native方法添加到一个白名单。检测代码如下:
|
|
0x04 通过/proc/[pid]/maps检测可疑的共享对象或者JAR
/proc/[pid]/maps记录了内存映射的区域和访问权限,首先查看Android应用的映像,第一列是起始地址和结束地址,第六列是映射文件的路径。
|
|
因此可以写代码检测加载到当前内存区域中的可疑文件:
|
|
Substrate会用到几个so:
|
|
Xposed会用到一个Jar:
|
|
0x05 绕过检测的方法
上面讨论了几个anti-hooking的方法,不过相信也会有人提出绕过的方法,这里对应每个检测方法如下:
- hook PackageManager的getInstalledApplications,把Xposed或者Substrate的包名去掉
- hook Exception的getStackTrace,把自己的方法去掉
- hook getModifiers,把flag改成看起来不是native
- hook 打开的文件的操作,返回/dev/null或者修改的map文件
0x06 总结
第三个方法中通过分析java方法的属性是否为 native 来判断是否被hook, 这中方式可以用来保护一些自定义的接口,或者SDK API 接口。
后来同事通过分析Xposed源码,发现通过ClassLoader可以识别出具体的Xposed module hook了某个具体的函数,下次说下分析思路。