一、Xposed介绍
1.1 引言
frida 和 objection 对于App 的修改仅仅只是单次性的,App重启之后,我们就又需要手动使用 frida 或 objection 对 App 进行注入。不过我们有 Xposed 框架,基于此框架开发插件,则可以对 App 实现永久性修改。
1.2 Xposed原理
Xposed 本质上是通过替换 Android 系统中的 zygote (或者说是 zygote 的进程文件 app_process)以及 libart.so 库,从而将 XposedBridge.jar 注入应用中,最终实现针对应用进程的 Hook 的。(毕竟 zygote 进程是万物之源)
二、Xposed插件开发
2.1 初始化配置
Xposed 插件也是以 App 的形式安装在系统中的,只是区别于普通 App 的开发,Xposed 插件的开发还需要一些特别的配置。
在 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" /> <meta-data android:name="xposeddescription" android:value="这是一个Xposed模块" /> <meta-data android:name="xposedminversion" android:value="82" /> </application>
|
在 App 工程的 settings.gradle 文件中进行如下添加,引入 API 语法提示。
1 2 3 4 5 6 7 8 9
| dependencyResolutionManagement { ... repositories { ... maven { url = uri("https://api.xposed.info/") } // 添加 } }
|
在App工程的 app/build.gradle 文件中的dependencies节点中加上以下依赖,并同步用于编写 Xposed 相关代码和执行 Hook 操作。
1 2 3 4 5
| dependencies { compileOnly("de.robv.android.xposed:api:82") //compileOnly("de.robv.android.xposed:api:82:sources")//此引入会导致无语法提示 ... }
|
在 app/src/main/assets 目录下新建一个 xposed_init 文件用于指定 Xposed 模块入口类的完整类名。
至此,将一个配置好的App安装到手机上,Xposed软件即可将相应的App识别为一个Xposed模块,如下图所示。
此时将图中的复选框勾选上并重启手机,即可使得xposed_init文件中指定的Hook入口类生效。
2.2 一个简单的功能开发
对于在 xposed_init 文件中指定的 Hook 入口类,我们需要让该类实现 IXposedHookLoadPackage 接口,用于引入在安装 Xposed 框架的系统中。
1 2 3 4 5 6
| public class HookTest implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { ... } }
|
由于每个 Zygote 孵化出来的 App 进程在启动时都会调用函数 handleLoadPackage(),此时如果想要 Hook 指定进程,就需要通过 handleLoadPackage 函数的参数 lpparam 进行过滤。lpparam 参数是一个 XC_LoadPackage.LoadPackageParam 类型的参数,它提供了一些有用的成员变量,用于表示应用进程的一些信息,其中主要成员类型信息如表:
成员变量类型 |
成员变量名 |
含义 |
String |
packageName |
包名 |
String |
processName |
进程名 |
ClassLoader |
classLoader |
类加载器 |
ApplicationInfo |
appInfo |
应用的更多信息 |
因此我们想要 Hook 指定进程,就可以通过 packageName 成员变量来进行筛选,之后再对进程进行 Hook 操作,这就涉及到 XposedHelpers 类,它本质上是封装了反射函数。
以下是 Hook 某函数并修改参数以及返回值的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.example.xposedtest;
import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookTest implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if(lpparam.packageName.equals("com.example.test")) { XposedHelpers.findAndHookMethod("com.example.test.Calculate", lpparam.classLoader,"add", new XC_MethodHook() {
@Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); Log.d("gal2xy", "参数修改之前 arg1: " + param.args[0]); Log.d("gal2xy", "参数修改之前 arg2: " + param.args[1]); param.args[0] = 1; param.args[1] = 2; Log.d("gal2xy", "参数修改之后 arg1: " + param.args[0]); Log.d("gal2xy", "参数修改之后 arg2: " + param.args[1]); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); param.setResult(9); } }); } } }
|
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和写法
3.1 类反射
1
| Class clazz = XposedHelpers.findClass("类名" ,lpparam.classLoader)
|
3.2 实例对象的获取
1 2 3
| 1. XposedHelpers.newInstance(Class<?> clazz, Object... args) 2. XposedHelpers.newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args) 3. 通过 3.3 Hook变量获取
|
3.3 Hook 变量
静态变量Hook
1 2
| XposedHelpers.getStatic<type>Field(clazz, "变量名") XposedHelpers.setStatic<type>Field(clazz, "变量名", value)
|
实例变量Hook
1 2
| XposedHelpers.get<type>Field(obj, "变量名") XposedHelpers.set<type>Field(obj, "变量名", value)
|
3.4 Hook 函数
1 2 3 4 5 6
| XposedHelpers.findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) XposedHelpers.findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
XposedHelpers.findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) XposedHelpers.findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback)
|
3.5 主动调用
静态函数调用
1 2 3
| XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Object... args); XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args)
|
实例函数调用
1 2 3
| XposedHelpers.callMethod(Object obj, String methodName, Object... args) XposedHelpers.callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args);
|
3.6 Hook加固App的真实逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Class activityThreadClass = XposedHelpers.findClass("android.app.ActivityThread", lpparam.classLoader); XposedHelpers.findAndHookMethod(activityThreadClass, "performLaunchActivity", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); Application mInitialApplication = (Application) XposedHelpers.getObjectField(param.thisObject, "mInitialApplication"); ClassLoader finalLoader = mInitialApplication.getClassLoader(); XposedBridge.log("found classloader => " + finalLoader.toString()); } });
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context context = (Context) param.args[0]; ClassLoader classLoader = context.getClassLoader(); } });
|
3.7 Hook multiDex方法
某些 App 对应的APK中可能是多dex的形式,以下是如何hook某一dex中的函数的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { ClassLoader cl = ((Context)param.args[0]).getClassLoader(); Class<?> hookclass = null; try { hookclass = cl.loadClass("类名"); }catch (Exception e){ XposedBridge.log("未找到类" + e.toString()); return; } XposedHelpers.findAndHookMethod(hookclass, "方法名", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { } }); } });
|
3.8 遍历所有类下的所有方法
首先需要 Hook loadClass() 方法获取反射得到的类,然后通过类的 getDeclaredMethods() 方法获取到类中的所有函数,接着就可以对目标函数进行 Hook。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); Class clazz = (Class) param.getResult(); String clazzName = clazz.getName(); if(clazzName.contains("类名")){ Method[] mds = clazz.getDeclaredMethods(); for(int i =0;i<mds.length;i++){ int mod = mds[i].getModifiers(); if(!Modifier.isAbstract(mod) && !Modifier.isNative(mod) &&!Modifier.isAbstract(mod)){ XposedBridge.hookMethod(mds[i], new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); } }); } } } } });
|
3.9 日志输出
1 2
| XposedBridge.log("message"); XposedBridge.log(new Exception());
|
四、Xposed的RPC调用
Xposed框架本身并未提供RPC调用方式的支持,但是基于Xposed模块在成为Hook插件前首先是一个普通的应用App,因此在其他普通Android App上的开发方式可以完美地移植到Xposed模块中。基于此,Xposed RPC的解决方案可以是在App内部搭建服务端(例如使用NanoHTTPD)。
参考:
hook框架-xposed安装使用-Android安全-看雪-安全社区|安全招聘|kanxue.com
从零开始编写Xposed模块 (lbqaq.top)
《安卓Frida逆向与协议分析》