Android 台灣中文網
標題:
分析Android程式之解密Crackme
[打印本頁]
作者:
fam1001
時間:
2016-3-24 11:09
標題:
分析Android程式之解密Crackme
破解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
複製代碼
作者:
police4
時間:
2016-4-19 21:05
受益良多,繼續加油!!
作者:
caryvincent
時間:
2016-4-20 12:01
好厲害,解說得很詳細,
不過小弟我還是霧煞煞!
作者:
xinbenxin
時間:
2016-4-21 01:29
好厲害,解說得很詳細
作者:
chunnannn
時間:
2016-8-2 09:39
版大,不好意思,不知道發在這裡適不適合?!
請問一下遊戲的修改數據,通常都是尋找一串dword,修改為另一個dword,分析完smail後,怎麼轉換成這串dword呢?
感謝您!
作者:
680mb
時間:
2016-8-20 18:36
很不錯的入門指導,感謝樓主對初學者的用心。
作者:
oabearoa
時間:
2016-12-15 10:28
樓主~~感謝你的指導,小弟全不會但很努力的看完這篇,有事請教一下,
最後.line 40行調用return-void函數返回。這裏的40是否有誤,是不是42
歡迎光臨 Android 台灣中文網 (https://apk.tw/)
Powered by Discuz! X3.1