xposed开发与实践

好久不写技术向博客。

最近忙完release的开发,想起自己做xposed开发也差不多有一年了,从最简单的lin15开始,到lin16,再到release,对于xposed开发也有了一定的了解,当然还算不上精通。

准备阶段

用Android Studio新建一个工程,之后在AndroidManifest.xml的application标签里添加以下内容。

        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="@string/moduel_info" />
        <meta-data
            android:name="xposedminversion"
            android:value="82" />

第一个标签指明该APP是不是xposed模块,第二个标签是该模块在xposed里的说明,第三个则是xposed的最低A版本。

alt

在app文件夹右键->new->Folder->Assets Folder,可以创建assets文件夹,在这个文件夹内创建一个文本文件,名为xposed_init 。它的作用是告诉xposed框架,需要注入哪些hook的class。所以里面所放的便是各种hook的class,记住要写全这些class的名字,你可以利用Android Studio的复制功能,选中class后右键,选择Copy Reference,即可复制类的全名,将其粘贴到这个文件里。多个hook的class只需要换行后继续填写即可。

alt

** 注意,如果你只是新建了hook类却并未将其写入到xposed_init这个文件夹里,那么这个类是不会生效的。 **

新建hook类

xposed最重要的还是hook,翻译是钩子,可以在程序运行的时候,往钩住的地方注入自己的代码。

随意创建一个class,将它实现IXposedHookLoadPackage 这个接口,就可以实现一个简单的hook类。当然hook并非只有方法那么简单,还有资源hook,专门用于更改资源(xml文件)的hook,实现IXposedHookInitPackageResources 接口,只是现如今的xposed大多数不支持资源钩子,所以我也并没有深入去玩过资源hook。

创建好类并实现IXposedHookLoadPackage 接口后,会要求实现里面handleLoadPackage方法,实现即可。

之后是一个重要的角色,XposedHelpers ,这是在xposed开发里的主角,基本上所有使用hook的方法都要使用它。

首先是在实现的方法里使用这个类的静态方法,去hook一个普通类的一个方法,栗子如下。

public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {

        if (!lpparam.packageName.equals(Conf.PACKAGE)) {
            return;
        }


        /*hook布局,一开始将布局给替换*/
        XposedHelpers.findAndHookMethod(CLASS, lpparam.classLoader, "b", LayoutInflater.class, ViewGroup.class, Bundle.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                decorView = (ViewGroup) param.getResult();//获取返回的view

//                XposedBridge.log("info---->>>>"+decorView);
//
//                XposedBridge.log("view----->>>"+param.getResult());

//                resume();

            }
        });
}

以上是最简单的hook方法的方法。

使用的是XposedHelpers.findAndHookMethod 这个方法,它是最基础的hook方法,里面所需参数分别是需要hook的class,classloader,method名字,这个method所需参数类型,以及最后需要以怎样的方式去hook这个method。

看不懂?我们一步一步来。

首先是需要hook的class

我们使用xposed,无非就是对某个class进行hook,找到里面某个方法,然后在这个方法执行的时候,将这个方法修改,执行我们所需要的代码。所以第一个参数就是定位这个class,当然如果是包内自定义的class,则需要将这个class写全名,连同包名一起写,才能找到这个class。

第二个参数

这是classloader,一般来说lpparam内置的classloader就可以了。

第三个参数

这是method名,也就是需要hook的方法名字,xposed开发所针对的最小单位就是method,所以一定要是这个class里写明出来的method,如果该class是子类,却并未实现父类的方法,则在这个class内无法hook继承父类的方法。

后面的参数

虽然知道了类名,知道了方法名,但一个类里面可以拥有多个同样名字的方法,所以不知道参数的话,还是无法准确hook到这个方法,所以要在后面添加这个method所需要的参数的class,比如我上面的栗子里,所需的参数分别是LayoutInflater,ViewGroup以及Bundle,所以才需要将其class也给写出来。当然如果是自定义的class,也可以直接将class的全名写全,让xposed框架识别出来。聪明的同学应该能看出来,我这里hook的是Fragment的onCreateView方法,只是这个Fragment被混淆了,所以具体hook的是混淆之后的方法名。

