如何分析Android程序之破解第一個程序

破解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.bz2apktool-install-windows-r04-brut1.tar.bz2兩個壓縮包,若是是linux系統則須要下載apktool1.4.3.tar.bz2apktool-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-nezDalvik指令集中的一個條件跳轉指令。相似的還有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.pk8testkey.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
相關文章
相關標籤/搜索