2018年1月22日 星期一

[Android] ProGuard 混淆程式碼及反編譯教學


這個主題網路上搜尋有著千千萬萬的文章,
通常混淆跟反編譯寫在一起的文章比較少,
因此我還是想寫得更完整,以我自己的方式記錄一遍這樣的過程。
如有任何地方有誤都歡迎指教!




為什麼需要混淆

Android App 只要把 .apk 檔換成 .zip 我們就可以將它解壓縮,看到裡面的 resource 以及 dex 檔,
很容易利用相關工具例如 dex2jar、jd-gui ,便可以把裡面的程式碼看的一覽無遺,當然如果是開源的話,就沒關係,但如果我們是商業用 apk ,安全疑慮無疑是個大漏洞,利用混淆讓程式碼可讀性降低,延緩破解速度,不僅有此好處,透過 Proguard 我們可以藉此優化 apk 的大小,它會幫我們移除不必要的程式碼,簡化程式碼的寫法。

Proguard 的優點

https://www.youtube.com/watch?v=5frxLkO4oTM
這裡有個不錯的影片告訴我們其實 proguard 不只混淆,它影響了 apk 大小,也減少了 method 數。( java 有限制 method 數量 限制在 65535 ,只要超過了就會編譯失敗,當然現在有 multidex 解決了問題,不過 apk 大小仍然是影響著使用者的下載意願)
https://lab.getbase.com/proguard-for-android/
這篇文章更是一步一步帶領我們實際驗證透過 proguard 優化了 apk。使用 dependencies 掛越多第三方 lib 會越有感覺, apk 的大小不斷的在增長。
相關指令:
./gradlew assembleDebug
打包 apk 後可以看見包完的 總時間
dexcount ./app/build/outputs/apk/app-debug.apk
分析 apk 使用的 method 數量

Proguard 混淆優化程式碼

透過 proguard 混淆程式碼後, class name 、 method name 、變數,都會變成 a、b、c…這種難以閱讀的樣子,並且移除 log、註解 ,簡化一些複雜的程式邏輯。這些都可以透過反編譯後看見。


1
2
3
4
5
6
7
8
9
10
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}

在 gradle 打開混淆的設定方式就是 minifyEnabled true , sdk 有一個預設寫好的 proguard 檔案就是proguard-android.txt,而後面的 proguard-rules.pro 就是我們自己的 proguard rule 的檔案。
打開 proguard-rules.pro 後,要寫 App 有哪些地方需要保留不被 proguard 混淆優化,通常第三方 SDK 在文件都會提供 rule ,那些都要寫進去 proguard file,我們自己 App 的 code 要保留的 code rule 注意事項。
  1. Activity 、 View 、 Broadcast….四大組件,這些需要被保留,因為 AndroidManifest.xml 註冊著它們的 name ,如果混淆了,會找不到。
  2. JNI 程式碼需保留
  3. 運行的程式碼需保留(例如有反射,尋找 name 的相關 method 都需要保留 (App 最常遇到要保留的是 gson mapping 的物件)、序列化物件…)
    有一個更完整的寫法,算是比較廣泛 App 會使用到的 rule
    https://gist.github.com/albinmathew/c4436f8371c9c41461ab
不過實際上建議依據自己的 App 去調配你要保留的規則,通常會在 release apk 做混淆,混淆後還是需要安裝 apk 測試一下那些功能壞掉了,然後保留那個部分,並且搭配反編譯去看 code 解析問題點。
-keep class com.squareup.okhttp.** { *; }
遇到問題的萬用保留法,這個會讓 package 下 所有包含子 package 還有 class 、 method 、 變數都保留不受 proguard 混淆優化。
-dontwarn com.squareup.okhttp.**
打包 apk 打開 proguard 時,會有一些 warn 警告導致打包 apk fail ,此 rule 也會比較常用到。

混淆後的 Exception 追蹤

最麻煩的莫過於這個了,proguard 打開後,連 crash 的行數都找不到了,如果使用者有問題工程師 debug 也麻煩,Crashlytics SDK 官方其實有提供相關的保留規則。
https://docs.fabric.io/android/crashlytics/dex-and-proguard.html
-keepattributes *Annotation*
保留注解
-keepattributes SourceFile,LineNumberTable
保留 crash 有意義的錯誤回報、行數
-keep public class * extends java.lang.Exception
保留 Exception
-printmapping mapping.txt
產出混淆前後的差異文件檔
為什麼要產生 mapping.txt 是因為即使寫了保留以上的規則,但 crash 時還是顯示混淆後的行數、package name,其實行數不對,看到的 package name 也都是 com.a.b.c ,所以需要倚賴一個 sdk 附的工具 retrace 和 mapping.txt 再次把錯誤訊息解出來。
retrace.bat -verbose mapping.txt obfuscated_trace.txt
還有一個圖形化界面的工具 proguardgui.sh 幫助我們,
路徑大概是長這樣,要看你把 Android sdk 放哪個檔案。
xxx/Android/sdk/tools/proguard/bin/proguardgui.sh

反編譯 APK

這邊教學不用 dex2jar 工具,多次使用後發現其實有時無法真實還原出 apk ,還會產出 error 檔,google 一輪後發現還真不好處理,猜測是因為專案有使用了 kotlin ,因此 error 檔出現了 data class hashcode 的錯誤 (這真的網路上沒找到甚麼好的解法,討論極少),故發現了一個更好用的工具幫助我們反編譯,名為 enjarify,重點是它是 google 官方的工具
enjarify github 連結:https://github.com/google/enjarify
使用它只需要安裝 python3 以及 把它 clone 下來執行即可。
官方有特別說明 dex2jar 其實是較為老舊的一個工具,不建議使用的原因皆寫在 github 上。

總結

其實有了 Proguard 無法保證 Apk 絕對安全,它能保證做一定程度的優化是無庸置疑的,更深入的混淆可以用付費的 Dexguard ,它可以連字串都幫你做加密,不過費用是年費,且不便宜,或是有其他 Apk 加殼的方式,不過付費方案的討論較少,請斟酌自己的需求選用。
相關介紹:https://www.guardsquare.com/zh-hans/blog/dexguard-vs-proguard

相關連結

沒有留言:

張貼留言