作者:xiaoyuer 合天智匯
前年的時候搞過一點Android逆向,好久沒搞了,最近有個哥們讓我幫他做個Android逆向的小題目,于是拾起來Android逆向的知識重新來搞搞吧,這個apk十分簡單,屬于入門級的Android逆向分析程序,所以本文面向的對象主要是想涉足Android逆向的讀者,讓讀者能夠了解一下Android逆向是怎么回事。我這里附上apk文件,感興趣的最好下載下來實操一下:
鏈接:https://pan.baidu.com/s/17d7zKMjh8rKjj9mUl3J_0Q 提取碼:htrx
引言
Android程序一般是使用JAVA語言開發,通過生成一個apk文件安裝到Android手機上來運行,apk我們很容易獲得,我們通過使用一些反匯編工具可以得知apk文件內部的邏輯,甚至得到他最初的Java源代碼(不完全等同于開發時的源代碼,但是大體上相同)。同時,也可以通過反匯編工具得到類似于smali代碼,smali代碼是一種類似于匯編語言的代碼,適合機器執行,但對于程序員來說就相對晦澀難懂了。
首先,使用apktools和jad-gui工具或者Androidkiller得到反匯編后的smali代碼和java代碼,這兩款工具可以很容易在網上搜到,使用方法也很簡單,這里就不贅述了。下面開始進入代碼分析階段,java代碼比較容易看懂,所以這里就先上java代碼吧:
通過java得到flag
通過上述兩款工具得到的MainActivity的java源碼如下所示:
package com.a.sample.androidtest;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.App.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText editText;
private byte[] s = new byte[]{(byte) 113, (byte) 123, (byte) 118, (byte) 112, (byte) 108, (byte) 94, (byte) 99, (byte) 72, (byte) 38, (byte) 68, (byte) 72, (byte) 87, (byte) 89, (byte) 72, (byte) 36, (byte) 118, (byte) 100, (byte) 78, (byte) 72, (byte) 87, (byte) 121, (byte) 83, (byte) 101, (byte) 39, (byte) 62, (byte) 94, (byte) 62, (byte) 38, (byte) 107, (byte) 115, (byte) 106};
public boolean check() {
byte[] chars = this.editText.getText().toString().getBytes();
if (chars.length != this.s.length) {
return false;
}
int i = 0;
while (i < this.s.length && i < chars.length) {
if (this.s[i] != (chars[i] ^ 23)) {
return false;
}
i++;
}
return true;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((int) R.layout.activity_main);
final Context context = this;
this.editText = (EditText) findViewById(R.id.edit_text);
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (MainActivity.this.check()) {
Toast.makeText(context, "You got the flag!", 1).show();
} else {
Toast.makeText(context, "Sorry your flag is wrong", 1).show();
}
}
});
}
}
猛一看這一段代碼可能有點懵,沒那個耐心去看這段代碼在干嘛,這里可以先嘗試使用安卓模擬器運行后觀察一下程序的運行邏輯,打開后發現是一個輸入框,然后一個check按鈕。如圖所示:
再結合代碼進行分析可知,當點擊check按鈕時,會觸發onClick函數,即如下代碼段:
if (MainActivity.this.check()) {
Toast.makeText(context, "You got the flag!", 1).show();
} else {
Toast.makeText(context, "Sorry your flag is wrong", 1).show();
由此可知如果check函數返回true,會提示You got the flag! 否則,提示Sorry your flag is wrong。下面對check函數進行分析:
public boolean check() {
byte[] chars = this.editText.getText().toString().getBytes();
if (chars.length != this.s.length) {
return false;
}
int i = 0;
while (i < this.s.length && i < chars.length) {
if (this.s[i] != (chars[i] ^ 23)) {
return false;
}
i++;
}
return true;
}
其中,this.s是一個byte型的數組:
private byte[] s = new byte[]{(byte) 113, (byte) 123, (byte) 118, (byte) 112, (byte) 108, (byte) 94, (byte) 99, (byte) 72, (byte) 38, (byte) 68, (byte) 72, (byte) 87, (byte) 89, (byte) 72, (byte) 36, (byte) 118, (byte) 100, (byte) 78, (byte) 72, (byte) 87, (byte) 121, (byte) 83, (byte) 101, (byte) 39, (byte) 62, (byte) 94, (byte) 62, (byte) 38, (byte) 107, (byte) 115, (byte) 106};
首先看到check函數先是檢查輸入的flag和s數組的長度是否相等,之后進行while循環,其中if中的判斷條件為:
this.s[i] != (chars[i] ^ 23)
這里^指的是二進制按位異或。這樣整個邏輯就分析清楚了,那么如何得到正確的flag呢?
有一個需要記住的實用技巧,就是兩次按位異或運算會得到自身,所以我們對s數組再進行一次異或即可得到真實的flag;Python腳本如下:
# -*- coding: utf-8 -*-
def ascii2str():
asciis=[113,123,118,112,108,94,99,72,38,68,72,87,89,72,36,118,100,78,72,87,121,83,101,39,62,94,62,38,107,115,106]
strs=[]
for ascii in asciis:
str=chr(ascii^23)
strs.append(str)
print(''.join(strs))
if __name__ == '__main__':
print("begin!")
ascii2str()
print("finished!")
運行后輸出結果為:flag{It_1S_@N_3asY_@nDr0)I)1|d},
輸入后可知顯示You got the flag! 如圖所示:
通過smali修改跳轉進行破解
前面提到除了Java源碼,還有smali代碼,那既然對Java源碼都已經分析清楚了,為啥還要看smali代碼?smali的好處在于能回編譯,實現破解效果,即不需要對他的算法進行分析,無需知道真實的flag即可讓他顯示You got the flag!那么我們就用smali來搞一下;
MainActivity的smali源碼check函數如下所示:
.method public check()Z
.locals 5
.prologue
const/4 v2, 0x0
.line 15
iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->editText:Landroid/widget/EditText;
invoke-virtual {v3}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v3
invoke-virtual {v3}, Ljava/lang/String;->getBytes()[B
move-result-object v0
.line 16
.local v0, "chars":[B
array-length v3, v0
iget-object v4, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
array-length v4, v4
if-eq v3, v4, :cond_1
.line 22
:cond_0
:goto_0
return v2
.line 18
:cond_1
const/4 v1, 0x0
.local v1, "i":I
:goto_1
iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
array-length v3, v3
if-ge v1, v3, :cond_2
array-length v3, v0
if-ge v1, v3, :cond_2
.line 19
iget-object v3, p0, Lcom/a/sample/androidtest/MainActivity;->s:[B
aget-byte v3, v3, v1
aget-byte v4, v0, v1
xor-int/lit8 v4, v4, 0x17
if-ne v3, v4, :cond_0
.line 18
add-int/lit8 v1, v1, 0x1
goto :goto_1
.line 22
:cond_2
const/4 v2, 0x1
goto :goto_0
.end method
看起來比Java源碼難懂了很多,其實我們目前只需要知道一些關鍵語句的含義就夠了,當然后面如果想深入學習肯,smali代碼越熟悉越好,這里對這里的部分smali進行解釋一下,.method public check()Z 中的Z表示這是bool類型的函數;.locals 5 表明了在這個函數中最少要用到的本地寄存器的個數。.line 15 表明了該代碼在原Java文件中的行數。其余的命令我也忘記了不少,為了避免誤人子弟,我就大概解釋一下主要的內容吧?
invoke-virtual {v3}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
invoke-virtual是調用函數,invoke-static后面有一對大括號“{}”,其實是調用該方法的實例+參數列表。對v3進行分析就會發現它就是我們輸入框輸入的flag,v4就是上述數組里的s;看到這里:
if-eq v3, v4, :cond_1
其實我們已經找到了跳轉點,if-eq 也就是v3 v4相等時跳轉到某處,那么如果我們把它直接改成不相等時跳轉,豈不是輸入除真實的flag外的任何值都會提示You got the flag! 那我們就來試試直接將if-eq改成if-ne,然后使用Androidkiller中的回編譯功能,對他進行簽名,重新安裝運行,發現隨便輸入都提示You got the flag!
AndroidAPK逆向分析
apk反編譯java代碼是進行apk代碼分析、修改的基礎,也能更好的理解apk的包結構。通過本實驗,可掌握Android APK文件的逆向反編譯過程,能夠對APK文件進行簡單的分析,學習相關工具的使用。
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014053009520900001
聲明:筆者初衷用于分享與普及網絡知識,若讀者因此作出任何危害網絡安全行為后果自負,與合天智匯及原作者無關!