Xposed插件开发

一、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 插件的开发还需要一些特别的配置。

  1. 在 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>
  2. 在 App 工程的 settings.gradle 文件中进行如下添加,引入 API 语法提示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    dependencyResolutionManagement {
    ...
    repositories {
    ...
    maven {
    url = uri("https://api.xposed.info/")
    } // 添加
    }
    }
  3. 在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")//此引入会导致无语法提示
    ...
    }
  4. 在 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")) {// 过滤出指定应用
// hook com.example.test.Calculate 类中的 add 方法
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);//设置返回值始终为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, "变量名")//获取obj对象中的指定变量的值
    XposedHelpers.set<type>Field(obj, "变量名", value)//修改obj对象中的指定变量的值

3.4 Hook 函数

1
2
3
4
5
6
//Hook普通函数(包括匿名函数和内部类函数)
XposedHelpers.findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback)
XposedHelpers.findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback)
//Hook构造函数
XposedHelpers.findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback)
XposedHelpers.findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback)

3.5 主动调用

  • 静态函数调用

    1
    2
    3
    //调用clazz类中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数
    XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Object... args);
    XposedHelpers.callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args)
  • 实例函数调用

    1
    2
    3
    //调用obj对象中的methodName方法,如果有参数则追加参数值,如果methodName方法是重载方法,则追加参数类型来指定目标函数
    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 {
//do something
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//do something
}
});
}
});

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++){
//获取方法的修饰符(pulic, static, final等)
int mod = mds[i].getModifiers();
//去除抽象、native、接口方法
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);
//do something
}
});
}

}
}

}
});

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逆向与协议分析》


Xposed插件开发
http://example.com/2024/07/29/Android安全/Xposed插件开发/
作者
gla2xy
发布于
2024年7月29日
许可协议