--------------------------------

文末有福利哦!

--------------------------------

破解Android程序的方法通常是:使用ApkTool反編譯APK文件,生成smali格式的反彙編代碼;通過閱讀smali文件的代碼來理解程序的運行機制,找到突破口,並對代碼進行修改;使用ApkTool重新編譯生成APK文件並對其進行簽名;運行測試如此循環,直至程序被破解。

在實際分析中,還可以使用IDA Pro直接分析APK文件,使用dex2jarjd-gui配合進行Java源碼級的分析等。這些分析方法會在本書後面的章節中詳細介紹。

反編譯APK 文件

ApkTool是一款常用的跨平臺APK文件反編譯工具,可以在WindowsmacOSUbuntu平臺上使用。訪問https://github.com/ibotpeaches/Apktool,即可下載新版本ApkTooljar包。針對不同的系統,還需要下載相關的運行時包裝腳本,具體如下。

  • Windows版本:https://github.com/iBotPeaches/Apktool/tree/master/s/windows/apktool.bat。
  • macOS版本:https://github.com/iBotPeaches/Apktool/tree/master/s/osx/apktool。
  • Ubuntu版本:https://github.com/iBotPeaches/Apktool/tree/master/s/linux/apktool。

將下載的jar包和腳本放到同一個目錄下,然後將該路徑添加到系統的PATH環境變量中,就完成了ApkTool的安裝。對macOS用戶,更簡單的方式是執行brew install apktool命令來安裝。

進入終端或命令行,執行如下命令,對APK文件進行反編譯。

$apktool d ./app-debug.apk -o outdir

I: Using Apktool 2.2.2 on app-debug.apk

I: Loading resource table...

I: Decoding AndroidManifest.xml with resources...

I: Loading resource table from file: /Users/android/Library/apktool/framework/1.apkI: Regular manifest package...

I: Decoding file-resources...

