当前位置: 首页 > 工具软件 > Xposed > 使用案例 >

Xposed 插件开发之二: Xposed的一些知识

吴建中
2023-12-01

上一篇《Xposed 插件开发之一: Xposed入门》

一、 Api说明

IXposedHookLoadPackage

加载回调接口,在xposed入口类继承,实现handleLoadPackage(XC_LoadPackage.LoadPackageParam) ,这个方法用于在打开app的时候回调,参数LoadPackageParam包含了加载的应用程序的一些基本信息。

IXposedHookInitPackageResources

加载回调接口,用于修改app的资源文件,在xposed入口类继承,实现handleInitPackageResources(InitPackageResourcesParam)方法,用于在加载应用程序的包的时候执行用户的操作 ,参数InitPackageResourcesParam包含了加载的应用程序的一些资源基本信息。

IXposedHookZygoteInit

加载回调接口,在xposed入口类继承,实现initZygote(IXposedHookZygoteInit.StartupParam) ,这个方法用于在开始hookApp的时候回调,参数StartupParam包含了模块app的路径。

XposedHelpers.java

Xposed功能辅助类,里面提供了一些辅助方法,简化连接和调用方法/构造函数,获取和设置字段,这里直说重要的方法

  • findAndHookMethod: hook类中的某个方法
    参数:
    className: 要hook的方法的所在类
    classloader: 要hook的包的classLoader,一般都写loadPackageParam.classLoader
    methodName: 要hook的方法
    parameterTypesAndCallback: 方法的参数和监听器。

  • callMethod: 在目标app中调用方法
    参数:
    Object: 要调用方法的所在类
    methodName: 要调用的方法名称
    args: 方法的参数

  • findClass: 获取class类实例
    参数:
    className: 类名
    classLoader: 类加载器

XposedBridge.java

  • log:在Xposed的app的日志功能里输出日志和/data/xposed/debug.log 这个文件中
    参数
    text: 要输出的内容

二、 Hook过程

Xposed 框架中真正起作用的是对方法的hook,hook到之后,在方法之前之后进行修改添加或者替换,那么寻找正确的目标方法名和所在的类名就很关键了。一般寻找目标方法有两种方法:

  • 开源的直接查看源码,主要是系统应用
  • 反编译查看源码,常见的有apkTool,jadx,jeb

1. Find

通过特定的类加载器加载要hook的类,通过反射找到被hook的成员。工具类XposedHelpers提供了一些工具方法来简化find过程;XposedBridge的hook*方法用于处理hook并执行回调。

XposedHelpers静态方法描述
findClass使用classLoader加载class
findField通过反射查找类的数据成员并设置可访问性(setAccessible(true))
findMethod通过反射查找类的成员函数并设置可访问性
findConstructor通过反射查找类的构造函数并设置可访问性
setStatic通过反射设置类静态变量的值
set通过反射设置对象数据成员的值
findAndHook查找并hook

2. Hook

XC_MethodHook中定义了回调方法:

  • beforeHookedMethod(MethodHookParam param):被hook方法调用前执行,调用param.setResult可以跳过被hook的方法。
  • afterHookedMethod(MethodHookParam param): 被hook方法调用后执行,调用param.setResult更改被hook方法的执行结果。
  • replaceHookedMethod: 继承自XC_MethodReplacement,能替换Hook的方法
  • XC_MethodReplacement: 继承自XC_MethodHook,通过在beforeHookedMethod中调用param.setResult实现了方法的替换。
@Override
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
    try {
        Object result = replaceHookedMethod(param);
        param.setResult(result);
    } catch (Throwable t) {
        param.setThrowable(t);
    }
}

三、部分注意点和技巧

1. Hook内部类

通过美元符号 $ 连接内部类, 例如:

//获取ItemDecoration的类路径
String clsName = "androidx.recyclerview.widget.RecyclerView$ItemDecoration"

2. hook的方法

只能hook正常的方法,或者实现的方法或者构造方法,不能hook接口和抽象方法

3. context上下文获取

拿到之后可以用静态变量存放起来

try {
    Class<?> ContextClass = findClass("android.content.ContextWrapper", loadPackageParam.classLoader);
    findAndHookMethod(ContextClass, "getApplicationContext", new XC_MethodHook() {
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            Context applicationContext = (Context) param.getResult();
            XposedBridge.log("得到上下文");
        }
    });
} catch (Throwable t) {
    XposedBridge.log("获取上下文出错"+t);
}

4. 获取控件

例如获取一个EditText,上一篇文章的例子,获取TextView,修改字符。。打开被hook的app就会发现文本变成了"我是被Xposed修改的啦"

public class Main implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

        findAndHookMethod("com.tpnet.companycalc.MainActivity",
                loadPackageParam.classLoader,
                "onCreate",
                Bundle.class,
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        //获取到当前hook的类,这里是MainActivity
                        Class clazz = param.thisObject.getClass();
                        XposedBridge.log("class name:" + clazz.getName());
                        // 输入框不为私有private 可通过以下方式获取
                        //Field field = clazz.getField("tvText");// 密码输入框对象变量名称
                        // 通过反射获取控件,无论parivate或者public
                        Field field = clazz.getDeclaredField("tvText");
                        // 设置访问权限
                        field.setAccessible(true);
                        TextView textView = (TextView) field.get(param.thisObject);
                        String string = textView.getText().toString();
                        XposedBridge.log("原来的字符 : " + string);
                        // 设置属性
                        textView.setText("我是被Xposed修改的啦");
                    }
                }
        );
    }
}

5. 参数中有自定义类

通过反射获得得到自定义类

Class<?> hookMessageListenerClass = null;

hookMessageListenerClass = lpparam.classLoader.loadClass("org.jivesoftware.smack.MessageListener");

findAndHookMethod("org.jivesoftware.smack.ChatManager", lpparam.classLoader, "createChat", String.class , hookMessageListenerClass ,new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

        String sendTo = (String) param.args[0];
 
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

6. hook 构造方法

XposedBridge类里面的findAndHookConstructor方法

7.打开Activity

Activity activity = (Activity) param.thisObject;
Intent intent  = new Intent();
intent.setClassName(activity,"com.ushaqi.zhuishushenqi.ui.home.HomeActivity");
activity.startActivity(intent);

8. 自定义接口的处理

当你想调用宿主app里面的一个方法,方法的参数是一个接口,接口不能new的,这时候应该怎么办? 使用Proxy.newProxyInstance

/**
* 生成接口实例
* @param loader 类加载器
* @param interface 接口的Class实例
* @param h 接口回调 
* /
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
 类似资料: