[Mac] 將 Android APK 檔案反編譯成 Java 原始碼
最近在研究如何反編譯 (decompile) Android APK~
其實已經有人把一次到位的工具寫出來,
例如 bytecode-viewer、AndroidDecompiler 等等。
不過實際用過的心得,是這些專案通常是組合了其他的工具,
因此專案沒有更新的話,附帶的工具也是舊的…
決定還是自己從頭到尾做一遍,
一方面可以確保各個工具都是最新的狀態,
另一方面可以視情況隨時抽換不同的工具,
獲得更好的反編譯效果~
1. 取出 APK 中的 classes.dex 檔案
以下是 Android 原始碼編譯的簡易過程:
- 原始碼 .java 編譯成 .class 檔案
- 所有的 .class 檔案合併壓縮成 .jar 檔
- .jar 檔編碼成 classes.dex 檔案
- classes.dex 被壓縮放到 .apk 檔案裡面 (APK 其實就是一種 zip 檔)
因此我們想取得原始碼的話,需要將這過程反過來。
先從 APK 中,把 classes.dex 解出來:
unzip test.apk classes.dex
2. 將 classes.dex 檔案解碼成 .jar 檔
我們可以利用 dex2jar 這個工具,
將 classes.dex 檔案解碼回原本的 .jar 檔~
在 Mac 上,用 Homebrew 直接安裝 dex2jar:
brew install dex2jar
dex2jar 裡附了許多 d2j- 開頭的工具,
我們可以用 d2j-dex2jar 將 .dex 檔解碼~
輸入是 classes.dex 時,預設輸出是 classes-dex2jar.jar:
d2j-dex2jar classes.dex
3. 使用 JD-GUI 檢視 .jar/.class 中的原始碼
JAR 檔裡包的是 .class 檔案,
要將 .class 檔案反編譯回 .java 原始碼,
有許多種工具可以做到,例如 JD-GUI~
在 Mac 上用 Homebrew 安裝 JD-GUI:
brew install jd-gui
安裝好後,用 JD-GUI 程式開啟 classes-dex2jar.jar 檔案,
就可以直接看到 .class 對應的 Java 原始碼了:
如果想要將所有的原始碼都儲存下來,
方便用別的方式檢視的話 (如 Sublime Text、或是 grep),
可以用 JD-GUI > File > Save All Sources,
就會將原始碼儲存壓縮成 classes-dex2jar.jar.src.zip,十分方便~
4. 使用 Procyon 反編譯 .jar/.class
依網友所言,JD-GUI 目前似乎還不支援新版 Java 的一些功能 (如 lamda),
一些後起之秀如 Procyon 值得一試~
在 Mac 上,用 Homebrew 安裝 procyon-decompiler:
brew install procyon-decompiler
接著就可以用它來反編譯 .jar 檔了:
procyon-decompiler -o src classes-dex2jar.jar
解完的原始碼就會放在 -o 指定的目錄下。
實際測試結果,procyon 反編譯後的原始碼,可讀性是比 JD-GUI 要好的,
但有時候 JD-GUI 看的到的原始碼,procyon 反而解不開,
這時會在原始碼裡面看到如下的錯誤訊息:
// // This method could not be decompiled. // // java.lang.IllegalStateException: Expression is linked from several locations: Label_0074: // at com.strobel.decompiler.ast.Error.expressionLinkedFromMultipleLocations(Error.java:27) // at com.strobel.decompiler.ast.AstOptimizer.mergeDisparateObjectInitializations(AstOptimizer.java:2592) // at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:235) // at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42) // at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:214) // at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:99) // at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:757) // at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:655) // at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:532) // at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:499) // at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:141) // at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:130) // at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:105) // at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71) // at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59) // at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:317) // at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:238) // at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:138) // throw new IllegalStateException("An error occurred while decompiling this method.");
因此建議各種反編譯工具都可以嘗試看看,可能會有不同效果喔~
參考資料:
A Quick Look at Java Decompilers
How to decompile an APK or DEX file on Android platform?
How to decompile a whole Jar file?