<<Android軟件安全權威指南>>筆記
第二章 如何分析Android程序
編寫一個Android程序
新建一個Android工程,名稱爲Crackme0201
吧
主界面添加用戶名和註冊碼輸入框
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
android:id="@+id/linearLayout">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/top_string"
android:textAlignment="center"
android:gravity="center_horizontal" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/username"/>
<EditText
android:id="@+id/edit_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:layout_weight="1"
android:hint="@string/hint_username"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/regCode"/>
<EditText
android:id="@+id/edit_regCode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="@string/hint_regCode"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1" />
</LinearLayout>
<Button
android:id="@+id/button_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_register"
android:layout_marginRight="10dp"
android:layout_gravity="right"/>
</LinearLayout>
然後,編寫MainActivity
類的代碼,添加一個checkSN()
方法
private boolean checkSN(String userName, String sn) {
try {
if ((userName == null) || (userName.length() == 0)) {
return false;
}
if ((sn == null) || (sn.length() != 16)) {
return false;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest();
String hexstr = toHexString(bytes, "");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < hexstr.length(); i+=2) {
stringBuilder.append(hexstr.charAt(i));
}
String userSN = stringBuilder.toString();
//Log.d("crackme", hexstr);
//Log.d("crackme", userSN);
if (!userSN.equalsIgnoreCase(sn)) {
return false;
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
return true;
}
該方法計算用戶名與註冊碼是否匹配,用MD5計算用戶名的散列值,轉換爲hex串,128bit轉換爲32位十六進制串,再取32位中的奇數位,拼成16位,即爲註冊碼。
最後判斷傳入的註冊碼與計算得出的註冊碼是否匹配。
在MainActivity
中的onCreate()
方法中添加註冊按鈕點擊事件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle(R.string.unregister);
edit_userName = (EditText) findViewById(R.id.edit_username);
edit_sn = (EditText) findViewById(R.id.edit_regCode);
btn_register = (Button) findViewById(R.id.button_register);
btn_register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!checkSN(edit_userName.getText().toString().trim(),
edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this, R.string.unsuccessed,
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, R.string.successed,
Toast.LENGTH_SHORT).show();
btn_register.setEnabled(false);
setTitle(R.string.register);
}
}
});
}
如果匹配,則彈出註冊成功,如果不匹配,則彈出註冊無效的提示
編譯APK文件
有兩種編譯方式,使用命令行和AS的Build菜單
命令行
gradlew是基於task來編譯項目的,在命令行下執行gradlew tasks
會列出所有支持的task,我們主要關注編譯使用的Build tasks和安裝APK時用的Install tasks
生成Debug調試版本的APK:./gradlew assembleDebug
,會在Crackme0201/app/build/outputs/apk
目錄下生成app-debug.apk文件
安裝Debug版本APK:./gradlew installDebug
,會安裝到與系統相連的設備/模擬器
adb devices
,列出當前連接的設備,包括模擬器
AS編譯
直接點擊run app
會自動編譯安裝
只編譯不安裝點make project
破解第一個Android程序
以Crackme0201爲例
通常方法是,使用ApkTool
反編譯APK文件,生成smali格式的反彙編代碼,通過閱讀smali代碼理解程序運行機制,找到突破口進行修改,再使用ApkTool
工具重新編譯爲APK並簽名,進行測試
上述過程循環,直到完全破解
實際中還可以用IDA Pro
直接分析APK文件,使用dex2jar
和jd-gui
配合進行Java源碼級分析
反編譯APK文件
下載ApkTool工具,添加路徑到PATH環境變量,命令行直接使用
apktool d ./apk-debug.apk -o outdir
會在outdir
文件夾下生成反編譯文件,smali
目錄下是程序的所有反彙編代碼,res
目錄下是程序所有資源文件,子目錄的文件結構與開發源碼一致。
分析APK文件
找到突破口,一般是錯誤提示信息,屬於字符串資源
APK打包時,string.xml
文件被加密存儲爲resources.arsc
文件,反編譯會解密
string.xml
中的所有字符串資源都在gen/R.java
文件中的String類中標識,每個字符串都有int類型的索引值,對應關係在同目錄下的public.xml
文件中
找到unsuccessed
字符串和對應索引值
但是搜索文件夾下沒有與索引值相關的信息
接下來生成Release版本APK文件
用AS進行簽名生成app-release.apk,再用apktool進行反編譯
找到unsuccessed
對應的索引值,全局搜索,在MainActivity$1.smali
中找到了索引值
在.line 33
處調用checkSN()
(但是這裏是access$000
,沒有#備註,不知道爲啥)
.line 33
invoke-static {p1, v0, v1}, Lorg/nuaa/crackme0201/MainActivity;->access$000(Lorg/nuaa/crackme0201/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z
move-result p1
const/4 v0, 0x0
if-nez p1, :cond_0
意思是,返回Boolean
類型值(哪裏看出來的?)
返回結果保存在p1
move-result p1
忽略掉第二行,對寄存器p1進行判斷,其值不爲0,即條件爲真則跳轉cond_0處
const/4 v0, 0x0
if-nez p1, :cond_0
繼續往下看,不跳轉會執行
.line 35
iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
const v1, 0x7f0b0032
invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object p1
.line 36
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
goto :goto_0
在.line 35
處事要iget-object
指令獲取MainActivity
的實例的引用,-this$0
是內部類MainActivity$1
中的一個synthetic
字段,存儲父類MainActivity的引用
const v1, 0x7f0b0032
該指令是將v1寄存器傳入0x7f0b0032,也就是unsuccessed的id值,接下來調用Toast;->makeToast()
如果跳轉cond_0,則執行如下指令
.line 38
:cond_0
iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
const v1, 0x7f0b002f
invoke-static {p1, v1, v0}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object p1
.line 39
invoke-virtual {p1}, Landroid/widget/Toast;->show()V
.line 40
iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
iget-object p1, p1, Lorg/nuaa/crackme0201/MainActivity;->btn_register:Landroid/widget/Button;
invoke-virtual {p1, v0}, Landroid/widget/Button;->setEnabled(Z)V
.line 41
iget-object p1, p0, Lorg/nuaa/crackme0201/MainActivity$1;->this$0:Lorg/nuaa/crackme0201/MainActivity;
const v0, 0x7f0b002c
invoke-virtual {p1, v0}, Lorg/nuaa/crackme0201/MainActivity;->setTitle(I)V
:goto_0
return-void
這裏的代碼是註冊成功時候的邏輯
修改smali文件代碼
通過上述分析,,line 32
處的代碼,if-nez p1, :cond_0
是跳轉關鍵點
這是Dalvik指令集的一個條件跳轉指令(和彙編差不多),應該是not equal zero,不等於0的意思,相反的指令是if-eqz
,表示等於0時跳轉
就用if-eqz
代替if-nez
,保存修改
if-eqz p1, :cond_0
重新編譯APK並簽名
修改好以後,使用apktool重新編譯打包
apktool b outdir_rel
在outdir_rel/dist
目錄下生成apk文件
要安裝還需要簽名,之前簽名的時候生成了證書,我們可以用命令行來對apk進行簽名
apksigner sign --ks [xxx.jks] --out [xxx.apk] [signed.apk]
apksigner
是Android的build-tool中自帶的,--ks
後接數字證書存儲路徑,--out
後接簽名後apk輸出路徑,最後是原apk路徑
在Terminal下,模擬器開啓的情況下,推出原應用,輸入命令卸載
adb uninstall org.nuaa.crackme0201
再安裝簽名後的apk
adb install signed.apk
安裝成功,測試隨意的用戶名+註冊碼都可以驗證成功。
總結
一般流程
反編譯 -> 分析 -> 修改 -> 回編譯 -> 簽名
集成工具:MAC下Android-Crack-Tool
,Windows下Android Killer