本文讨论一下xposed模块编写的步骤,与如何hook构造函数,以及一些需要注意的地方。
跟把大象放冰箱分3步一样,编写xposed模块只需要4步。
拷贝 XposedBridgeApi.jar 到模块工程的 libs 目录下,放一个 jar 下载链接,其实也可以去作者仓库下载源码自己编译一个jar
https://github.com/bywhat/XposedBridgeApi
https://github.com/rovo89/XposedBridge
修改 build.gradle 目录,将 libs 下的 jar 添加依赖
compileOnly?fileTree(dir:?'libs',?includes:?['*.jar'])
修改 AndroidManifest.xml,在 ?application 的标签下添加如下 mate 信息:
<application
????android:allowBackup="true"
????android:dataExtractionRules="@xml/data_extraction_rules"
????android:fullBackupContent="@xml/backup_rules"
????android:icon="@mipmap/ic_launcher"
????android:label="@string/app_name"
????android:roundIcon="@mipmap/ic_launcher_round"
????android:supportsRtl="true"
????android:theme="@style/Theme.EdxposedTest"
????tools:targetApi="31"?>
????<meta-data
????????android:name="xposedmodule"
????????android:value="true"?/>
????<meta-data
????????android:name="xposeddescription"
????????android:value="edxposed?hook?learning"?/>
????<meta-data
????????android:name="xposedminversion"
????????android:value="54"?/>
</application>
xposedmodule 表示该apk是不是一个 xposed 模块,肯定要填 true。
xposeddescription 这个描述信息会展示到模块列表的模块信息里面。
xposedminversion 最小的版本,与 libs 下使用的版本一致即可。
新建一个 hook 类(入口类),入口类必须是 public 的,否则会出现无法访问错误,入口类需要实现 IXposedHookLoadPackage 接口:
public?class?Xposed01?implements?IXposedHookLoadPackage?{
????@Override
????public?void?handleLoadPackage(XC_LoadPackage.LoadPackageParam?loadPackageParam)?throws?Throwable?{
????????...
????}
}
建立 assets 目录,在 assets 目录下新建文件 xposed_init 文件,在里面声明你在第3步写的入口类。可以声明多个,优先级是按照编写顺序,也就是哪个先声明,就先生效。
com.example.edxposedtest.Xposed01
以下面这个例子为演示,演示代码已传递到 p22。
package?com.example.hooktarge
class?HookTarget1?constructor(private?val?str:?String,?val?id:?Long?)?{
????constructor()?:this("",?0)
????override?fun?toString():?String?{
????????return?"HookTarget1(str='$str',?id=$id)"
????}
}
这里我们写了两个构造函数。我们针对这2个构造函数进行测试。分为两方面的测试,一个是无参数的构造函数,一个是有参数的构造函数。
hook构造参数有两个 api:
public?static?XC_MethodHook.Unhook?findAndHookConstructor(Class<?>?clazz,?Object...?parameterTypesAndCallback)?{
????if?(parameterTypesAndCallback.length?!=?0?&&?parameterTypesAndCallback[parameterTypesAndCallback.length?-?1]?instanceof?XC_MethodHook)?{
????????XC_MethodHook?callback?=?(XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length?-?1];
????????Constructor<?>?m?=?findConstructorExact(clazz,?getParameterClasses(clazz.getClassLoader(),?parameterTypesAndCallback));
????????return?XposedBridge.hookMethod(m,?callback);
????}?else?{
????????throw?new?IllegalArgumentException("no?callback?defined");
????}
}
public?static?XC_MethodHook.Unhook?findAndHookConstructor(String?className,?ClassLoader?classLoader,?Object...?parameterTypesAndCallback)?{
????return?findAndHookConstructor(findClass(className,?classLoader),?parameterTypesAndCallback);
}
可以看到,实际上第二个 api 也是使用了第一个 api。
所以当我们可以直接获取到 hook 类的 class 时,那么就使用第一个 api 更简单,否则使用第二个 api。对于一些加壳的 apk 或者动态加载的 类,我们需要传递特定的 classloader 才能正确的找到类,此时只能使用第二个 api。
XposedHelpers.findAndHookConstructor("com.example.hooktarge.HookTarget1",?loadPackageParam.classLoader,?new?XC_MethodHook()?{
????@Override
????protected?void?beforeHookedMethod(MethodHookParam?param)?throws?Throwable?{
????????super.beforeHookedMethod(param);
????????Log.e("Xposed01",?"no?params?beforeHookedMethod");
????}
????@Override
????protected?void?afterHookedMethod(MethodHookParam?param)?throws?Throwable?{
????????super.afterHookedMethod(param);
????????Log.e("Xposed01",?"no?params?afterHookedMethod");
????}
});
由于是 hook 构造方法,所以最后一个参数,我们使用 XC_MethodHook。
它有两个接口方法:
beforeHookedMethod 在方法调用之前执行
afterHookedMethod 在方法调用之后执行
其实代码也是与上面差不过的,我们可以先看一下 MethodHookParam 这个类的字段:
//?方法
public?Member?method;
//?方法所属的对象
public?Object?thisObject;
//?方法的参数
public?Object[]?args;
//?方法返回值
private?Object?result?=?null;
有了这些字段,我们就可以修改构造函数的参数传递。
同时,parameterTypesAndCallback 这个参数的名字也暗示了hook有参数的方法,需要传递参数类型:
XposedHelpers.findAndHookConstructor("com.example.hooktarge.HookTarget1",?loadPackageParam.classLoader,?String.class,?long.class,?new?XC_MethodHook()?{
????@Override
????protected?void?beforeHookedMethod(MethodHookParam?param)?throws?Throwable?{
????????super.beforeHookedMethod(param);
????????Log.e("Xposed01",?"params?beforeHookedMethod");
????????param.args[0]?=?"abd";
????????param.args[1]?=?42;
????}
????@Override
????protected?void?afterHookedMethod(MethodHookParam?param)?throws?Throwable?{
????????super.afterHookedMethod(param);
????????Log.e("Xposed01",?"params?afterHookedMethod");
????????Log.e("Xposed01",?"thisObject?=?"?+?param.thisObject);
????????Log.e("Xposed01",?"getResult?=?"?+?param.getResult());
????}
});
在 app 里面调用一下这些构造函数:
HookTarget1()
HookTarget1("a5right",?1)
安装 app 与 模块,我们运行一下看看效果:
E??no?params?beforeHookedMethod
E??params?beforeHookedMethod
E??params?afterHookedMethod
E??thisObject?=?HookTarget1(str='abd',?id=42)
E??getResult?=?null
E??no?params?afterHookedMethod
E??params?beforeHookedMethod
E??params?afterHookedMethod
E??thisObject?=?HookTarget1(str='abd',?id=42)
E??getResult?=?null
可以看到,无参构造函数触发了有参构造函数的调用。
我们在hook代码中修改的参数也生效了。
同时还需要注意到,getResult 返回了空,说明了构造函数是没有返回值的。这个应该好理解,之前在写JVM的时候,就学习过了,new 分两步,第一步分配空间,第二步初始化这个对象:
MyObject?obj?=?allocateInstance(MyObjcet.class);
obj.<init>;
所以,如果有需要,请在构造函数的 hook 中使用 thisObject,而不是 getResult。
重新修改代码后,需要在 edposed manager 的module里面的开关,重新关闭再打开后生效,如果未生效,尝试重启。