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作为参数调用这个方法
-
使用
XposedBridge
的hookAllMethods
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 在setAdapter
内 Hook
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
})
}
})
有两个问题
- 由于很多APP都会封装一遍
RecyclerView.Adapter
,比如一些万能CommonAdapter,对onBindViewHolder做一些封装,在其内部调用convert()
方法,对外只开放convert()
和其他几个方法,然后我们使用的时候需要继承这个CommonAdapter
然后实现这几个方法,假如叫做CustomAdapter
,这样打包进APK后,这个CustomAdapter
是找不到onBindViewHolder
这个方法的。
简版 A是
RecyclerView.Adapter
的封装类,使用时我们需要创建B继承A,B中没有onBindViewHolder
(除非它重写了)。
- 还是混淆问题,Androidx中ViewHolder也是静态内部类.🤣
尝试3 在setAdapter
内 Hook
mRecycler
恶补了一下
RecyclerView
中onBindViewHolder
的调用流程。
发现是与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")
}
然而
- 加固的还是直接找不到
RecyclerView
- 不知道是加固还是混淆的问题混淆地特别严重的,比如
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