最后的参数

最后的参数是个回调接口,我想应该看出来了。这就是真正需要hook好以后所执行的代码,在这里面可以获取这个方法的入参,返回值等等,那么就可以随意对这个method进行修改了。

另外有人可能注意到这个接口里需要实现的方法不止一个afterHookedMethod ,还有一个beforeHookedMethod ,看名字也知道它们是什么意思。分别是在运行宿主方法之后调用,以及在宿主方法运行之前调用这个方法。

在之前还是在之后,要看你想要什么,如果你想要彻底改变这个宿主方法的入参,那么就选择之前,如果是希望在这个宿主方法实例化了某些控件后再去获取控件的实例,那么就选择之后。

有没有直接代替这个宿主方法的操作?我不想要这个宿主方法被运行可以吗?

如果你能想到这个问题,至少说明你还是非常有自己想法的。没错,xposed提供了这样的骚操作,可以让整个宿主方法被替换掉,只运行你的代码。

只是想要使用这个,就不能使用XC_MethodHook 回调接口了,而是需要XC_MethodReplacement 接口,它定义了替换接口方法,只需要在里面实现你自己的代码即可。当然,它是有一个返回值的,这个返回值类型要与宿主方法的返回值类型一致,以便替换宿主方法所产生的返回值,如果宿主方法是void,那就返回null。如果你想让它继续执行宿主方法,那就需要这样实现。

return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);

如此一来便会继续执行宿主方法,当然写在前面的你的代码也会继续执行。

我一般用来修改某些参数,不让宿主方法继续执行的时候,才会选择替换这个宿主方法,一般情况下不需要使用这个接口,毕竟不知道会产生什么后果。

其他问题

说完了主角XposedHelpers以及它强大的hook,再来说说其他的内容。

怎么打印调试日志?

上面我特意留下了xposed开发打印日志的操作,用于在开发时期的调试,没错,它就是XposedBridge.log ,日志会在xposed框架那个APP里查看到。

怎么获取上帝对象Context

上帝对象context一直都是Android里的重头,对于这样一个对象,xposed里已经提供了获取的方法,具体如下所示

AndroidAppHelper.currentApplication()

这是xposed特意留下的获取context对象的方法,它是直接获取宿主APP的context对象,不必担心不能用。

怎么与模块通信

可能有的同学见过一些非常炫酷的模块,它能够在APP内就打开一些开关来设置模块是否进行工作,但是自己做的时候却发现获取不到hook的class的对象并设置。原因在于hook类被xposed框架给实例化了,虽然我们自己也能实例化它并拿到对象,但并不是真正hook到宿主APP的对象,所以直接操作并不起作用。可以简单地理解为hook已经与本APP脱离了关系,实际上是两个APP了,所以本APP并不能操控Hook。

但是,Android提供了便利的broadcast啊,也就是广播。虽然APP不能直接操控另一个APP,但是广播可以啊,我们可以利用hook在内部注入时注册广播接收器,在本APP内不就可以任意操控了吗。

当然要是宿主APP没启动,那么广播也就没用处了。这种情况,一般是利用配置文件,之后hook那边读取配置文件,一样可以达到通讯的目的。

怎么保存信息

某些时候可能需要存储信息,避免重启之后失效,之前xposed提供了XSharedPreferences,只可惜这个不支持7.0以后的系统,个人推荐直接使用SharedPreferences,在hook之后直接使用之前提到的上帝对象来生成,这样就可以存储一些配置信息了,哪怕是重启了也还是会保存在文件内部。

怎么使用反射