I: Decoding values */* XMLs...

I: Baksmaling classes.dex...

I: Baksmaling classes2.dex...

I: Copying assets and libs...

I: Copying unknown files...

I: Copying original files...

如無意外,會在當前的outdir目錄下生成反編譯文件。反編譯文件包含一系列目錄和文件,smali目錄中存放了程序的所有反彙編代碼,res目錄中存放的則是程序中所有的資源文件,這些目錄的子目錄和文件的組織結構與開發時源碼目錄的組織結構是一致的。

分析APK 文件

如何尋找突破口是分析一個程序的關鍵。對大部分Android程序來說,錯誤提示信息是指引我們找到關鍵代碼的明燈。錯誤提示代碼附近通常就是程序的核心驗證代碼,我們需要通過閱讀這些代碼來理解軟件的註冊流程。

錯誤提示屬於Android程序中的字符串資源。在開發Android程序時,這些字符串可能會被硬編碼到源碼中,也可能引用自resvalues目錄下的strings.xml文件。APK文件在打包時,strings.xml中的字符串被加密存儲爲resources.arsc文件並保存到APK程序包中;如果APK文件被成功反編譯,這個文件就被解密了。

回顧2.1.2節介紹的以命令行方式生成APK文件的內容,如果軟件註冊失敗,會以Toast的形式彈出提示信息,我們可以以此爲線索來尋找關鍵代碼。res/values/string.xml文件的內容如下,除了系統默認生成的一系列以“abc_”開頭的字符串,都是Crackme0201程序使用的字符串。

<?xml version="1.0" encoding="utf-8"?>

<resources>

<stringname="abc_action_bar_home_deion">Navigate home </string>

<stringname="abc_action_bar_home_deion_format">%1$s, %2$s </string>

<stringname="abc_action_bar_home_subtitle_deion_format">%1$s, %2$s, %3$s </string>

<stringname="abc_action_bar_up_deion">Navigate up </string>

<stringname="abc_action_menu_overflow_deion">More options </string>

<stringname="abc_action_mode_done">Done </string>

<stringname="abc_activity_chooser_view_see_all">See all </string>

<stringname="abc_activitychooserview_choose_application">Choose an app </string>

<stringname="abc_capital_off">OFF </string>

<stringname="abc_capital_on">ON </string>

<stringname="abc_search_hint">Search... </string>

<stringname="abc_searchview_deion_clear">Clear query </string>

<stringname="abc_searchview_deion_query">Search query </string>

<stringname="abc_searchview_deion_search">Search </string>

<stringname="abc_searchview_deion_submit">Submit query </string>

<stringname="abc_searchview_deion_voice">Voice search </string>

<stringname="abc_shareactionprovider_share_with">Share with </string>

<stringname="abc_shareactionprovider_share_with_application">Share with %s </string>

<stringname="abc_toolbar_collapse_deion">Collapse </string>

<stringname="search_menu_title">Search </string>

<stringname="status_bar_notification_info_overflow">999+ </string>

<stringname="abc_font_family_body_1_material">sans-serif </string>

<stringname="abc_font_family_body_2_material">sans-serif-medium </string>

<stringname="abc_font_family_button_material">sans-serif-medium </string>

<stringname="abc_font_family_caption_material">sans-serif </string>

<stringname="abc_font_family_display_1_material">sans-serif </string>

<stringname="abc_font_family_display_2_material">sans-serif </string>

<stringname="abc_font_family_display_3_material">sans-serif </string>

<stringname="abc_font_family_display_4_material">sans-serif-light </string>

<stringname="abc_font_family_headline_material">sans-serif </string>

<stringname="abc_font_family_menu_material">sans-serif </string>

<stringname="abc_font_family_subhead_material">sans-serif </string>

<stringname="abc_font_family_title_material">sans-serif-medium </string>

<stringname="app_name">Crackme0201 </string>

<stringname="hint_sn">請輸入16位的註冊碼 </string>

<stringname="hint_username">請輸入用戶名 </string>

<stringname="info">Android程序破解演示實例 </string>

<stringname="menu_settings">Settings </string>

<stringname="register">註冊 </string>

<stringname="registered">程序已註冊 </string>

<stringname="sn">註冊碼: </string>

<stringname="successed">恭喜您!註冊成功 </string>

<stringname="title_activity_main">Crackme0201 </string>

<stringname="unregister">程序未註冊 </string>

<stringname="unsuccessed">無效用戶名或註冊碼 </string>

<stringname="username">用戶名: </string>

</resources>

在開發Android程序時,string.xml文件中的所有字符串資源都在gen//R.java文件的String類中標識,每個字符串都有唯一的int類型的索引值。使用ApkTool反編譯APK文件後,所有的索引值都保存在與string.xml文件處於同一目錄的public.xml文件中。在以上代碼中,“無效用戶名或註冊碼”的字符串名稱爲“unsuccessed”。

打開public.xml文件,其內容如下。

<?xml version="1.0" encoding="utf-8"?>

<resources>

<publictype="attr"name="drawerArrowStyle"id="0x7f010000"/>

<publictype="attr"name="height"id="0x7f010001"/>

...

<publictype="string"name="app_name"id="0x7f060021"/>

<publictype="string"name="hint_sn"id="0x7f060022"/>

<publictype="string"name="hint_username"id="0x7f060023"/>

<publictype="string"name="info"id="0x7f060024"/>

<publictype="string"name="menu_settings"id="0x7f060025"/>

<publictype="string"name="register"id="0x7f060026"/>

<publictype="string"name="registered"id="0x7f060027"/>

<publictype="string"name="sn"id="0x7f060028"/>

<publictype="string"name="successed"id="0x7f060029"/>

<publictype="string"name="title_activity_main"id="0x7f06002a"/>

<publictype="string"name="unregister"id="0x7f06002b"/>

<publictype="string"name="unsuccessed"id="0x7f06002c"/>

<publictype="string"name="username"id="0x7f06002d"/>

unsuccessed字符串的id值爲0x7f06002c。在outdir目錄中搜索含有“0x7f05000c”字符串的文件,結果什麼都沒找到!這是怎麼回事呢?仔細查看,會發現在outdir/unknown目錄下有一個instant-run.zip文件。使用unzip命令查看其內容,具體如下。

$unzip -l outdir/unknown/instant-run.zip

Archive: outdir/unknown/instant-run.zip

Length Date Time Name

-------- ---- ---- ----

185932 02-21-17 14:04 com.android.support-support-fragment-25.1.1_876a7f4c146abb7a4bfe709db4ae95f698cf7afc-classes.dex

127996 02-21-17 14:04 slice_8-classes.dex

255140 02-21-17 14:04 com.android.support-support-core-ui-25.1.1_4646db78ab051d00387c34b7487ff8cbda35e1d9-classes.dex

296 02-21-17 14:04 slice_9-classes.dex

296 02-21-17 14:04 slice_6-classes.dex

756 02-21-17 14:04 com.android.support-support-v4-25.1.1_1edf8a56efa25471ab24785fa5faf2d1bd2cc943-classes.dex

296 02-21-17 14:04 slice_7-classes.dex

604460 02-21-17 14:04 com.android.support-appcompat-v7-25.1.1_334fa07c2f0aef4b9ed22c27d83f86a39b94f7d9-classes.dex

49196 02-21-17 14:04 com.android.support-support-vector-drawable-25.1.1_03c7df1b6f19e0228ccd7bbc6283520b10ea32a1-classes.dex

228344 02-21-17 14:04 com.android.support-support-media-compat-25.1.1_60e3b20b322593837da8044d821bb0d26522d69d-classes.dex

621644 02-21-17 14:04 com.android.support-support-compat-25.1.1_0e52ea72ab593acbfcd0d0b4a9e8d5e0cb4b61ba-classes.dex

8984 02-21-17 14:04 support-annotations-25.1.1_db3de1499fed4ae6b95f8f78d56192e715c93897-classes.dex

15396 02-21-17 14:04 com.android.support-animated-vector-drawable-25.1.1_af2fb95bbc530e1b2e91c0e6d3c8058c970a69cf-classes.dex

85924 02-21-17 14:04 slice_4-classes.dex

296 02-21-17 14:04 slice_5-classes.dex

296 02-21-17 14:04 slice_2-classes.dex

296 02-21-17 14:04 slice_3-classes.dex

296 02-21-17 14:04 slice_0-classes.dex

102736 02-21-17 14:04 com.android.support-support-core-utils-25.1.1_924320d14eb503e843c1937440bbcf9f4cac7cea-classes.dex

296 02-21-17 14:04 slice_1-classes.dex

-------- -------

2288876 20 files

instant-run.zip中有20DEX文件。可以猜測:ApkTool內部使用baksmaliAPK中的DEX文件反編譯爲smali文件,但ApkTool在反編譯APK時沒有處理instant-run.zip中的DEX文件,因此,在反彙編輸出信息中沒有程序真正的反彙編代碼。

instant-run.zipAndroid Studio在開啓Instant Run後生成的文件。Instant Run技術是在Android Studio 2.2中引入且默認開啓的,其目的是讓程序員在開發Android程序時能夠快速進行編譯,擁有順暢的調試體驗。Instant Run技術的本質是將程序的代碼生成爲一個個片段,以DEX文件的形式打包到instant-run.zip中。在APK啓動時,會有固定的DEX加載啓動程序來加載該文件,這縮短了代碼編譯過程所花費的打包時間。如果不想使用這個默認開啓的功能,可以選擇“SettingsPreferences”選項,在打開的對話框中定位“Build, Execution, Deployment”下的“Instant Run”選項,取消勾選“Enable Instant Run...”選項。

即使開啓了Instant Runinstant-run.zip也只會在Debug版本的APK文件中出現(Release版本會禁用Instant Run技術,因此我們不會看到它)。

調整分析思路,接下來我們需要生成Release版本的APK文件。單擊Android Studio菜單項“BuildGenerate Signed APK”,在彈出的對話框中選擇app模塊,然後單擊“Next”按鈕在選擇Key Store的界面上單擊“Create New...”按鈕,新建一個用於程序簽名的Key Store。將Key Store的密碼設置爲“androidbook”。設置Alias爲“Android”,密碼爲“androidbook”。設置好其他選項,單擊“OK”按鈕,就會生成androidbook.jks文件。以上設置如圖2-5所示。

2-5 生成Release版本的APK文件

回到Android Studio主界面,打開項目的app模塊下的build.gradle文件,會發現多出了signingConfigs這項配置。修正storeFile的路徑爲相對路徑後,其內容如下。

signingConfigs {

config {

keyAlias 'Android'

keyPassword 'androidbook'

storeFile file('../../../ks/androidbook.jks')

storePassword 'androidbook'

}

}

在爲Key Store添加配置信息後,還需要對具體的編譯版本進行設置。可以在Android Studio中通過菜單項“FileProject Structure...”進行設置,更簡單的方法是在buildTypesrelease項下添加如下內容。

signingConfig signingConfigs.config

配置完成後,在終端提示符下執行./gradlew assembleRelease命令,或者單擊Android Studio的菜單項“BuildBuild APK”,都可以生成Release版本的APK文件。編譯後,會得到app-release.apk文件。

使用ApkToolapp-release.apk進行反編譯,操作方法與對app-debug.apk的一樣,只不過要將輸出目錄設置爲outdir_rel

下面使用grep命令來查找錯誤提示信息。grepmacOSUbuntu的自帶命令,在Windows中該命令可以通過Cygwin來安裝。使用grep -r命令,可以在指定的目錄中搜索包含特定字符串的文件。按照上面的分析思路,整個搜索流程及結果如下。

$grep -r "無效用戶名或註冊碼"outdir_rel/

outdir_rel//res/values/strings.xml: <string name="unsuccessed">無效用戶名或註冊碼</string>

$grep -r unsuccessed outdir_rel/

outdir_rel//res/values/public.xml: <public type="string" name="unsuccessed" id="0x7f06002c" />

outdir_rel//res/values/strings.xml: <string name="unsuccessed">無效用戶名或註冊碼</string>

outdir_rel//smali/com/droider/crackme0201/R$string.smali:.field public static final unsuccessed:I = 0x7f06002c

$grep -r 0x7f06002c outdir_rel/

outdir_rel//res/values/public.xml: <public type="string" name="unsuccessed" id="0x7f06002c" />

outdir_rel//smali/com/droider/crackme0201/MainActivity$1.smali: const v1, 0x7f06002c

outdir_rel//smali/com/droider/crackme0201/R$string.smali:.field public static final unsuccessed:I = 0x7f06002c

除了public.xmlR$string.smali資源文件,發現只有MainActivity$1.smali文件一處進行了調用,代碼如下(smali代碼中的註釋以“#”開頭)。

$cat outdir_rel/smali/com/droider/crackme0201/MainActivity$1.smali

.class Lcom/droider/crackme0201/MainActivity$1;

...

#virtual methods

.method public onClick(Landroid/view/View;)V

.locals 4

.param p1, "v" # Landroid/view/View;

.prologue

const/4 v3, 0x0

.line 32

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

iget-object v1, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

# getter for: Lcom/droider/crackme0201/MainActivity;->edit_userName:Landroid/widget/EditText;

invoke-static {v1}, Lcom/droider/crackme0201/MainActivity;->access$000(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/EditText;

move-result-object v1

invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;

move-result-object v1

invoke-virtual {v1}, Ljava/lang/Object;->toString()Ljava/lang/String;

move-result-object v1

invoke-virtual {v1}, Ljava/lang/String;->trim()Ljava/lang/String;

move-result-object v1

iget-object v2, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

.line 33

# getter for: Lcom/droider/crackme0201/MainActivity;->edit_sn:Landroid/widget/EditText;

invoke-static {v2}, Lcom/droider/crackme0201/MainActivity;->access$100(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/EditText;

move-result-object v2

invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;

move-result-object v2

invoke-virtual {v2}, Ljava/lang/Object;->toString()Ljava/lang/String;

move-result-object v2

invoke-virtual {v2}, Ljava/lang/String;->trim()Ljava/lang/String;

move-result-object v2

.line 32

# invokes: Lcom/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z

invoke-static {v0, v1, v2}, Lcom/droider/crackme0201/MainActivity;->access$200(Lcom/droider/crackme0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z

move-result v0

if-nez v0, :cond_0

.line 34

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

const v1, 0x7f06002c

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 37

:cond_0

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

const v1, 0x7f060029

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/widget/Button;

invoke-static {v0}, Lcom/droider/crackme0201/MainActivity;->access$300(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, 0x7f060027

invoke-virtual {v0, v1}, Lcom/droider/crackme0201/MainActivity;->setTitle(I)V

goto :goto_0

.end method

.line 32處調用了checkSN()方法進行註冊碼合法性檢查,接着有如下兩行代碼。

move-result v0

if-nez v0, :cond_0

checkSN()方法返回Boolean類型的值。第1行代碼將返回的結果保存到v0寄存器中。第2行代碼對v0寄存器進行判斷,如果其值不爲0(即條件爲真)就跳轉到cond_0標號處,反之程序繼續執行。

如果代碼不跳轉,會執行如下代碼。

.line 34

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->this$0:Lcom/droider/crackme0201/MainActivity;

const v1, 0x7f06002c

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(對這類代碼,會在本書後面的章節中詳細介紹)。const v1, 0x7f06002c指令將v1寄存器傳入unsuccessed字符串的id值。接下來,調用Toast;->makeText()方法創建字符串,在.line 35處將其顯示出來。

如果代碼跳轉,則執行如下代碼。

.line 37

:cond_0

iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$ 1;-> this$ 0:Lcom/droider/crackme0201/MainActivity;

constv1, 0x7f060029

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/widget/Button;

invoke- static{v0}, Lcom/droider/crackme0201/MainActivity;->access$ 300(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;

constv1, 0x7f060027

invoke- virtual{v0, v1}, Lcom/droider/crackme0201/MainActivity;->setTitle(I)V

goto:goto_0

以上代碼的功能是彈出註冊成功的提示,也就是說,這裏的跳轉成功意味着程序註冊成功。

修改smali文件的代碼

通過2.2.3節的分析可以發現,.line 32處的代碼if-nez v0, :cond_0是程序破解的關鍵點。if-nezDalvik指令集中的一個條件跳轉指令,與之類似的指令有if-eqzif-gezif-lez等(這些指令會在本書的第3章中介紹)。在這裏,讀者只需要知道:與if-nez指令功能相反的指令爲if-eqz,表示在比較結果爲0或相等時跳轉。

用任意一款文本編輯器打開MainActivity$1.smali文件,將.line 32處的if-nez v0,:cond_0改爲if-eqz v0, :cond_0,保存後退出,代碼就修改完成了。

重新編譯APK文件並簽名

修改smali文件的代碼後,需要將該文件重新編譯,打包成APK文件。回編譯命令是apktool b。在終端執行如下命令,即可將smali和資源編譯成APK文件。

$apktool b outdir_rel

I: Using Apktool 2.2.2

I: Checking whether sources has changed...

I: Smaling smali folder into classes.dex...

I: Checking whether resources has changed...

I: Building resources...

W: /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res/values-v23/styles.xml:8: error: Error retrieving parent for item: No resource found that matches the given name '@android:style/AlertDialog'.

W:

W: /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res/values-v23/styles.xml:9: error: Error retrieving parent for item: No resource found that matches the given name '@android:style/DialogWindowTitle'.

W:

W: /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res/values-v24/styles.xml:7: error: Error retrieving parent for item: No resource found that matches the given name '@android:style/Animation.InputMethodFancy'.

W:

W: /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res/values-v24/styles.xml:8: error: Error retrieving parent for item: No resource found that matches the given name '@android:style/Animation.VoiceInteractionSession'.

W:

Exception in thread "main" brut.androlib.AndrolibException: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/brut_util_Jar_6345550585086565161.tmp, p, --forced-package-id, 127, --min-sdk-version, 14, --target-sdk-version, 25, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/APKTOOL8773426023413751152.tmp, -0, arsc, -0, arsc, -I, /Users/android/Library/apktool/framework/1.apk, -S, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res, -M, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/AndroidManifest.xml]

at brut.androlib.Androlib.buildResourcesFull(Androlib.java:477)

at brut.androlib.Androlib.buildResources(Androlib.java:411)

at brut.androlib.Androlib.build(Androlib.java:310)

at brut.androlib.Androlib.build(Androlib.java:263)

at brut.apktool.Main.cmdBuild(Main.java:227)

at brut.apktool.Main.main(Main.java:84)

Caused by: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/brut_util_Jar_6345550585086565161.tmp, p, --forced-package-id, 127, --min-sdk-version, 14, --target-sdk-version, 25, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/APKTOOL8773426023413751152.tmp, -0, arsc, -0, arsc, -I, /Users/android/Library/apktool/framework/1.apk, -S, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res, -M, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/AndroidManifest.xml]

at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:440)

at brut.androlib.Androlib.buildResourcesFull(Androlib.java:463)

... 5 more

Caused by: brut.common.BrutException: could not exec (exit code = 1): [/var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/brut_util_Jar_6345550585086565161.tmp, p, --forced-package-id, 127, --min-sdk-version, 14, --target-sdk-version, 25, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /var/folders/rd/mts0362j0n92rq0z1cnmdb580000gn/T/APKTOOL8773426023413751152.tmp, -0, arsc, -0, arsc, -I, /Users/android/Library/apktool/framework/1.apk, -S, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/res, -M, /Users/android/Documents/workspace/Crackme0201/app/build/outputs/apk/outdir_rel/AndroidManifest.xml]

at brut.util.OS.exec(OS.java:95)

at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:434)

... 6 more

從以上代碼中可以看出—回編譯過程出錯了!根據提示信息“at brut.androlib.Androlib.buildResourcesFull(Androlib.java:477)”判斷,錯誤是在打包資源時發生的。目前使用的是ApkTool 2.2.2,而framework-res.apk的版本是基於Android 6.0的,其API23,但Crackme0201API25,因此,出現了資源無法解析的問題。解決方法是:找一臺API25Android 7.1設備,從中獲取framework-res.apk,並將該APK文件安裝到本地。

因爲此處已經打開了Nexus 6 API 25的模擬器,所以只需要執行如下命令。

$adb pull /system/framework/framework-res.apk

[100%] /system/framework/framework-res.apk

然後,執行如下命令,安裝framework-res.apk

$apktool if./framework-res.apk

I: Framework installed to:

/Users/android/Library/apktool/framework/1.apk

安裝後,再次進行回編譯,仍然出錯,提示layout-v22/abcalertdialogbuttonbar_material.xml文件中有一個錯誤。這可能是資源處理上的一個Bug,最簡單也最直接的處理方法是將layout-v22目錄刪除。再次進行回編譯,提示程序執行成功,輸出的內容如下。

$apktool b outdir_rel/

I: Using Apktool 2.2.2

I: Checking whether sources has changed...

I: Smaling smali folder into classes.dex...

I: Checking whether resources has changed...

I: Building resources...

I: Building apk file...

I: Copying unknown files/dir...

回編譯完成後,會在dist目錄下生成app-release-unsigned.apk文件。

不過,通過編譯生成的APK文件是沒有簽名的,因此不能進行安裝和測試。接下來,需要使用signapkAPK文件進行簽名。signapk是一個包裝腳本,其內容如下。

$cat /Users/android/Program/signapk

#!/bin/sh

java -jar ~/Program/signapk_jar/signapk.jar

~/Program/signapk_jar/testkey.x509.pem

~/Program/signapk_jar/testkey.pk8 $1 signed.apk

signapk.jartestkey.x509.pemtestkey.pk8文件可以從Android系統源碼中提取。

執行如下命令,完成APK的簽名操作。

signapk outdir_rel/dist/app-release-unsigned.apk

簽名操作完成後,會在當前目錄下生成signed.apk文件。

安裝和測試

啓動一個Android模擬器,或者使用數據線將Android設備和計算機連接起來,在終端執行adb uninstall命令卸載原來安裝的程序,然後執行adb install命令安裝破解後的程序,輸出的信息如下。

$adb uninstall com.droider.crackme0201

Success

$adb install signed.apk

Success

啓動Crackme0201,在“用戶名”和“註冊碼”輸入框中輸入任意字符,單擊“註冊”按鈕,程序會彈出註冊成功的提示信息,且標題欄的字符會變成“程序已註冊”,如圖2-6所示。

2-6 註冊成功

小結

破解Android程序的操作流程爲:反編譯分析修改回編譯簽名。本節講解的操作都是在命令行下完成的,如果讀者覺得命令行操作比較煩瑣,可以使用一些集成了這些操作步驟的工具,舉例如下。

  • macOS:Android-Crack-Tool,下載地址爲https://github.com/Jermic/Android-Crack-Tool。
  • Windows:Android Killer,下載地址爲http://www.pd521.com。

----------------------------------------------------------------

本文節選自《Android軟件安全權威指南》一書,豐生強著,電子工業出版社出版。

豐生強,網名"非蟲”,獨立軟件安全研究員,資深安全專家,ISC2016安全訓練營獨立講師,有豐富的軟件安全實戰經驗。自2008年起,在知名安全雜誌《黑客防線》上發表多篇技術文章,從此踏上軟件安全研究道路,常年混跡於國內各大軟件安全論壇,著有暢銷安全圖書《Android軟件安全與逆向分析》與《macOS軟件安全與逆向分析》。

《Android軟件安全權威指南》從平臺搭建和語言基礎開始,循序漸進地講解了Android平臺上的軟件安全技術,提供了對Windows、Linux、macOS三個平臺的支持,涉及與Android軟件安全相關的環境搭建、文件格式、靜態分析、動態調試、Hook與注入、軟件保護技術、軟件殼等主題,涵蓋OAT、ELF等新的文件格式。

將Java與Native層的軟件安全技術分開講解,加入了與軟件殼相關的章節,內容安排細緻、合理。

每一章都以實例講解的方式來展開內容,實踐性較強。

--------------------------------------------------------

福利環節到了

在微信訂閱號本篇文章下面留言,集贊,就有機會免費得到本書。下週公佈獲獎名單。

立即行動吧!

--------------------------------------------------------

相關文章