问题原因
首先看下 这段新浪微博的代码
里面有deviceId的获取,目前这个代码是不合规的对吧,我想把这段代码替换掉
那怎么做呢? 我们先写一个demo class 看看能否hook成功》?
然后就是操作我们的自己码了,我们用asm来做
很显然我们的思路就是 将 TelephoneManager.getDeviceId这个操作 在编译期间 利用asm 来 替换成 FakeTelephoneManager的getFakeDeviceId 这个方法 但是注意了,我这里的getFake方法是一个 静态方法
然后重新build 发现TestHook这个类编译成功了,
然后我又增加了一个配置 去hook 文章开头的新浪微博的DeviceInfo这个类
结果transform这个地方执行成功了,但是 dexBuilder那里执行失败报错了
具体报错原因如下:
[CIRCULAR REFERENCE: com.android.tools.r8.internal.Hc: Different stack heights at jump target: 0 != 1]
这就很奇怪了, 为啥r8这里会报错呢? 看报错原因 似乎是有循环引用? 还是r8和asm不兼容?
我尝试的去掉 deviceInfo 只保留testhook类 发现是ok的
看来问题就出现在asm hook deviceInfo上
是asm的写法 有问题 导致了 r8 这边有报错(到这里其实大概率能猜到 r8这个报错其实是误报了,根源在asm hook上)
自习排查以后 发现
deviceInfo里面的调用 时invokevirtual的操作符,我们这里修改的本质其实是:
对象成员函数的调用 修改成静态函数调用。
所以如果要这么做的话 需要修改 参数的入栈逻辑才行 因为之前是有个对象入栈的逻辑的
那改成静态函数调用的话 这一步操作就没有了,所以你要额外处理之前的参数入栈逻辑
这里为了简单一点,我们可以 在定义fake静态函数的时候 加上一个对应的参数就可以了,这样就可以利用之前的参数的入栈逻辑,而不用大改了
这里可以看下 hook的方法中 对desc做了修改,新增了android/telephony/TelephonyManager作为参数
private fun MethodInsnNode.optimize(klass: ClassNode, method: MethodNode) {
// 判断是否是deviceId(0)这个方法
val flag = (this.desc == "(I)Ljava/lang/String;" && this.name == "getDeviceId")
this.owner = fakeOwner
if (flag) {
this.name = fakeTelephonyManagerTakeIdMethodName
this.desc = "(Landroid/telephony/TelephonyManager;I)Ljava/lang/String;"
} else {
this.name = fakeTelephonyManagerMethodName
this.desc = "(Landroid/telephony/TelephonyManager;)Ljava/lang/String;"
}
this.opcode = Opcodes.INVOKESTATIC
this.itf = false
}
字节码对比
可以看下对比 test1 这里是调用了一个对象的函数,test2 则是调用的静态函数
可以很明显的看出来 指令的区别 ,对于对象的函数来说 是有 astore和aload的指令的,有入栈的操作
而静态函数则没有
再看下我们之前错误的asm 修改
这里的aload3 其实就是load了 telephonemagner的对象 入栈顶了,但是invokestatic 却没使用这个
报错就是在这里了, 所以解决方案也很简单,我们静态函数增加一个telephonemagner的参数即可,这样就可以利用到这个zhan顶的元素了。 从而规避掉这个问题
否则你还要想办法 去掉这个aload的指令 那样确实是太麻烦了 哈哈