xposed开发最离不开的就是反射,通常来说用到反射肯定是需要获取宿主class的某个属性或是某个对象,反射便起了很大的作用。通常来说反射就是Java那一套常规的操作,只是那一套比较繁琐,所幸xposed提供了非常便利的反射方法。

XposedHelpers.getObjectField//获取object对象
XposedHelpers.getBooleanField//获取Boolean
XposedHelpers.getCharField//获取Char
XposedHelpers.getDoubleField//获取Double
XposedHelpers.getFloatField//获取Float
XposedHelpers.getIntField//获取Int

更多请自己体会

当然,反射有时候并不仅仅是获取想要的属性那么简单,还有可能是希望运行宿主APP里的某个方法。当然这种操作就使用一般的反射即可,没什么太多可以说的。

调试的时候没起作用

大概是开启了Instant Run功能,请在Android Studio的设置里,找到这个并关闭,之后即可在调试时起作用了。

某些小结

1、对于Fragment以及Activity,无法直接new一个实例然后给宿主APP替换。

原因在于classloader不同,所以哪怕都是在v4包下的Fragment,其实也无法在宿主APP内进行工作,这也就是为什么大多数xposed模块并没有在宿主APP内有什么自定义的Fragment以及Activity界面。但是xposed是强大的,虽然无法直接new一个进行替换,却可以在他们实例化的时候直接鸠占鹊巢,据为己有。如果实现就要看你怎么玩了。

2、hook的好处

hook虽然麻烦,却打破了固有的写代码方式,它可以将很多hook都写在一个class内,便于获取不同宿主class内的对象然后进行管理控制,甚至是替换。

3、hook的坏处

hook虽然可以很自由,但过于自由便难于管理,所以一般来说不建议将全部hook都写在一个class内,建议是根据功能来写hook的class,方便集中管理不同功能的hook。

APP的反编译

现如今都9102年了,APP的反编译并不算什么难题,难的是用什么工具。

正如我上面所言,都9102年了,你随便到搜索框里搜索APP的反编译,其结果都是几年前的三件套,一丝都不愿改变。

在这里我推荐其他的反编译工具给你。

第一个是Apktool,古老的反编译三件套之一,它的用处在于将资源文件反编译成可阅读的文件,而不是一大堆看不懂的乱码。

第二个是jadx,这个可绝对是秒杀三件套的存在,比jd还吊的存在,用它直接打开apk,直接将里面的dex文件给反编译成Java代码,并提供反混淆操作,当然要写xposed模块的话,最好还是关闭反混淆,因为会不知道原名。

第三个就是我们一直在使用的AS,Android Studio。它内置了反编译操作,虽然无法直接将dex反编译为Java代码,但对于资源文件却认真地反编译了,可以很直观看到资源文件的内容。

xposed开发里的反编译技巧

反编译是为了定位需要hook的class以及具体的方法,有时候我们光看宿主APP是很难定位到这个方法究竟写在了哪个class里,更何况这还是混淆以后的class,全部都变成了abc的字样,想要读懂简直是要命。但是宿主APP也逃不过使用字符串的命,比如你需要定位宿主APP的某个界面在哪,可以查看这个界面用了哪些字符串,我们可以从资源文件内,搜索该字符串,即可得到该字符串的定义名称,之后复制该名称,在jadx直接搜索,瞬间即可知道在哪里使用了这个字符串,即可知道这个界面被定义在了哪个class内。

其余的就不必我多说了吧,基本上大同小异,得知了class后,定位具体方法,再hook……

嗯,今天说的有点多了,可能会有点杂,有点乱,有点记不住,别提了我肩膀都痛了。如果你有一点的xposed开发经验,我想我说的你应该都能看懂才是。如果你完全没有经验,别着急,从头慢慢看起,虽然有些参数我没说明白,但有过Java开发经验的你应该能明白那是什么意思。

今天就到这里,下次再见。

暂无评论
发表新评论