背景
当下,数据就像水、电、空气一样无处不在,说它是“21 世纪的生产资料”一点都不夸张,由此带来的是,各行业对于数据的争夺热火朝天。随着互联网和数据的思维深入人心,一些灰色产业悄然兴起,数据贩子、爬虫、外挂软件等等也接踵而来,互联网行业中各公司竞争对手之间不仅业务竞争十分激烈,黑科技的比拼也越发重要。随着移动互联网的兴起,爬虫和外挂也从单一的网页转向了 App,其中利用 Android 平台下 Dalvik 模式中的Xposed Installer
和Cydia Substrate
框架对 App 的函数进行 Hook 这一招,堪称老牌经典。
接下来,本文将分别介绍针对这两种框架的防护技术。
Xposed Installer
原理
Zygote
在 Android 系统中 App 进程都是由 Zygote 进程“孵化”出来的。Zygote 进程在启动时会创建一个虚拟机实例,每当它“孵化”一个新的应用程序进程时,都会将这个 Dalvik 虚拟机实例复制到新的 App 进程里面去,从而使每个 App 进程都有一个独立的 Dalvik 虚拟机实例。
Zygote 进程在启动的过程中,除了会创建一个虚拟机实例之外还会将Java Rumtime
加载到进程中并注册一些 Android 核心类的 JNI(Java Native Interface,Java 本地接口)方法。一个 App 进程被 Zygote 进程孵化出来的时候,不仅会获得 Zygote 进程中的虚拟机实例拷贝,还会与 Zygote 进程一起共享Java Rumtime
,也就是可以将XposedBridge.jar
这个 Jar 包加载到每一个 Android App 进程中去。安装Xposed Installer
之后,系统app_process
将被替换,然后利用 Java 的Reflection
机制覆写内置方法,实现功能劫持。下面我们来看一下细节。
Hook 和 Replace
Xposed Installer
框架中真正起作用的是对方法的 Hook 和 Replace。在 Android 系统启动的时候,Zygote 进程加载XposedBridge.jar
,将所有需要替换的 Method 通过JNI
方法hookMethodNative
指向 Native 方法xposedCallHandler
,这个方法再通过调用handleHookedMethod
这个 Java 方法来调用被劫持的方法转入 Hook 逻辑。
上面提到的hookMethodNative
是XposedBridge.jar
中的私有的本地方法,它将一个方法对象作为传入参数并修改 Dalvik 虚拟机中对于该方法的定义,把该方法的类型改变为 Native 并将其实现指向另外一个 B 方法。
换言之,当调用那个被 Hook 的 A 方法时,其实调用的是 B 方法,调用者是不知道的。在 hookMethodNative 的实现中,会调用XposedBridge.jar
中的handleHookedMethod
这个方法来传递参数。handleHookedMethod
这个方法类似于一个统一调度的 Dispatch 例程,其对应的底层的 C++函数是xposedCallHandler
。而handleHookedMethod
实现里面会根据一个全局结构hookedMethodCallbacks
来选择相应的 Hook 函数并调用他们的before
和after
函数,当多模块同时 Hook 一个方法的时候Xposed
会自动根据Module
的优先级来排序。
调用顺序如下:A.before -> B.before -> original method -> B.after -> A.after。
检测
在做 Android App 的安全防御中检测点众多,Xposed Installer
检测是必不可少的一环。对于 Xposed 框架的防御总体上分为两层:Java 层和 Native 层。
Java 层检测
需要说明的是,Java 层的检测基本只能检测出基础的Xposed Installer
框架,而不能防护其对 App 内方法的 Hook,如果框架中带有反检测则 Java 层检测大多不起作用。
下面列出 Java 层的检测点,仅供参考。
① 通过 PackageManager 查看安装列表
最简单的检测,我们调用 Android 提供的PackageManager
的 API 来遍历系统中 App 的安装情况来辨别是否有安装Xposed Installer
相关的软件包。
通常情况下使用Xposed Installer
框架都会屏蔽对其的检测,即 Hook 掉PackageManager的getInstalledApplications
方法的返回值,以便过滤掉de.robv.android.xposed.installer
来躲避这种检测。
② 自造异常读取栈
Xposed Installer
框架对每个由 Zygote 孵化的 App 进程都会介入,因此在程序方法异常栈中就会出现Xposed
相关的“身影”,我们可以通过自造异常Catch
来读取异常堆栈的形式,用以检查其中是否存在Xposed
的调用方法。
③ 检查关键 Java 方法被变为 Native JNI 方法
当一个 Android App 中的Java
方法被莫名其妙地变成了Native JNI
方法,则非常有可能被Xposed Hook
了。由此可得,检查关键方法是不是变成Native JNI
方法,也可以检测是否被 Hook。
通过反射调用Modifier.isNative(method.getModifiers())
方法可以校验方法是不是Native JNI
方法,Xposed 同样可以篡改isNative
这个方法的返回值。
④ 反射读取 XposedHelper 类字段
通过反射遍历XposedHelper
类中的fieldCache
、methodCache
、constructorCache
变量,读取 HashMap 缓存字段,如字段项的 key 中包含 App 中唯一或敏感方法等,即可认为有Xposed
注入。
Native 层检测
由上文可知,无论在 Java 层做何种检测,Xposed 都可以通过 Hook 相关的 API 并返回指定的结果来绕过检测,只要有方法就可以被 Hook。如果仅在 Java 层检测就显得很徒劳,为了有效提搞检测准确率,就须做到 Java 和 Native 层同时检测。每个 App 在系统中都有对应的加载库列表,这些加载库列表在/proc/
下对应的pid/maps
文件中描述,在 Native 层读取/proc/self/maps
文件不失为检测 Xposed Installer 的有效办法之一。由于Xposed Installer
通常只能 Hook Java 层,因此在 Native 层使用 C 来解析/proc/self/maps
文件,搜检 App 自身加载的库中是否存在XposedBridge.jar
、相关的 Dex、Jar 和 So 库等文件。
Cydia Substrate
原理
Cydia Substrate
注入 Hook 的一个典型流程如下图所示,在 Java 层配置注入的关键 So 库libsubstrate.so
和libsubstratedvm.so
。考虑到 Java 层检测强度太低,Substrate 的检测主要在 Native 层来实现。
检测
动态加载式检测
读取/proc/self/maps
,列出了 App 中所有加载的文件。
上图为Cydia Substrate
在 Android 4.4 上注入后的进程 maps 表,其中libsubstrate.so
和libsubstrate-dvm.so
两个文件为 Substrate 必载入文件。通过IDA Pro
分析对其分析。
先来看libsubstrate-dvm.so
的导出表,共有 9 个函数导出。
当进程 maps 表中出现libsubstrate-dvm.so
,可以尝试去 load 该 so 文件并调用MSJavaHookMethod
方法,它会返回该方法的地址即判定为恶意模块(第三方程序)。
该方式基于载入库文件的文件名或文件路径和导出函数来判断是否为恶意模块,如果完全依赖此方式来判断可能会误判,但也不失为检测方式的一个点。
基于方法特征码检测
特征码即用来判断某段数据属于哪个计算机字段。在非 Root 环境下一般一个正常 App 在启动时候,系统会调度相关大小的内存、空间给 App 使用,此时 App 的运行环境内产生的数据、内存、存储等是独立于其它 App 的(即独立运行在沙箱中)。因为处于运行沙箱环境中的进程对沙箱的内存有最高读写权限,当我们的 App 进程被恶意模块附加或注入时,就可以通过对当前进程的 PID 所对应的 maps 中加载的模块进行合法校验。这里的模块校验我们可以采取对单个模块内容取样来判断是否为恶意模块,这种方式被定义为“基于方法的特征码检测”。
下面对一段程序段中OpcodeSample
方法来提取特征码。
方法原型:
通过IDA Pro
对其分析。
左侧红色方框代表为OpcodeSample
方法的操作码,右边为操作码对应 ARM 平台的指令集。我们要在左侧的操作码中取出一段作为OpcodeSample
的定位特征码,选用__android_log_print
方法调用指令集上下文,来确定特征码。
通过第一次取样,查找结果有三处相似,再进一步分析。这次我们加入一个常量取样:
继而得出唯一特征码,到此,我们对特征码方法取样有了初步的了解。下面来把它转为实用的技能——动态加载式检测+特征码结合。
我们对libsubstrate-dvm.so
中导出函数MSJavaHookMethod
来精准定位。
IDA PRO
导出函数表如图:
以上即为对Cydia Substrate
的注入检测识别,通过检测/proc/self/maps
下的加载so
库列表得到各个库文件绝度路径,通过fopen
函数将so
库的内容以 16 进制读进来放在内存里面进行规则比对,采用字符串模糊查找来检测是否命中黑名单中的方法特征码。
总结
在安全对抗领域,相比攻击方,防守方历来处于弱势的一方。上文所提到的Xposed Installer
和Cydia Substrate
的检测也仅仅是保障 App 安全的手段之一。App 安全的防御不应仅仅依赖于此,应该构建起整体的安全防御闭环,尽可能在所有已知的可能攻击点都追加检测,再配合代码加固,将防御代码隐藏。遗憾的是 App 防御代码隐藏再深也终究会被破解,仅仅依赖于客户端的防御显然是不足的。移动互联网领域的整体安全防御应该是走端云结合协作之道,共同防御,方能在攻防对抗中占据优势地位。
作者简介
礼赞,美团安全工程师,2016 年 11 月加入美团。专注于二进制、移动端攻防相关工作,现负责美团 Android 移动安全组件的建设工作。
毅然,美团技术专家,2016 年初加入美团。致力于美团配送 App 组的 Android App crash 解决工作、Android App 性能优化、Android App 反外挂、反爬虫。目前主导负责美团配送 Android App 移动安全相关建设。
评论