美文网首页
Xposed Hook 魔趣列表动画 xuimod

Xposed Hook 魔趣列表动画 xuimod

作者: 小强开学前 | 来源:发表于2020-12-28 19:32 被阅读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为例)

    • 直接 TextView::class.java

      这种可能会遇到ClassLoader问题,见注意点1

    • 使用 辅助方法

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

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

    方法的Hook

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

    • 想办法获取到对应类型的对象,再用其class作为参数调用这个方法

    • 使用XposedBridgehookAllMethods

      XposedBridge.hookAllMethods(recyclerViewClass, "setAdapter", object : XC_MethodHook() {
        override fun beforeHookedMethod(param: MethodHookParam?) {
          // WRITE YOUR CODE HERE
        }
      })
      

    类或者方法被混淆

    Android四大组件和View相关的类不会被混淆,所以我们发挥的空间其实很大,但是谷歌的代码越来越封闭,一发现能往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启动流程

    • Application.attachBaseContext(context:Context?)
    • ContentProvider.onCreate()
    • Application.onCreate()
    • Activity.onCreate()/Service.onCreate()不分先后

    之前遇到的问题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
        }
    }
    

    插曲

    • 想要动画的连贯,之前尝试用View.animate()方法,它实际上是属性动画,底层是ViewPropertyAnimator,原理是Handler不断向主线程POST消息改变自身属性实现动画
      ,结果发现很多Item是动态的(比如系统设置里的电量、时间等),这样Item动画(比如Scale从0->1)刚开始(Scale = 0)就被这个item状态更新打断了,所以状态(Scale==0)就不对。结果还是要用ScaleAnimation之类的Tween动画。
    • 想提高运行效率,所以把Animation提到静态全局常量,发现每滑动一次所有Item全部一起动,明白了必须要为每个Item创建单独的动画。

    结果

    • 设备:Smartisan R1

    • OS:Mokee-100-NIGHTLY

    类别 动画可用 动画不可用
    系统 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...

    相关文章

      网友评论

          本文标题:Xposed Hook 魔趣列表动画 xuimod

          本文链接:https://www.haomeiwen.com/subject/vwstoktx.html