Xposed 是一个强大的 Android 框架,允许开发者在不修改 APK 的情况下修改系统和应用程序的行为。它通过 Hook 技术拦截方法调用,实现对应用行为的修改。

1. 环境准备

  • 下载安装AndroidStudio
  • 获取XposedBridgeApi
  • 安装好xposed框架的设备

2. 创建Xposed插件项目

2.1 创建新项目

image.png

下一步,选择项目模板,一般插件不需要用户交互界面,所以这里选择“No Activity”,如果需要一些交互界面,选择适合的模板创建即可

image.png

进行必要的设置:

  • 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,它提供更好的代码补全和类型安全。

image.png

2.1 引入Xposed API依赖

在项目文件夹的app目录创建一个新的lib文件夹

image.png

复制xposedbridgeapi到lib目录

image.png

拷贝后,选择add as library

image.png

image.png

然后在build.gradle 中配置Xposed API 的依赖声明

compileOnly files('lib/XposedBridgeApi-82.jar')

image.png

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" />

image.png

2.4 入口类实现

创建一个实现 IXposedHookLoadPackage 接口的类:

image.png

image.png

接口类最基本的代码实现


// 声明包名,包名应与项目创建时设置的包名一致
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目录

image.png

然后在 assets目录下创建 xposed_init 文件,内容为入口类的完整路径:

image.png

fun.appvuln.firstxpmodule.MainHook

image.png

3. 编译运行插件

可以看到,一个最简单的xposed插件编译运行成功,当目标APP打开运行是,输出对应的日志信息。

image.png

4. Xposed 插件开发 常见API 用法

通过前面的学习,基本了解了Xposed插件开发的基本配置与流程,接下来就是掌握Xposed插件开发常见API的用法,实现综合运用解决实际需求的效果

4.1 修改静态变量

方法名参数功能
setStaticBooleanFieldClass<?> clazz, String fieldName, boolean value修改布尔类型静态字段
setStaticByteFieldClass<?> clazz, String fieldName, byte value修改字节类型静态字段
setStaticCharFieldClass<?> clazz, String fieldName, char value修改字符类型静态字段
setStaticIntFieldClass<?> clazz, String fieldName, int value修改整型静态字段
setStaticDoubleFieldClass<?> clazz, String fieldName, double value修改双精度浮点型静态字段
setStaticFloatFieldClass<?> clazz, String fieldName, float value修改单精度浮点型静态字段
setStaticLongFieldClass<?> clazz, String fieldName, long value修改长整型静态字段
setStaticObjectFieldClass<?> clazz, String fieldName, Object value修改对象类型静态字段
setStaticShortFieldClass<?> 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"。

image.png

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"));