xposed使用小记

415 阅读4分钟

上一篇尝试安装了Xposed框架并成功运行,该篇主要学习使用xposed框架

利用xposed框架编写插件主要有四步

  • AndroidManifest.xml中添加xposed配置xposedmodule xposeddescription xposedminversion
  • 拷贝XposedBridgeApi-xx.jar到对应的libs库,或者通过jcenter gradle的方式导入XposedBridgeApi(github.com/rovo89/Xpos… ),并配置compileOnly到依赖项中
  • 编写xxxModule并继承IXposedHookLoadPackage,hook目标应用的类
  • 声明xxxModuleassets/xposed_init文件中

一个简单的demo

目标:创建目标app,包名com.learn.demo1,包括MainActivity,通过hook手段,在启动MainActivity.onCreate的时候打印call com.learn.demo1.MainActivity.onCreate()

通过android studio创建一个上述目标demo1,运行到手机上,此时成功安装,并显示默认界面

创建hook module(对demo1进行hook),在AndroidManifest.xmlApplication标签中配置如下

<!--是否是xposed模块-->
<meta-data android:name="xposedmodule" android:value="true"/>
<!--描述,会显示在对应xposed installer中-->
<meta-data android:name="xposeddescription" android:value="learn hook"/>
<!--xposed bridge的最小版本-->
<meta-data android:name="xposedminversion" android:value="82" />

导入XposedBridgeApi库,此处我们使用gradle方式,注意由于jcenter已经停更,需要使用其他gradle源,这里使用了https://api.xposed.info/

// 配置repositories,在project根目录下的的gradle文件中,找到repositories并加入以下源
repositories {
    maven {
        setUrl("https://api.xposed.info/")
    }
    ...
}

// 配置依赖,具体项目中的gradle
dependencies {
    compileOnly("de.robv.android.xposed:api:82")
}

创建LearnModule类,代码如下

package com.learn.xposed;

import android.os.Bundle;
import android.util.Log;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class LearnModule implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        // 过滤非目标包
        if (!lpparam.packageName.equals("com.learn.demo1")) {
            return;
        }
        ClassLoader classLoader = lpparam.classLoader;
        // hook MainActivity.onCreate函数并在onCreate调用结束后,执行打印逻辑
        XposedHelpers.findAndHookMethod("com.learn.demo1.MainActivity", classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Log.i("testzy", "call com.learn.demo1.MainActivity.onCreate()");
            }
        });
    }
}

创建assets资产目录,创建文件xposed_init并配置Module模块

com.learn.xposed.LearnModule

此时就完成了一个简单的插件编写,运行到手机中,打开xposed installer,选择module并勾选启用当前插件,重启后即可看到在logcat中成功打印了call com.learn.demo1.MainActivity.onCreate()

Xposed API使用

目标: 创建Dog类

  1. hook其中的构造函数/静态函数/非静态函数并打印日志
  2. 修改静态or非静态field,并主动调用Dog类中的函数打印其内容

对应目标app修改如下修改目标app的onCreate()


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // 添加如下两行
    Dog dog = new Dog();
    Dog dogWithName = new Dog("wangwang", 1);
}

// 添加类
public class Dog {
    private static final String TAG = "testzy";
    private static String KIND = "Dog";
    String name;
    int age;
    public Dog() {
        Log.i(TAG, "call Dog()");
    }

    public Dog(String name, int age) {
        Log.i(TAG, "call Dog(String, int)");
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return KIND + "{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

所有的xxxModule都派生自de.robv.android.xposed.IXposedHookLoadPackage,该接口当一个apk启动加载时会被调用,并通过回调函数void handleLoadPackage(LoadPackageParam lpparam)得到启动apk中的信息,其中LoadPackageParam中的信息如下

// 包名
public String packageName;

// 进程名
public String processName;

// 类加载器
public ClassLoader classLoader;

// 暂不关注
public ApplicationInfo appInfo;

// 暂不关注
public boolean isFirstApplication;

构造函数的Hook api如下,几乎所有的api都会有如下两种类似的调用方式,要么通过传入className字符串和classLoader,要么直接构造Class并使用

public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback)
public static XC_MethodHook.Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback)

我们通过hook Dog类中的构造函数可以成功打印了附加的信息

