ASM 修改位元組碼 引發的R8 編譯報錯

語言: CN / TW / HK

問題原因

首先看下 這段新浪微博的程式碼 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的指令 那樣確實是太麻煩了 哈哈