破解Android程序一般的方法是將apk文件利用ApkTool反編譯,生成Smali格式的反彙編代碼,而後閱讀Smali文件的代碼來理解程序的運行機制,找到程序的突破口進行修改,最後使用ApkTool從新編譯生成apk文件並簽名,最後運行測試,如此循環,直至程序被成功破解。java
1. 反編譯APK文件linux
ApkTool是跨平臺的工具,能夠在windows平臺與linux平臺下直接使用。使用前到:http://code.google.com/p/android-apktool/ 下載ApkTool,目前最新版本爲1.4.3,Windows平臺須要下載 apktool1.4.3.tar.bz2與apktool-install-windows-r04-brut1.tar.bz2兩個壓縮包,若是是linux系統則須要下載apktool1.4.3.tar.bz2與apktool-install-linux-r04-brut1.tar.bz2,將下載後的文件解壓到同一目錄下。進入到命令行的解壓目錄下,執行apktool命令會列出程序的用法:android
反編譯apk文件的命令爲: apktool d[ecode] [OPTS] <file.apk> [<dir>]shell
編譯apk文件的命令爲: apktool b[uild] [OPTS] [<app_path>] [<out_file>]windows
那麼在命令行下進入到apktool工具目錄,輸入命令:app
$ ./apktool d /home/fuhd/apk/gnapk/nice/com.nice.main.apk outdir
稍等片刻,程序就會反編譯完成,如圖:編輯器
2. 分析APK文件ide
如上例,反編譯apk文件成功後,會在當前的outdir目錄下生成一系列目錄與文件。其中smali目錄下存放了程序全部的反彙編代碼,res目錄則是程序中全部的資源文件,這些目錄的子目錄和文件與開發時的源碼目錄組織結構是一致的。函數
如何尋找突破口是分析一個程序的關鍵。對於通常Android來講,錯誤提示信息一般是指引關鍵代碼的風向標。以書中的註冊示例爲例,在錯誤提示附近通常是程序的核心驗證代碼,分析人員須要閱讀這些代碼來理解軟件的註冊流程。工具
錯誤提示是Android程序中的字符串資源,開發Android程序時,這些字符串可能硬編碼到源碼中,也可能引用 自「res/values」目錄下的strings.xml文件,apk文件在打包時,strings.xml中的字符串被加密存儲爲「resources.arsc」文件保存到apk程序包中,apk被成功反編譯後這個文件也被解密出來了。
以書中2.1.2節運行程序時的錯誤提示,在軟件註冊失敗時會Toast彈出「無效用戶名或註冊碼」,咱們以此爲線索來尋找關鍵代碼。打開「res/values/strings.xml」文件,內容以下:
<?xml version="1.0" encoding="utf-8" ?> <resources> <string name="app_name">Crackme0201</string> <string name="hello_world">Hello world!<string> <string name="menu_settings">Settings</string> <string name="title_activity_main">crackme02</string> <string name="info">Android程序破解演示實例</string> <string name="username">用戶名:</string> <string name="sn">註冊碼:</string> <string name="register">註冊</string> <string name="hint_username">請輸入用戶名</string> <string name="hint_sn">請輸入16位的註冊碼</string> <string name="unregister">程序未註冊</string> <string name="registered">程序已註冊</string> <string name="unsuccessed">無效用戶名或註冊碼</string> <!-- 就是這一行 --> <string name="successed">恭喜您!註冊成功</string> </resources>
開發Android程序時,strings.xml文件中的全部字符串資源都在「gen/<packagename>/R.java」文件的String類中被標識,每一個字符串都有惟一的int類型索引值,使用Apktool反編譯apk文件後,全部的索引值保存在strings.xml文件同目錄下的public.xml文件中。
從上面列表中找到「無效用戶名或註冊碼」的字符串名稱unsuccessed。打開public.xml文件,它的內容以下:
<?xml version="1.0" encoding="utf-8"?> <resources> <public type="drawable" name="ic_launcher" id="0x7f020001" /> <public type="drawable" name="ic_action_search" id="0x7f020000" /> ....... <public type="string" name="unsuccessed" id="0x7f05000c"/> <!-- 這是這一行 --> ....... <public type="id" name="edit_sn" id="0x7f080002" /> <public type="id" name="button_register" id="0x7f080003" /> <public type="id" name="menu_settings" id="0x7f080004" /> </resources>
unsuccessed的id值爲0x7f05000c,在smali目錄中搜索含有內容爲0x7f05000c的文件,最後發現只有MainActivity$1.smali文件一處調用,代碼以下:
# virtual methods .method public onClick(Landroid/view/View;)V .locals 4 .parameter "v" .prologue const/4 v3, 0x0 ...... .line 32 #calls: Locm/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z invoke-static {v0,v1,v2}, Lcom/droider/crackme0201/MainActivity;-> #檢查註冊碼是否合法 access$2(Lcom/droider/crackme0201/MainActivity;Ljava/lang/string;Ljava/lang/String;)Z move-result v0 if-nez v0, :cond_0 #若是結果不爲0,就跳轉到cond_0標號處 .line 34 iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity; .line 35 const v1, 0x7f05000c #unsuccessed字符串,就是這一句 .line 34 invoke-static {v0, v1, v3}, Landroid/widget/Toast;-> makeText(Landroid/content/Context;II) Landroid/widget/Toast; move-result-object v0 ............. .............(略) ............. .end method
Smali代碼中添加的註釋使用「#」號開頭,".line 32"行調用了checkSN()函數進行註冊碼的合法檢查,接着下面有以下兩行代碼:
move-result v0 if-nez v0, :cond_0
checkSN()函數返回Boolean類型的值。這裏的第一行代碼將返回的結果保存到v0寄存器中,第二行代碼對v0進行判斷,若是v0的值不爲零,即條件爲真的狀況下,跳轉到cond_0標號處,反之,程序順序向下執行。
若是代碼不跳轉,會執行以下幾行代碼:
.line 34 iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity; .line 35 const v1, 0x7f05000c #unsuccessed字符串 .line 34 invoke-static {v0, v1, v3}, Landroid/widget/Toast;-> makeText(Landroid/content/Context;II)Landroid/widget/Toast; move-result-object v0 .line 35 invoke-virtual {v0}, Landroid/widget/Toast;->show()V .line 42 :goto_0 return-void
「.line 34」行使用iget-object指令獲取MainActivity實例的引用。代碼中的->this$0是內部類MainActivity$1中的一個synthetic字段,存儲 的是父類MainActivity的引用,這是Java語言的一個特性,相似的還有->access$0,這一類代碼會在後面進行詳細介紹。「.line 35」行將v1寄存器傳入unsuccessed字符串的id值,接着調用Toast;->makeText()建立字符串,而後調用Toast;->show()V方法彈出提示,最後.line 40行調用return-void函數返回。
若是代碼跳轉,會執行以下代碼:
:cond_0 iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity; .line 38 const v1, 0x7f05000d #successed字符串 .line 37 invoke-static {v0, v1, v3}, Landroid/widget/Toast;-> makeText(Landroid/content/Context;II)Landroid/widget/Toast; move-result-object v0 .line 38 invoke-virtual {v0}, Landroid/widget/Toast;->show()V .line 39 iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity; #getter for:Lcom/droider/crackme0201/MainActivity;->btn_register:Landroid/widger/Button; invoke-static {v0}; Lcom/droider/crackme0201/MainActivity;-> access$3(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/Button; move-result-object v0 invoke-virtual {v0, v3}, Landroid/widget/Button;->setEnabled(Z)V #設置註冊按鈕不可用 .line 40 iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity; const v1, 0x7f05000b # registered字符串,模擬註冊成功 invoke-virtual {v0, v1}, Lcom/droider/crackme0201/MainActivity;->setTitle(I)V goto :goto_0
這段代碼的功能是彈出註冊成功提示,也就是說,上面的跳轉若是成功意味着程序會成功註冊。
3. 修改Smali文件代碼
通過上一小節的分析能夠發現,「.line 32」行的代碼「if-nez v0,:cond_0」是程序的破解點。if-nez是Dalvik指令集中的一個條件跳轉指令。相似的還有if-eqz,if-gez,if-lez等。這些指令會在後面blog中進行介紹,在這裏只須要知道,與if-nez指令功能相反的指令爲if-eqz,表示比較結果爲0或相等時進行跳轉。
用任意一款文本編輯器打開MainActivity$1.smali文件,將「.line 32」行的代碼「if-nez v0,:cond_0」修改成「if-eqz v0,:cond_0」,保存後退出,代碼就算修改完成了。
4. 從新編譯APK文件並簽名
修改完Smali文件代碼後,須要將修改後的文件從新進行編譯打包成apk文件。編譯apk文件的命令格式爲:
apktool b[uild] [OPTS] [<app_path>] [<out_file>],打開命令行進入到apktool工具的目錄,執行如下命令:
$ ./apktool b /home/fuhd/apk/gw/outdir/
不出意外的話,程序就會編譯成功。編譯成功 後會在outdir目錄下生成dist目錄,裏面存放着編譯成功的apk文件。編譯生成的crackme02.apk沒有簽名,還不能安裝測試,接下來須要使用signapk.jar工具對apk文件進行簽名。signapk.jar是Android源碼包中的一個簽名工具。代碼位於Android源碼目錄下的/build/tools/signapk/SignApk.java文件中,源碼編譯後能夠在/out/host/linux-x86/framework目錄中找到它。使用signapk.jar簽名時須要提供簽名文件,咱們在此可使用Android源碼中提供的簽名文件 testkey.pk8與testkey.x509.pem,它們位於Android源碼的build/target/product/security目錄。將signapk.jar,testkey.x509.pem,testkey.pk8,3個文件放到同一目錄,而後在命令提示符下輸入以下命令對APK文件進行簽名:
$ java -jar signapk.jar testkey.x509.pem testkey.pk8 /home/fuhd/apk/gw/outdir/crackme02.apk crackme02Sign.apk
簽名成功後會在同目錄下生成crackme02sign.apk文件。
4. 安裝測試
如今是時候測試修改後的成果了。啓動一個Android AVD,或者使用數據線鏈接手機與電腦,而後在命令提示符下執行如下命令安裝破解後的程序:
$ adb install crackme02sign.apk