package com.learn.xposed;

import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class LearnModule implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals("com.learn.demo1")) {
            return;
        }
        ClassLoader classLoader = lpparam.classLoader;
        XposedHelpers.findAndHookConstructor("com.learn.demo1.Dog", classLoader, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Log.i("testzy", "before call com.learn.demo1.Dog()");
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Log.i("testzy", "after call com.learn.demo1.Dog()");
            }
        });
        Class<?> dogClass = XposedHelpers.findClass("com.learn.demo1.Dog", classLoader);
        XposedHelpers.findAndHookConstructor(dogClass, String.class, int.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
                Log.i("testzy", "before call com.learn.demo1.Dog(String, int)");
                for (Object p: param.args) {
                    Log.i("testzy", "param = " + p);
                }
                Log.i("testzy", "result = " + param.getResult());
            }

            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Log.i("testzy", "after call com.learn.demo1.Dog(String, int)");
                for (Object p: param.args) {
                    Log.i("testzy", "param = " + p);
                }
                Log.i("testzy", "result = " + param.getResult());
                Log.i("testzy", "this = " + param.thisObject);
            }
        });

    }
}

注意:

  • 不要在构造函数的beforeHookedMethod()中使用param.thisObject
  • 获取class时采用XposedHelpers.findClass()的方式,而不是classLoad.loadClass(),findClass最终调用的是Class.forName()classLoader.loadClass()实现上有差异(当前xiaomi2s中采用loadClass方式插件不会生效,了解到其他手机可以使用该方法,猜测存在兼容问题)

打印结果如下

testzy                  com.learn.demo1                      I  before call com.learn.demo1.Dog()
testzy                  com.learn.demo1                      I  call Dog()
testzy                  com.learn.demo1                      I  after call com.learn.demo1.Dog()
testzy                  com.learn.demo1                      I  before call com.learn.demo1.Dog(String, int)
testzy                  com.learn.demo1                      I  param = wangwang
testzy                  com.learn.demo1                      I  param = 1
testzy                  com.learn.demo1                      I  result = null
testzy                  com.learn.demo1                      I  call Dog(String, int)
testzy                  com.learn.demo1                      I  after call com.learn.demo1.Dog(String, int)
testzy                  com.learn.demo1                      I  param = wangwang
testzy                  com.learn.demo1                      I  param = 1
testzy                  com.learn.demo1                      I  result = null
testzy                  com.learn.demo1                      I  this = com.learn.demo1.Dog@2333a300

接着尝试对Dog中的内容做修改,包括更改年龄,KIND&flag字段改成大写,并调用toString()方法

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Log.i("testzy", "after call com.learn.demo1.Dog(String, int)");
        for (Object p: param.args) {
            Log.i("testzy", "param = " + p);
        }
        Log.i("testzy", "result = " + param.getResult());
        Log.i("testzy", "this = " + param.thisObject);

        // 添加如下代码
        Class<?> aClass = XposedHelpers.findClass("com.learn.demo1.Dog", classLoader);

        // 设置kind & flag静态变量
        // kind
        // Field kind = aClass.getDeclaredField("kind");
        // kind.setAccessible(true);
        // kind.set(null, "DOG");
        XposedHelpers.setStaticObjectField(aClass, "kind", "DOG");
        
        // flag
        // 方案1
        // Field flag = aClass.getDeclaredField("flag");
        // flag.setAccessible(true);
        // flag.set(null, 666);    // 成功修改
        
        // 方案2
        // Class<?> integerClass = XposedHelpers.findClass("java.lang.Integer", classLoader);
        // Object number = XposedHelpers.newInstance(integerClass, 123);
        // XposedHelpers.setStaticObjectField(aClass, "flag", number);
        
        // 方案3
        // 注意这里是ObjectField
        XposedHelpers.setStaticObjectField(aClass, "flag", 777);

        // 调用非静态方法打印结果
        // 方案1
        // Method print = aClass.getMethod("print");
        // print.invoke(param.thisObject);  // 成功调用
        
        // 方案2
        XposedHelpers.callMethod(param.thisObject, "print");
    }

此时可打印出

testzy                  com.learn.demo1                      I  DOG{name='wangwang', age=1}, 666

参考文章