Xposed Hook 魔趣列表动画 xuimod

当年感觉魔趣的列表动画挺有意思,后来发现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...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容