原文鏈接 http://www.woaitqs.cc/2016/07/07/a-method-to-protect-your-android-code.html
在某個風不平浪不靜的日子里,接到了一個技術調研的任務,這個技術相對而言畢竟繁瑣且門檻很高,于是我在網上搜尋相應的應用,看是否能從他們混淆后的代碼中得到一些思考。在經歷一段波折的反編譯后,雖然對調研需要的內容沒有起到太大的作用,反倒是學到了一些如何保護代碼的不太常見的技巧,大有無心插柳柳成蔭的意思,于是專門寫這篇文章分享給大家。
如果對應用打包過程和常見反編譯方法都有所了解,可以跳過這兩部分,直接看最后的章節.
Android 打包過程
要進行逆向工程,反編譯項目,那么首先得知道順向工程是如何進行的,即我們得知道怎么打包的。Android 的打包過程是通過各種工具將你的工程項目轉換成 APK 格式的可安裝程序,這個過程其實非常靈活,在每個步驟可以做很多自定義的事情,甚至你可以完全手動完成整個步驟,正是這樣靈活的過程也給許多黑色產業可乘之機。接下來我們看看具體的打包過程。
1) 打包 res 目錄下的資源
這里用到了 Android Asset Packaging Tool (aapt) 這個工具,如同名字所述,這個工具負責對 Android 中用的資源進行打包,實際上這個工具能提供的功能還不只打包,還可以查看 APK 相應的信息(權限、資源表、label和Icon 等等),也可以對資源進行增刪改查。
這里列舉一些參見的用法,對 AAPT 感興趣的,可以通過 aapt --help
的進行了解。
# 查看前面10個資源文件
$ aapt list weibo.apk | head -n 10
AndroidManifest.xml
assets/AZURE.png
assets/BLUE.png
assets/CYAN.png
assets/Common.zip
assets/GREEN.png
assets/LineRound.pvr
assets/MAGENTA.png
assets/MusicAssets.zip
assets/MusicVideoAssets.zip
# 查看 APK 權限
$ aapt dump permissions weibo.apk
package: com.sina.weibo
permission: com.sina.weibo.data.sp.dbsharedpreference
uses-permission: com.sina.weibo.data.sp.dbsharedpreference
uses-permission: android.permission.AUTHENTICATE_ACCOUNTS
uses-permission: android.permission.WRITE_SYNC_SETTINGS
uses-permission: android.permission.MANAGE_ACCOUNTS
uses-permission: android.permission.READ_SYNC_SETTINGS
uses-permission: android.permission.USE_CREDENTIALS
uses-permission: android.permission.READ_SYNC_STATS
...
uses-permission: android.permission.GET_ACCOUNTS
# 打包資源到對應的APK中去,并生成相應的 R 文件。
aapt p[ackage] -f -S <res路徑> -I <android.jar路徑> -A <assert路徑> -M <AndroidManifest.xml路徑> -F <輸出的包目錄+包名>
2) AIDL 的編譯
(Android Interface definition language)AIDL 是方便我們進行進程間通訊的語法糖。Android 同樣提供了工具來方便我們進行對 AIDL 的編譯,在 SDK 中的 build-tools 目錄下。具體與 AIDL 相關的知識,可參看 Android Binder 全解析(3) -- AIDL原理剖析
3) 生成對應的 class 文件,將 Class 文件進行打包
我們書寫的是 Java 文件與平臺無關,但實際運行環境各有差異,于是需要生成相應的中間文件來幫我們屏蔽這些差異,對于 java 而言這就是 Class 文件。這一步完成的工作就是將 R 文件、AIDL 生成的代碼和 src 中的java文件編譯成 class 文件。JDK 中就帶有這個工具,Java programming language compiler(javac)。
但 Android 采用的虛擬機不是 JVM,而是針對移動設備進行優化后的 Davlik 或者 ART 虛擬機,不能簡單地使用 class 文件,而是需要將 class 文件轉成 smali 格式文件,這個步驟是通過 android-sdk/platforms/android-X/tools/lib/
目錄下的 dx 工具生成 classes.dex
文件,這個 Dex 文件就含有工程中的所有 java 文件。
題外是說一句,大家可以看看 Scala 等同樣基于 JVM 來實現的語言,這樣會更明顯地意識到當初虛擬機這套設計帶來的價值。
4) 打包并簽名
通過 apkbuilder 工具將所有 dex 文件、編譯后的資源一并打包到 apk 文件中。但是此時生成的 APK 包不具備簽名,是無法在手機上安裝的。具體的簽名流程,可以在 Sign Your App 里進行查看。
在開發過程中,主要用到的就是兩種簽名的 keystore 。一種是用于調試的 debug.keystore,它主要用于調試,在 Eclipse 或者 Android Studio 中直接 run 以后跑在手機上的就是使用的 debug.keystore。另一種就是用于發布正式版本的 keystore。
在簽名結束后,使用 zipalign 進行相應 APK 的優化,至此這個打包過程結束。
最后這個 APK 目錄所包含的內容就如下表所示:
常見反編譯方法
知道打包是怎么進行后,就可以辦輕松地進行反編譯的工作,我們感興趣的內容無非是其中的資源和其中的代碼,根據前面提到的打包知識,代碼主要在 classes.dex 文件中,因此我們需要做的是兩件事情,一是得到被壓縮處理的資源文件,二是從 classes.dex 文件中得到相應的代碼。
1) 獲取對應的資源文件
這里需要使用到 Apktool 這個工具來幫助我們完成對應的獲取資源的任務。這個工具只要有兩個功能,一是反編碼資源文件,使得其盡量具有高的可讀性;二是解析 dex 文件,得到可讀性稍微高點的 smali 文件。smali 文件就類似于 JVM 中的 class 文件,這里的 smali 文件就是 Davlik 中的 class 文件,其具體的語法可以參考鏈接 http://androidcracking.blogspot.hk/search/label/smali
apktool 有非常豐富的語法,支持較多的功能,這里只是簡單地說一下反編譯的用法。
# apktool d -o filePath apkPath
# 解析 APK 的資源到指定的 filePath 中去.
$ apktool d -o test.out test.apk
I: Using Apktool 2.1.1 on test.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: 1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
反編譯后,會得到如下所示的 APK 文件,在這種情況下,就可以查看 AndroidManifest 文件,以及 res 目錄下的各類文件。
2) 解析 classes.dex 文件
APK 包本事就是一個 zip 包,所以直接將后綴名改成 zip,就可以進行解壓了,解壓后就能看到里面的 classes.dex 文件,現在的工作就是講 classes.dex 轉成日常可讀的 jar 文件。這里就需要用到另一個工具 dex2jar, 這個工具在絕大多數情況下都能滿足我們的需要。
./d2j-dex2jar.sh classes.dex
在這個步驟后,就能得到 classes-dex2jar.jar
,為了更方便地閱讀 jar 文件,可以使用 JD-GUI 進行代碼閱讀。
保護代碼資源
現在知道了打包過程,和反編譯的基本用法,那么如何保護自身呢?如何使得盡可能地將自己的內容保護好?沒有絕對的安全,就像《美國隊長3》里面說的, 有經驗有耐心,是沒什么做不到的
,我們只有盡可能地增加反編譯的難度,才能盡可能地保護好自身。解決這些問題,最好的做法,可能是類似于 iOS 的做法,把這些過程完全納入自身控制中。
1) 資源的混淆
在使用資源的時候,都會盡量用一些可讀性較高的命名,例如 layout/card_list_item.xml
,使用 apktools 后就能看到這些名字,那么反編譯的人就能按圖索驥地找到對應的邏輯。對資源命名進行混淆后,能改善這種狀況。
混淆主要是將有意義的名字變成無意義的代號,這樣反編譯者就不能從中得到什么有用的信息,例如
R.string.name -> R.string.a
res/drawable/icon -> r/s/a
目前對資源進行混淆,主要方案是基于 resources.arsc 的修改。微信Android資源混淆打包工具 中詳細地說明了原理,有興趣的同學可以去看看。
筆者針對某個 APK 進行了資源混淆,不僅對資源進行了混淆,也使得包大小減少了不少,有興趣的可以試試,效果如圖所示。
2) 代碼的混淆
資源可以進行混淆,代碼更是可以,而且代碼混淆由來已久,也有完備的解決方案。目前主要采用了 Progruad 來進行,Proguard 通過刪除無用代碼,將代碼中類名、方法名、屬性名用晦澀難懂的名稱重命名從而達到代碼混淆、壓縮和優化的功能。關于 Progruad 用法的文章,可以參考這個鏈接 Android中ProGuard配置和總結。這里就不再詳細地說明用法,網上的教程足夠多,直接看看混淆后的效果。
3) 一種別樣的技巧
盡管進行了混淆,但有些類由于特殊性是無法被混淆的,即便被混淆了,也能通過 java 中的一些方法調用看出蛛絲馬跡。那么還有什么好的方法嗎?可能有人說,通過插件的方式將代碼從網上下載下來,這樣也不是很好,比較你的核心邏輯也喜歡在無網的時候可以使用。于是,就想到一種方法,將代碼在 APK 包里面藏起來,再動態加載。
這里先提及在 PC 時代,被廣泛使用的一種技巧 -- 加殼,這種技巧常被用于病毒和軟件保護。
利用特殊的算法,對可執行文件里的資源進行壓縮,只不過這個壓縮之后的文件,可以獨立運行,解壓過程完全隱蔽,都在內存中完成。它們附加在原程序上通過加載器載入內存后,先于原始程序執行,得到控制權,執行過程中對原始程序進行解密、還原,還原完成后再把控制權交還給原始程序,執行原來的代碼部分。加上外殼后,原始程序代碼在磁盤文件中一般是以加密后的形式存在的,只在執行時在內存中還原,這樣就可以比較有效地防止破解者對程序文件的非法修改,同時也可以防止程序被靜態反編譯。
將代碼藏起來的方式,也就是在 Android 上使用加殼技術。那么首要問題是在哪里加殼?Android 官方提供的 assert 目錄,就是一個比較合適的地方,其后可以通過 AssertManager 來進行訪問。另一個問題是對什么加殼,這里可以使用 jar 包,Android 是可以動態加 jar 包加載進入到 classLoader 中的。最后一個問題是怎么藏起來,我們可以將 jar 包藏在某個具有虛假的資源中,例如 icon.png 下,換而言之是,看上去是 png ,實際也可以打開,但在其中有被隱藏的 jar 文件。
有不少現成的工具,可以使用這樣的技術,可以實現將圖藏起來的功能。我在這里,找到 sf.net 上面的一個工具, hide in picture,可以藏任何文件,并且支持對這些文件進行加密,也就是說沒有密鑰,就算知道這樣的加殼技術,也不見得能夠得到被隱藏的內容。
可以看看下圖,1.1M 的 icon.png 里面是否藏著什么秘密了?
接下來在應用啟動時,將其中的 icon.png 后面的 jar 包讀取出來,并加載到 classLoader 中,就可以正常啟動了。下面的 smali 代碼,就是其他應用正在解析 icon.png 背后東西的部分代碼。
const-string v6, "icon.png"
invoke-virtual {v3, v6}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;
move-result-object v16
const-wide/16 v6, ----
move-object/from16 v0, v16
invoke-virtual {v0, v6, v7}, Ljava/io/InputStream;->skip(J)J
可能有人好奇,我是怎么想到這種方案的?嘿嘿,不告訴你。
即便使用了這種方法,也不能保定周全,有些開發者可以直接進行二次打包。而且反編譯應用包后,修改資源再二次打包,并發布出去這樣的做法,已經形成一個灰色產業鏈,特別是對國外的游戲進行修改并加廣告層出不窮,在這樣相對惡劣的情況下,應用開發商特別是獨立開發者要學會用合適的方式來保護好自身。
期待一個更好,更健康的 Android 生態圈~
參考文章
文檔信息
- 版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)
- 發表日期:2016年7月7日
- 社交媒體:weibo.com/woaitqs
- Feed訂閱:www.woaitqs.cc/feed.xml