Xposed Hook 魔趣列表动画 xuimod

2020-12-28  本文已影响0人  小强开学前

当年感觉魔趣的列表动画挺有意思,后来发现xuimod这个xposed模块,玩过一阵,现在已经这么些年没更新了,既然没人,那我就自己尝试写写吧。

少废话,先看成品

CoolApk JD

代码:github

基础方法

注意点

ClassCastException

尝试将一个变量强转为想要的类型时,例如我强转hook来的obj类型的变量为RecyclerView,这个obj虽然是这个类型,但是它是由宿主APP进程加载完成的,classLoader假设称为com.a.b.c.loader,而这个强转代码是写在我们插件APP中的,classLoader可能为com.d.f.e.loader,而不同loader是不能强转的。

一篇好文供参考Tips for writing Xposed Module to Hook Android App’s Methods

但是Java基础类型和String类以及Android基础类能豁免(至少View没问题)。

XposedBridge的一些常用方法

[图片上传失败...(image-211888-1609148847130)]

XposedHelper的主要方法

[图片上传失败...(image-d6a56e-1609148847130)]

示例

// 注入到TextView的setTextColor方法
XposedHelpers.findAndHookMethod(textViewClass, "setTextColor", Int::class.java, object : XC_MethodHook() {
    override fun beforeHookedMethod(param: MethodHookParam?) {
        // 会在第一行代码执行前调用
    }
    override fun afterHookedMethod(param: MethodHookParam?) {
         // 会在最后一行代码执行前调用
    }
})

class的获取

有两种方法(以TextView为例)

[图片上传失败...(image-7e9d9d-1609148847130)]

当然,传个那么长的className也太累了,可以直接传入TextView::class.java.name
classLoader就是handleLoadPackage这个方法的参数,每个APP,不对,应该说每个进程启动都会回调这个方法,因为发现有些比如:pushservice启动它也会回调。

方法的Hook

示例中是理想情况,因为这个方法的参数恰好是基础类型,假如不是呢?

类或者方法被混淆

Android四大组件和View相关的类不会被混淆,所以我们发挥的空间其实很大,但是谷歌的代码越来越封闭,一发现能往View外抽,就独立成一个内部类,然后这个内部类就被混淆了。

获取混淆类

尝试过程

尝试1 将APP原有的Adapter塞入自定义的AnimationAdapter中

由于ClassLoader的问题告终。但正是这里学到了ClassLoader的相关知识。

尝试2 在setAdapterHook onBindViewHolder


