介绍
frida 和 objection 对于App 的修改仅仅只是单次性的,App重启之后,我们就又需要手动使用 frida 或 objection 对 App 进行注入。不过我们有 Xposed 框架,基于此框架开发插件,则可以对 App 实现永久性修改。
原理
- ⼿机启动时init进程会启动所有Android进程的父进程——
Zygote(孵化)进程
。该进程的启动配置在/init.rc
脚本中,而Zygote进程对应的执行文件是/system/bin/app_process
,该文件完成类库的加载以及一些函数的调用工作。在Zygote进程创建后,再fork出SystemServer进程和其他进程。而Xposed Framework呢,就是用自己实现的app_process替换掉了系统原本提供的app_process,加载一个额外的jar包,然后入口从原来的com.android.internal.osZygoteInit.main()
被替换成了de.robv.android.xposed.XposedBridge.main()
,然后创建的Zygote进程就变成Hook的Zygote进程了,而后面Fork出来的进程也是被Hook过的。这个Jar包在/data/data/de.rbov.android.xposed.installer/bin/XposedBridge.jar
。 - Xposed_zygote进程启动后会初始化⼀些so⽂件(system/lib system/lib64),然后进⼊XposedBridge.jar中的XposedBridge.main中初始化jar包完成对⼀些关键Android系统函数的hook。
- Hook则是利⽤修改过的虚拟机将函数注册为native函数。
- 然后再返回zygote中完成原本zygote需要做的⼯作。
Xposed组成:
Xposed→C++音分,Xposed版的zygote,用于替换原生zygote,并为XposedBridge提供JNI方法,需由XposedInstaller在root后放到/system/bin目录下
XposedBridge→Java部分,编译后会生成一个jar包,负责在Native层与Framework层法行交互;
XposedInstaller→Xposed插件管理及功能控制的APP,包括启用、下载、禁用插仁等功能;
XposedTools→用于译Xposed及XposedBridge;
衍生品
太极Taichi
Edxposed
VA(virtual app)(2B)
平头哥
Riru: 提供了一种将代码注入到 zygote 进程的方法。
Zygisk: 是 Magisk 的一个模块,用于在系统启动时注入代码。
对比表格
特征/框架 Xposed Framework EdXposed Framework LSPosed Framework 开发者 rovo89 多个开发者维护 乐星 支持系统 Android 7 及之前版本 Android 8 到 Android 11 Androids 10 和 Android 11、12、13 状态 停更,最后一版支持 Android 7 可能陷入停更危机,目前未能支持 Android 12 停更 2021.2.15 最后一次更新 活跃,可能成为 EdXposed 的替代品 停更 2024.1.8日宣布 特点 通过修改系统的 Dalvik 运行时实现 作为 Magisk 模块的一部分,在 Magisk 环境中安装和管理 对 Android 10 和 11、12和13的更好支持和优化,更好的兼容性,可以与 Magisk 一起使用 基于 原创 Xposed Framework Xposed Framework 代际 第一代 第二代 第三代
安装
真机
基本条件:手机已经root并且已经刷入第三方recovery 注意fastboot版本匹配
首先下载并安装好XposedInstaller
然后下载和手机cpu对应的Framework,并放在手机存储卡上
然后重启到recovery
模式,刷入Framework
再重启即可
模拟器
下载并安装好XposedInstaller 3.1.5
去搬移:Xposed所有版本下载地址 - 吾爱破解 - 52pojie.cn下载对应的framework xposed-v89-sdk25-x86
文件不存在就创建一下,(先看存不存在再去推入)
去雷神模拟器下使用它的adb
1 | adb push xposed-v89-sdk25-x86.zip /sdcard/Android/data/de.robv.android.xposed.installer/cache/downloads/framework |
永久记住超级用户访问权限
xposed已激活
Xposed插件开发
初始化配置
Xposed 插件也是以 App 的形式安装在系统中的,只是区别于普通 App 的开发,Xposed 插件的开发还需要一些特别的配置。
我们选择创建一个No Activity
,语言选择Java
,SDK选默认的API 25
就可。
在 AndroidManifest.xml 中的 application 节点中增加如下 3 个meta-data属性,分别用于表示是不是 Xposed 模块、Xposed 模块的介绍以及支持最低的 Xposed 版本。
1
2
3
4
5
6
7
8
9
10
11
12<application
...>
<meta-data
android:name="xposedmodule"
android:value="true" /><!--是不是Xposed模块-->
<meta-data
android:name="xposeddescription"
android:value="这是一个Xposed模块" /> <!--Xposed模块介绍-->
<meta-data
android:name="xposedminversion"
android:value="82" /> <!--最低的Xposed版本-->
</application>在 App 工程的 settings.gradle 文件中进行如下添加,引入 API 语法提示。
1 | dependencyResolutionManagement { |
进入我们app目录下的build.gradle,引入xposed的依赖
1 | dependencies { |
还要在./app/src/main/res/values
目录下创建arrays.xml
,填入下面的内容,这一步主要是指定模块的作用域包名,效果就是在Lsposed中勾选作用域时会在应用下提示推荐应用。
1 | <resources> |
在 app/src/main ,新建一个assets文件夹。
目录下新建一个 xposed_init 文件用于指定 Xposed 模块入口类的完整类名。
在assets文件夹下新建文件xposed_init,文件类型选择text,文件内容填上你要新建的xposed类的名字。这个文件标记了你的xposed模块的入口。
模板:
对于在 xposed_init 文件中指定的 Hook 入口类,我们需要让该类实现 IXposedHookLoadPackage 接口,用于引入在安装 Xposed 框架的系统中。
1 | import de.robv.android.xposed.IXposedHookLoadPackage; |
由于每个 Zygote 孵化出来的 App 进程在启动时都会调用函数 handleLoadPackage(),此时如果想要 Hook 指定进程,就需要通过 handleLoadPackage 函数的参数 lpparam 进行过滤。lpparam 参数是一个 XC_LoadPackage.LoadPackageParam 类型的参数,它提供了一些有用的成员变量,用于表示应用进程的一些信息,其中主要成员类型信息如表:
成员变量类型 | 成员变量名 | 含义 |
---|---|---|
String | packageName | 包名 |
String | processName | 进程名 |
ClassLoader | classLoader | 类加载器 |
ApplicationInfo | appInfo | 应用的更多信息 |
因此我们想要 Hook 指定进程,就可以通过 packageName 成员变量来进行筛选,之后再对进程进行 Hook 操作,这就涉及到 XposedHelpers 类,它本质上是封装了反射函数。
findAndHookMethod 是用于寻找并 Hook 指定函数的函数,它有如下两个重载函数:
- findAndHookMethod(Class<?> clazz, String methodName, Object… parameterTypesAndCallback)
- findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object… parameterTypesAndCallback)
它们之间的区别仅在于类是否自动获取。值得一提的是,当存在重载函数时,可以额外在参数 methodName 后面添加参数的类型来指定某一重载函数的 Hook。
findAndHookMethod 函数中的参数 Callback 可以是一个抽象函数 XC_MethodHook,也可以是一个抽象函数XC_MethodReplacement。
在使用 XC_MethodHook 抽象函数时根据需求可实现如下抽象回调函数:
- beforeHookedMethod() :通常用于获取和修改目标函数的参数类型和值。
- afterHookedMethod():通常用于获取和修改目标函数的返回值。
在使用 XC_MethodReplacement 抽象函数时需要实现如下抽象回调函数:
- replaceHookedMethod():完全替换目标函数的功能。
常用API和写法
类反射
1 | Class clazz = XposedHelpers.findClass("类名" ,lpparam.classLoader) |
实例对象的获取
1 | XposedHelpers.newInstance(Class<?> clazz, Object... args) |
Hook 变量
静态变量Hook
1 | XposedHelpers.getStatic<type>Field(clazz, "变量名")//获取变量值 |
实例变量Hook
1 | XposedHelpers.get<type>Field(obj, "变量名")//获取obj对象中的指定变量的值 |
Hook 函数
1 | //Hook普通函数(包括匿名函数和内部类函数) |
主动调用
静态函数调用
1 | //调用clazz类中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数 |
实例函数调用
1 | //调用obj对象中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数 |
Hook加固App的真实逻辑
1 | Class activityThreadClass = XposedHelpers.findClass("android.app.ActivityThread", lpparam.classLoader); |
Hook multiDex方法
某些 App 对应的APK中可能是多dex的形式,以下是如何hook某一dex中的函数的代码:
1 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { |
遍历所有类下的所有方法
首先需要 Hook loadClass() 方法获取反射得到的类,然后通过类的 getDeclaredMethods() 方法获取到类中的所有函数,接着就可以对目标函数进行 Hook。
1 | XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() { |
日志输出
1 | XposedBridge.log("message"); |
检测和绕过
检测
Java层检测:
(1)通过PackageManager查看安装列表过滤
(2)自造异常读取堆栈
(3)检查关键字Java方法变成NativeJNI方法
(4)反射读取XposedHelper类字段
(5)检测方法是否被算改
(6)检测包名
native层检测:
(1)解析/proc/self/maps,搜检App自身加载的库中是否存在XposedBridge.jar、相关的Dex、Jar和so库等
(2)XposedCheck的实现参考
绕过
(1)Hook绕过
绕过jar Class检测
绕过堆栈检测
绕过包名检测
绕过jar文件检测
绕过maps检测
绕过vxp检测
绕过S0检测
绕过classPath检测
检测缓存
(2)定制源码绕过
修改Xposed源码特征
XposedInstaller、XposedBridge、Xposed、XposedTools全部修改指纹