Xposed插件开发之旅(一):环境搭建与基础API
Xposed 是一个强大的 Android 框架,允许开发者在不修改 APK 的情况下修改系统和应用程序的行为。它通过 Hook 技术拦截方法调用,实现对应用行为的修改。
1. 环境准备
- 下载安装AndroidStudio
- 获取XposedBridgeApi
- 安装好xposed框架的设备
2. 创建Xposed插件项目
2.1 创建新项目
下一步,选择项目模板,一般插件不需要用户交互界面,所以这里选择“No Activity”,如果需要一些交互界面,选择适合的模板创建即可
进行必要的设置:
-
Name(名称):项目的名称,这将作为您的应用程序的标识符之一。
-
Package name(包名):应用的包名,是应用在 Android 系统中的唯一标识符。包名通常采用反向域名格式,确保在 Google Play 等应用商店中的唯一性。、
-
Save location(保存位置) :项目文件保存的本地路径。
-
Language(语言)选择的编程语言。
-
Minimum SDK(最低 SDK 版本)应用支持的最低 Android 版本。选择 API 24 意味着您的应用只能在 Android 7.0 及以上版本的设备上运行。
-
Build configuration language(构建配置语言):Groovy DSL (build.gradle) 用于编写 Gradle 构建脚本的语言。Groovy 是传统选择,另一个选项是 Kotlin DSL,它提供更好的代码补全和类型安全。
2.1 引入Xposed API依赖
在项目文件夹的app目录创建一个新的lib文件夹
复制xposedbridgeapi到lib目录
拷贝后,选择add as library
然后在build.gradle 中配置Xposed API 的依赖声明
compileOnly files('lib/XposedBridgeApi-82.jar')
2.3 配置AndroidManifest
在AndroidManifest.xml中添加 Xposed 模块的元数据
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="this is a xposed module" />
<meta-data
android:name="xposedminversion"
android:value="54" />
2.4 入口类实现
创建一个实现 IXposedHookLoadPackage
接口的类:
接口类最基本的代码实现
// 声明包名,包名应与项目创建时设置的包名一致
package fun.appvuln.firstxpmodule;
// 导入 Xposed 框架相关的类
// IXposedHookLoadPackage: Xposed 模块必须实现的接口之一,用于处理应用程序包加载事件
import de.robv.android.xposed.IXposedHookLoadPackage;
// XposedBridge: Xposed 框架的核心类,提供日志记录和 Hook 方法的功能
import de.robv.android.xposed.XposedBridge;
// XC_LoadPackage: 包含应用程序加载时的相关信息,如包名、类加载器等
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* MainHook 类 - Xposed 模块的主入口类
*
* 该类实现了 IXposedHookLoadPackage 接口,这是 Xposed 框架用于处理应用程序加载事件的主要接口。
* 当系统加载任何应用程序时,Xposed 框架会调用此类的 handleLoadPackage 方法。
*
* 注意:该类的完整路径必须在 assets/xposed_init 文件中指定,否则 Xposed 框架无法找到它。
*/
public class MainHook implements IXposedHookLoadPackage {
/**
* 当任何应用程序被加载时,Xposed 框架会调用此方法
*
* @param lpparam 包含被加载应用程序信息的参数对象,包括:
* - packageName: 应用程序的包名
* - processName: 应用程序的进程名
* - classLoader: 应用程序的类加载器,用于加载应用程序的类
* - appInfo: 应用程序的 ApplicationInfo 对象
* - isFirstApplication: 是否是首次加载此应用
* @throws Throwable 可能抛出的任何异常
*/
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
// 检查当前加载的应用程序包名是否为目标应用
// 如果不是目标应用,则立即返回,不执行后续代码
// 这是一种优化,避免对非目标应用执行不必要的操作
if (!lpparam.packageName.equals("com.dodonew.online")) return;
// 如果是目标应用,则在 Xposed 日志中记录信息
// XposedBridge.log 方法用于向 Xposed 的日志系统写入信息
// 这些日志可以通过 Xposed Installer 或其他 Xposed 管理应用查看
XposedBridge.log("Loaded app: " + lpparam.packageName);
// 在这里编写具体的 Hook 代码
// 例如,可以使用 XposedHelpers.findAndHookMethod 方法来 Hook 特定类的方法
// 或者使用 XposedHelpers.findClass 方法查找特定类,然后进行操作
}
}
main文件夹下创建assets目录
然后在 assets目录下创建 xposed_init 文件,内容为入口类的完整路径:
fun.appvuln.firstxpmodule.MainHook
3. 编译运行插件
可以看到,一个最简单的xposed插件编译运行成功,当目标APP打开运行是,输出对应的日志信息。
4. Xposed 插件开发 常见API 用法
通过前面的学习,基本了解了Xposed插件开发的基本配置与流程,接下来就是掌握Xposed插件开发常见API的用法,实现综合运用解决实际需求的效果
4.1 修改静态变量
方法名 | 参数 | 功能 |
---|---|---|
setStaticBooleanField | Class<?> clazz, String fieldName, boolean value | 修改布尔类型静态字段 |
setStaticByteField | Class<?> clazz, String fieldName, byte value | 修改字节类型静态字段 |
setStaticCharField | Class<?> clazz, String fieldName, char value | 修改字符类型静态字段 |
setStaticIntField | Class<?> clazz, String fieldName, int value | 修改整型静态字段 |
setStaticDoubleField | Class<?> clazz, String fieldName, double value | 修改双精度浮点型静态字段 |
setStaticFloatField | Class<?> clazz, String fieldName, float value | 修改单精度浮点型静态字段 |
setStaticLongField | Class<?> clazz, String fieldName, long value | 修改长整型静态字段 |
setStaticObjectField | Class<?> clazz, String fieldName, Object value | 修改对象类型静态字段 |
setStaticShortField | Class<?> clazz, String fieldName, short value | 修改短整型静态字段 |
这些方法的使用格式基本相同:
XposedHelpers.setStaticXXXField(目标类, "字段名", 新值);
获取目标类对应的clazz对象的方法:
Class<?> targeclass = XposedHelpers.findClass("com.example.app.Constants", lpparam.classLoader)
关于修改静态变量遇到的一个问题:
当静态变量被final关键字修饰时,编译器会将使用这些常量的地方直接替换为常量的字面值。
假设目标应用有这样的代码
// 在 Config 类中
public static final String BASE_DES_KEY = "65102933";
// 在 RequestUtil 类中
public static String encodeDesMap(String data, String key, String iv) {
// 如果方法内部有这样的代码:
if (key == null) {
key = Config.BASE_DES_KEY; // 这里在编译时已替换为 "65102933"
}
// ...加密逻辑
}
编译后,encodeDesMap 方法实际上变成了:
public static String encodeDesMap(String data, String key, String iv) {
// 编译后变成:
if (key == null) {
key = "65102933"; // 直接使用字面值,不再引用 Config.BASE_DES_KEY
}
// ...加密逻辑
}
所以即使我们修改了 BASE_DES_KEY 的值,encodeDesMap 方法仍然使用编译时嵌入的 "65102933"。
4.2 Hook构造函数
hook构造函数有以下2种写法,如果是无参构造函数省略参数部分即可。
public static XC_MethodHook.Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) {
throw new RuntimeException("Stub!");
}
public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
throw new RuntimeException("Stub!");
}
示例
目标方法:
public JsonRequest(Context context, int method, String url, String requestBody, Response.Listener<RequestResult<T>> listener, Response.ErrorListener errorListener, Type typeOfT){...}
如果不会参数类型转换,可以使用jadx生成xposed代码获取
Hook代码实现:
Class<?> target = XposedHelpers.findClass("com.dodonew.online.http.JsonRequest", lpparam.classLoader);
//hook构造方法一
XposedHelpers.findAndHookConstructor(target, "android.content.Context",int.class,String.class,String.class, "com.android.volley.Response$Listener", "com.android.volley.Response$ErrorListener", "java.lang.reflect.Type",new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("hook构造方法一 , 这是构造函数前");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("hook构造方法一 , 这是构造函数后");
}
});
//hook构造方法二
XposedHelpers.findAndHookConstructor("com.dodonew.online.http.JsonRequest", lpparam.classLoader,"android.content.Context",int.class,String.class,String.class, "com.android.volley.Response$Listener", "com.android.volley.Response$ErrorListener", "java.lang.reflect.Type", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("hook构造方法二 , 这是构造函数前");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("hook构造方法二 , 这是构造函数前");
}
});
4.3 Hook普通方法
和hook构造方法类似
//第一种方法要先获取Class对象
public static Class<?> findClass(String className, ClassLoader classLoader) {
throw new RuntimeException("Stub!");
}
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
throw new RuntimeException("Stub!");
}
//第二种方法
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
throw new RuntimeException("Stub!");
}
4.3 Hook自定义类参数
//方法一:
Class aniclazz = loadPackageParam.classLoader.loadClass("类名");
//方法二:
Class aniclazz = XposedHelpers.findClass("类名", loadPackageParam.classLoader);
//方法三:
Class aniclazz = Class.forName("类名",false,loadPackageParam.classLoader);
//方法四:
Class aniclazz = "类名";
4.4 替换函数实现
//hook替换函数
XposedHelpers.findAndHookMethod(
"com.dodonew.online.http.JsonRequest", //目标类
lpparam.classLoader,
"方法名",
String.class, //参数类型
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
//具体的代码实现
return null;
}
}
);
4.5 主动调用
public static Object callMethod(Object obj, String methodName, Object... args) {
throw new RuntimeException("Stub!");
}
public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) {
throw new RuntimeException("Stub!");
}
public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) {
throw new RuntimeException("Stub!");
}
public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) {
throw new RuntimeException("Stub!");
}
使用示例
//主动调用
Class<?> RequestUtil = XposedHelpers.findClass("com.dodonew.online.http.RequestUtil", lpparam.classLoader);
Object result = XposedHelpers.callMethod(RequestUtil.newInstance(),"encodeDesMap","66666","88888888","88888888");
XposedBridge.log("主动调用执行结果:"+result);
4.6 调用栈打印
XposedBridge.log("构造函数调用栈:"); XposedBridge.log(android.util.Log.getStackTraceString(new Throwable()));
//或者直接使用Log.e
Log.e("tag","调用栈信息:",new Throwable("Stack dump"));
- 感谢你赐予我前进的力量