馬上加入Android 台灣中文網,立即免費下載應用遊戲。
您需要 登錄 才可以下載或查看,沒有帳號?註冊
x
破解Android程式通常的方法是將apk檔案利用ApkTool反編譯,生成Smali格式的反彙編代碼,然後閱讀Smali檔案的代碼來理解程式的執行機制,找到程式的突破口進行修改,最後使用ApkTool重新編譯生成apk檔案並簽名,最後執行測試,如此循環,直至程式被成功破解。
1. 反編譯APK檔案
ApkTool是跨平台的工具,可以在windows平台與linux平台下直接使用。目前最新版本為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命令會列出程式的用法:
反編譯apk檔案的命令為: apktool d[ecode] [OPTS] <file.apk> [<dir>]
編譯apk檔案的命令為: apktool b[uild] [OPTS] [<app_path>] [<out_file>]
那麼在命令行下進入到apktool工具目錄,輸入命令:
- $ ./apktool d /home/fuhd/apk/gnapk/nice/com.nice.main.apk outdir
複製代碼
2. 分析APK檔案
如上例,反編譯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
複製代碼 |