作者:xiaoyuer 合天智匯
前年的時(shí)候搞過一點(diǎn)Android逆向,好久沒搞了,最近有個(gè)哥們讓我?guī)退鰝€(gè)Android逆向的小題目,于是拾起來(lái)Android逆向的知識(shí)重新來(lái)搞搞吧,這個(gè)apk十分簡(jiǎn)單,屬于入門級(jí)的Android逆向分析程序,所以本文面向的對(duì)象主要是想涉足Android逆向的讀者,讓讀者能夠了解一下Android逆向是怎么回事。我這里附上apk文件,感興趣的最好下載下來(lái)實(shí)操一下:
鏈接:https://pan.baidu.com/s/17d7zKMjh8rKjj9mUl3J_0Q 提取碼:htrx
引言
Android程序一般是使用JAVA語(yǔ)言開發(fā),通過生成一個(gè)apk文件安裝到Android手機(jī)上來(lái)運(yùn)行,apk我們很容易獲得,我們通過使用一些反匯編工具可以得知apk文件內(nèi)部的邏輯,甚至得到他最初的Java源代碼(不完全等同于開發(fā)時(shí)的源代碼,但是大體上相同)。同時(shí),也可以通過反匯編工具得到類似于smali代碼,smali代碼是一種類似于匯編語(yǔ)言的代碼,適合機(jī)器執(zhí)行,但對(duì)于程序員來(lái)說就相對(duì)晦澀難懂了。
首先,使用apktools和jad-gui工具或者Androidkiller得到反匯編后的smali代碼和java代碼,這兩款工具可以很容易在網(wǎng)上搜到,使用方法也很簡(jiǎn)單,這里就不贅述了。下面開始進(jìn)入代碼分析階段,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();
}
}
});
}
}
猛一看這一段代碼可能有點(diǎn)懵,沒那個(gè)耐心去看這段代碼在干嘛,這里可以先嘗試使用安卓模擬器運(yùn)行后觀察一下程序的運(yùn)行邏輯,打開后發(fā)現(xiàn)是一個(gè)輸入框,然后一個(gè)check按鈕。如圖所示:

再結(jié)合代碼進(jìn)行分析可知,當(dāng)點(diǎn)擊check按鈕時(shí),會(huì)觸發(fā)onClick函數(shù),即如下代碼段:
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函數(shù)返回true,會(huì)提示You got the flag! 否則,提示Sorry your flag is wrong。下面對(duì)check函數(shù)進(jìn)行分析:
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是一個(gè)byte型的數(shù)組:
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函數(shù)先是檢查輸入的flag和s數(shù)組的長(zhǎng)度是否相等,之后進(jìn)行while循環(huán),其中if中的判斷條件為:
this.s[i] != (chars[i] ^ 23)
這里^指的是二進(jìn)制按位異或。這樣整個(gè)邏輯就分析清楚了,那么如何得到正確的flag呢?
有一個(gè)需要記住的實(shí)用技巧,就是兩次按位異或運(yùn)算會(huì)得到自身,所以我們對(duì)s數(shù)組再進(jìn)行一次異或即可得到真實(shí)的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!")
運(yùn)行后輸出結(jié)果為:flag{It_1S_@N_3asY_@nDr0)I)1|d},

輸入后可知顯示You got the flag! 如圖所示:

通過smali修改跳轉(zhuǎn)進(jìn)行破解
前面提到除了Java源碼,還有smali代碼,那既然對(duì)Java源碼都已經(jīng)分析清楚了,為啥還要看smali代碼?smali的好處在于能回編譯,實(shí)現(xiàn)破解效果,即不需要對(duì)他的算法進(jìn)行分析,無(wú)需知道真實(shí)的flag即可讓他顯示You got the flag!那么我們就用smali來(lái)搞一下;
MainActivity的smali源碼check函數(shù)如下所示:
.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
看起來(lái)比Java源碼難懂了很多,其實(shí)我們目前只需要知道一些關(guān)鍵語(yǔ)句的含義就夠了,當(dāng)然后面如果想深入學(xué)習(xí)肯,smali代碼越熟悉越好,這里對(duì)這里的部分smali進(jìn)行解釋一下,.method public check()Z 中的Z表示這是bool類型的函數(shù);.locals 5 表明了在這個(gè)函數(shù)中最少要用到的本地寄存器的個(gè)數(shù)。.line 15 表明了該代碼在原Java文件中的行數(shù)。其余的命令我也忘記了不少,為了避免誤人子弟,我就大概解釋一下主要的內(nèi)容吧?
invoke-virtual {v3}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
invoke-virtual是調(diào)用函數(shù),invoke-static后面有一對(duì)大括號(hào)“{}”,其實(shí)是調(diào)用該方法的實(shí)例+參數(shù)列表。對(duì)v3進(jìn)行分析就會(huì)發(fā)現(xiàn)它就是我們輸入框輸入的flag,v4就是上述數(shù)組里的s;看到這里:
if-eq v3, v4, :cond_1

其實(shí)我們已經(jīng)找到了跳轉(zhuǎn)點(diǎn),if-eq 也就是v3 v4相等時(shí)跳轉(zhuǎn)到某處,那么如果我們把它直接改成不相等時(shí)跳轉(zhuǎn),豈不是輸入除真實(shí)的flag外的任何值都會(huì)提示You got the flag! 那我們就來(lái)試試直接將if-eq改成if-ne,然后使用Androidkiller中的回編譯功能,對(duì)他進(jìn)行簽名,重新安裝運(yùn)行,發(fā)現(xiàn)隨便輸入都提示You got the flag!

AndroidAPK逆向分析
apk反編譯java代碼是進(jìn)行apk代碼分析、修改的基礎(chǔ),也能更好的理解apk的包結(jié)構(gòu)。通過本實(shí)驗(yàn),可掌握Android APK文件的逆向反編譯過程,能夠?qū)PK文件進(jìn)行簡(jiǎn)單的分析,學(xué)習(xí)相關(guān)工具的使用。
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014053009520900001
聲明:筆者初衷用于分享與普及網(wǎng)絡(luò)知識(shí),若讀者因此作出任何危害網(wǎng)絡(luò)安全行為后果自負(fù),與合天智匯及原作者無(wú)關(guān)!