ASM 修改字节码 引发的R8 编译报错

3,503 阅读3分钟

问题原因

首先看下 这段新浪微博的代码 image.png

里面有deviceId的获取,目前这个代码是不合规的对吧,我想把这段代码替换掉

那怎么做呢? 我们先写一个demo class 看看能否hook成功》?

image.png

然后就是操作我们的自己码了,我们用asm来做

image.png

很显然我们的思路就是 将 TelephoneManager.getDeviceId这个操作 在编译期间 利用asm 来 替换成 FakeTelephoneManager的getFakeDeviceId 这个方法 但是注意了,我这里的getFake方法是一个 静态方法

然后重新build 发现TestHook这个类编译成功了,

然后我又增加了一个配置 去hook 文章开头的新浪微博的DeviceInfo这个类

结果transform这个地方执行成功了,但是 dexBuilder那里执行失败报错了

image.png

具体报错原因如下:

[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 则是调用的静态函数

image.png

可以很明显的看出来 指令的区别 ,对于对象的函数来说 是有 astore和aload的指令的,有入栈的操作

而静态函数则没有

再看下我们之前错误的asm 修改

image.png

这里的aload3 其实就是load了 telephonemagner的对象 入栈顶了,但是invokestatic 却没使用这个

报错就是在这里了, 所以解决方案也很简单,我们静态函数增加一个telephonemagner的参数即可,这样就可以利用到这个zhan顶的元素了。 从而规避掉这个问题

否则你还要想办法 去掉这个aload的指令 那样确实是太麻烦了 哈哈