val adapterClazz = XposedHelpers.findClass(RecyclerView.Adapter::class.java.name,lpparam)
// hook setAdapter方法
XposedHelpers.findAndHookMethod(recyclerViewClazz, "setAdapter",adapterClazz, object : XC_MethodHook() {
    override fun afterHookedMethod(param: MethodHookParam?) {
        XposedBridge.hookAllMethods(adapterClazz, "onBindViewHolder", object : XC_MethodHook() {
            override fun afterHookedMethod(bindParam: MethodHookParam?) {
               // DO THE HOOK
        })
    }
})

Adapter类被混淆导致几乎所有Release包都无效而告终

尝试2.1 使用MethodHookParam代替findClass

// hook setAdapter方法
XposedBridge.hookAllMethods(recyclerViewClazz, "setAdapter", object : XC_MethodHook() {
    override fun afterHookedMethod(param: MethodHookParam?) {
        // 排除干扰
        if (param == null) return
        if (param.args.size != 1) return
        if (param.args[0] == null) return
        // hook Adapter.onBindViewHolder
        XposedBridge.hookAllMethods(param.args[0]::class.java, "onBindViewHolder", object : XC_MethodHook() {
            override fun afterHookedMethod(bindParam: MethodHookParam?) {
               // DO THE HOOK
        })
    }
})

有两个问题

  1. 由于很多APP都会封装一遍RecyclerView.Adapter,比如一些万能CommonAdapter,对onBindViewHolder做一些封装,在其内部调用convert()方法,对外只开放convert()和其他几个方法,然后我们使用的时候需要继承这个CommonAdapter然后实现这几个方法,假如叫做CustomAdapter,这样打包进APK后,这个CustomAdapter是找不到onBindViewHolder这个方法的。

简版 A是RecyclerView.Adapter的封装类,使用时我们需要创建B继承A,B中没有onBindViewHolder(除非它重写了)。

  1. 还是混淆问题,Androidx中ViewHolder也是静态内部类.🤣

尝试3 在setAdapterHook mRecycler

恶补了一下RecyclerViewonBindViewHolder的调用流程。
发现是与Recycler这个类有关,但是,没错,它又是一个静态内部类。

无果。

尝试3.1 使用反射,获取到 mRecycler的tryGetViewHolderForPositionByDeadline方法名

// 获取Recycler所有的方法
val methods = recycler::class.java.declaredMethods
var method: Method? = null
for (i in methods.indices) {
    if ( methods[i].parameterTypes.size == 3
          && methods[i].parameterTypes[0] == Int::class.java
          && methods[i].parameterTypes[1] == Boolean::class.java
          && methods[i].parameterTypes[2] == Long::class.java) {
          // 找到 tryGetViewHolderForPositionByDeadline
        method = methods[i]
        break
    }
}
if (method != null) {
    hookTryGetViewHolderForPositionByDeadline(recycler::class.java,method.name)
} else {
    XposedBridge.log("method is null")
}

然而

  1. 加固的还是直接找不到RecyclerView
  2. 不知道是加固还是混淆的问题混淆地特别严重的,比如Share,找不到method。

另外 ViewHolder中直接拿itemView尝试虽然能成功,但是是因为ViewHolder没有被混淆,不知为何,还是要继续优化。

尝试4 Hook启动后获取到的ClassLoader

因为一直都是自己一个人在写,遇到瓶颈去论坛看了看,无意间看见万能代码,虽然不知道fart是什么,但是前面的代码应付我的需求是够了。

另外,还梳理了一遍APP启动流程

之前遇到的问题1:加固后找不到RecyclerView
主要是由于很多加固用的是自己的ClassLoader,而这个移花接木的过程是在Application的初始化过程完成的,最多到第三步,而hookAllMethods是在这一切开始之前的。
版主介绍的 Hook 点为ActivityThread.performLaunchActivity()也就是第四步,这样是没问题的。

[图片上传失败...(image-484617-1609148847130)]

之前遇到的问题2:混淆后找不到正确的Class和Method,这个没办法,只能一一用反射解决
多次试验后找混淆后的类的技巧:综合特定的修饰符(Modifiers)、所在的package一一匹配找到四大组件或者自定义View类的特定方法,根据其返回值找到相应的Class(例下文的ViewHolder以及Recycler的反射获取)
多次试验后找混淆后的方法的技巧:综合特定的修饰符(Modifiers)、特定的ReturnType、特定的参数个数、参数类型一一匹配(例下文的tryBindViewHolderByDeadline的反射获取)
多次试验后找混淆后的参数的技巧:综合特定的修饰符(Modifiers)、特定的Type一一匹配(例下文的itemView的反射获取)

hook入口

XposedBridge.hookAllMethods(activityThread,"performLaunchActivity",object :XC_MethodHook(){
    override fun afterHookedMethod(param: MethodHookParam?) {
        if(param==null) return
        val mInitialApplication = XposedHelpers.getObjectField(param.thisObject,"mInitialApplication")
        val  finalCL = XposedHelpers.callMethod(mInitialApplication,"getClassLoader") as ClassLoader
        XposedBridge.log("found classload is => $finalCL")
        r = XposedHelpers.findClassIfExists(RecyclerView::class.java.name,finalCL)
        // TODO
    }
}

Recycler的反射获取

查看源码发现只有Recycler为public final 修饰的

private fun findRecylerClass(recyclerViewClass:Class<*>,finalCL:ClassLoader){
  for (i in it.classes.indices)
    // 17 为 public final《==》可以用Modifier.toString()转换
    if(it.classes[i].modifiers == 17){
        l = XposedHelpers.findClass(it.classes[i].name,finalCL)
        break
    }
}

ViewHolder的反射获取

查看源码发现RecyclerView有个public findViewHolderForItemId(long id)方法返回值为ViewHolder,重要的是参数为long很独特,就它一个。
Class.methods可以过滤掉不是public类型的方法,这样我们只用判断参数类型和参数个数就行。

private fun findRecylerClass(recyclerViewClass:Class<*>,finalCL:ClassLoader){
  for (i in it.methods.indices)
    if(it.methods[i].parameterTypes.size==1 && it.methods[i].parameterTypes[0] == Long::class.java){
        h = XposedHelpers.findClass(it.methods[i].returnType.name,finalCL)
        XposedBridge.log("--------\nFOUND ViewHolder\n---------\n")
        break
    }
}

TryBindViewHolderByDeadline的反射获取

这个方法返回值为Boolean,同时有四个参数,特别好找。注意它不是public方法,需要用declaredMethods

private fun findBindName(recycler:Class<*>,finalCL:ClassLoader){
  for (i in recycler.declaredMethods.indices) {
    if(recycler.declaredMethods[i].modifiers==2
            &&recycler.declaredMethods[i].parameterTypes.size==4
            && recycler.declaredMethods[i].returnType == Boolean::class.java){
        b = recycler.declaredMethods[i].name
        break
    }
  }
}

itemView的反射获取

ViewHolder的全局变量就itemView类型为View

private fun findItemViewName(viewHolderClass:Class<*>,finalCL:ClassLoader){
  for (i in it.methods.indices)
    if(it.methods[i].parameterTypes.size==1 && it.methods[i].parameterTypes[0] == Long::class.java){
        v = XposedHelpers.findClass(it.methods[i].returnType.name,finalCL)
        break
    }
}

插曲

结果

类别 动画可用 动画不可用
系统 Lawnchair设置短信电话... 暂无
第三方 京东什么值得买酷安V2rayNG Share微博客户端淘宝锤子论坛

没时间全测试,但是应该涵盖了所有情况。
锤子论坛(1.6.4)用的是ListView,被时代淘汰的东西,算了吧。
淘宝(Google Play 9.13.1)是因为它用的还是supportv7包,再加上不给通话权限不给登录等等恶心行径,懒得适配了。
Share(3.7.9)是真牛逼,加固混淆得妈都不认识,猜测和MT管理器作者一样是个大佬,下一章节研究研究。

另外发现开启插件APP加载时间明显变长,下一章节想办法优化优化。

优化4

因为每次activity启动都会调用 performLaunchActivity()导致hook代码被反复进行,其实没有必要。
所以针对每个package做一个键值对的记录,不管加载失败还是成功,只要调用了就记录下,下次直接return.

if(foundedPackage[lpparam.packageName] == true) return
foundedPackage[lpparam.packageName] = true

5 Coming soon...

上一篇下一篇

猜你喜欢

热点阅读