原文地址:http://jserv.blogspot.com/2010/05/android.html
转载是由于国内用户实在没法访问blogspot网站,但本文又具有相当大的实用价值。
為了某個實驗的動機,我們評估反編譯 Android 應用程式的可行性,本文即是筆者的心得與實際的範例,僅供參考。就筆者的認知,目前還沒有針對 Android 的 DEX to Java source 反編譯工具,可實際處理一般的 Android 應用程式,多半要繞幾圈,還會得到不甚理想的結果,不過,smali 這個反組譯工具,已是可用了,只是得對付類似 Jasmin 語法的 Dalvik 組合語言。筆者打包了 smali 與 Frozen Bubble for Android,作為示範:
* http://0xlab.org/~jserv/dex-dis-example.tar.bz2
* 国内用户可以通过以下网址下载:http://u.115.com/file/f0b0322f12
在 GNU/Linux 環境中,首先取得 Android SDK,這裡採用 Eclair/2.1,工具執行檔的路徑是 android-sdk-linux_86/tools,將此路徑放入 $PATH 環境變數,如此一來,就可以操作 adb, aapt, apkbuilder 一類的工具程式。筆者提供的套件已包含 Frozen Bubble 執行檔,檔名為 "FrozenBubble-orig.apk"。一旦 Android emulator 啟動後,即可安裝進去執行:(後續的操作也需要 Emulator 持續開啟)
$ adb install -r FrozenBubble-orig.apk
以下是執行畫面:
二話不說,當然是觀察反組譯的結果:
smali-src$ find ./org/jfedor/frozenbubble/FrozenBubble.smali ./org/jfedor/frozenbubble/R$id.smali ./org/jfedor/frozenbubble/GameView.smali ./org/jfedor/frozenbubble/SoundManager.smali ./org/jfedor/frozenbubble/LaunchBubbleSprite.smali ./org/jfedor/frozenbubble/Compressor.smali ./org/jfedor/frozenbubble/R$attr.smali ./org/jfedor/frozenbubble/BubbleFont.smali ./org/jfedor/frozenbubble/PenguinSprite.smali ./org/jfedor/frozenbubble/GameView$GameThread.smali ./org/jfedor/frozenbubble/BubbleSprite.smali./org/jfedor/frozenbubble/R$string.smali ./org/jfedor/frozenbubble/R$drawable.smali ./org/jfedor/frozenbubble/ImageSprite.smali ./org/jfedor/frozenbubble/BubbleManager.smali ./org/jfedor/frozenbubble/GameScreen.smali ./org/jfedor/frozenbubble/R.smali ./org/jfedor/frozenbubble/R$layout.smali ./org/jfedor/frozenbubble/BmpWrap.smali./org/jfedor/frozenbubble/FrozenGame.smali ./org/jfedor/frozenbubble/Sprite.smali ./org/jfedor/frozenbubble/LevelManager.smali ./org/jfedor/frozenbubble/R$raw.smali
前者就是 org/jfedor/frozenbubble/GameView.java 的編譯輸出,因為是 inner class,獨立編譯出 org/jfedor/frozenbubble/GameView.class$GameThread.smali,由這個 class 命名方式大概就可猜出其重要性,基本上只要能夠控制此 class 的實做,就掌握此 Android 應用程式的行為。而 class LevelManager 顧名思義,看來掌握了遊戲進行的程序控制。先觀察其 method 列表:
smali-src$ grep "\.method" org/jfedor/frozenbubble/LevelManager.smali .method public constructor <init>([BI)V .method private getLevel(Ljava/lang/String;)[[B .method public getCurrentLevel()[[B .method public getLevelIndex()I .method public goToFirstLevel()V .method public goToNextLevel()V.method public restoreState(Landroid/os/Bundle;)V .method public saveState(Landroid/os/Bundle;)V
倘若我們以 "goToFirstLevel" 一類的關鍵字,在 org/jfedor/frozenbubble/GameView$GameThread.smali 檔案中搜尋,可找出有具體的呼叫行為:
smali-src$ grep -r goToFirstLevel * org/jfedor/frozenbubble/GameView$GameThread.smali: invoke-virtual {v2}, Lorg/jfedor/frozenbubble/LevelManager;->goToFirstLevel()V org/jfedor/frozenbubble/LevelManager.smali:.method public goToFirstLevel()V
由此更確定我們之前的猜想。其中組合語言寫為以下:
move-object/from16 v0, p0 iget-object v0, v0, Lorg/jfedor/frozenbubble/GameView$GameThread;->mLevelManager:Lorg/jfedor/frozenbubble/LevelManager; move-object v2, v0 invoke-virtual {v2}, Lorg/jfedor/frozenbubble/LevelManager;->goToFirstLevel()V
字串何其多,依據之前的推測來說,我們要找接近 "LevelManager" 字眼的組合語言程式碼,道理很明顯,從一般 Java programmer 的寫作邏輯去反推。經過一番搜尋,我們找到以下的反組譯程式碼: (位於檔案 org/jfedor/frozenbubble/GameView$GameThread.smali中)
const-string v3, "level" const/4 v4, 0x0 move-object/from16 v0, v25 move-object v1, v3 move v2, v4 invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result p4 new-instance v3, Lorg/jfedor/frozenbubble/LevelManager; move-object v0, v3 move-object/from16 v1, v22 move/from16 v2, p4 invoke-direct {v0, v1, v2}, Lorg/jfedor/frozenbubble/LevelManager;-><init>([BI)V
與 Register v1 相關的組合語言指令有這幾行:(用粗體字標示)
const-string v3, "level" const/4 v4, 0x0 move-object/from16 v0, v25 move-object v1, v3 move v2, v4 invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result p4 new-instance v3, Lorg/jfedor/frozenbubble/LevelManager; move-object v0, v3 move-object/from16 v1, v22 move/from16 v2, p4 invoke-direct {v0, v1, v2}, Lorg/jfedor/frozenbubble/LevelManager;-><init>([BI)V
那麼,看看 Register v2 吧,同樣用粗體字標示相關的指令:
const-string v3, "level" const/4 v4, 0x0 move-object/from16 v0, v25 move-object v1, v3 move v2, v4 invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result p4 new-instance v3, Lorg/jfedor/frozenbubble/LevelManager; move-object v0, v3 move-object/from16 v1, v22 move/from16 v2, p4 invoke-direct {v0, v1, v2}, Lorg/jfedor/frozenbubble/LevelManager;-><init>([BI)V
這個 Register v4 的內含值 "0x0" 會指派到 Register v2 中,而讓我們似乎找到方向了,回頭看看 class org.jfedor.frozenbubble.LevelManager 的 constructor 宣告方式: (之前 grep 結果的第一行)
smali-src$ grep "\.method" org/jfedor/frozenbubble/LevelManager.smali .method public constructor <init>([BI)V
不過,回顧稍早 Register v2 的相關程式碼輸出,其中有兩行需要留意 (以粗體字為主):
invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I move-result p4 new-instance v3, Lorg/jfedor/frozenbubble/LevelManager; move-object v0, v3 move-object/from16 v1, v22 move/from16 v2, p4
"p4" 用以保存 method invocation 之後的回傳值,顯然,Register v2 受到 p4 的指派,也就是被更動為 android.content.Shared.Preference.getInt() method 的回傳值,這存在不確定性,於是,我們乾脆一口氣改掉: (修改的部份會用井字號 "#" 作註解)
# Modified from 0x0 to 0x4" const/4 v4, 0x4 move-object/from16 v0, v25 move-object v1, v3 move v2, v4 # Modified: removed the following 2 lines # invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getInt(Ljava/lang/String;I)I # move-result p4 new-instance v3, Lorg/jfedor/frozenbubble/LevelManager; move-object v0, v3 move-object/from16 v1, v22 # Modified: removed the following 1 line # move/from16 v2, p4 invoke-direct {v0, v1, v2}, Lorg/jfedor/frozenbubble/LevelManager;-><init>([BI)V
改好程式,當然要驗證,回到上一層目錄,透過 smali 提供的組譯器,重新產生 Dalvik DEX 輸出,為了簡化流程,筆者把 smali, apkbuilder, aapt, adb install 都一次整合進去,所以會直接讓 Android Emulator 生效,來看看我們的戰果吧: