[Android]反編譯檢視、修改原始碼、逆向分析以及二次打包簽名
本文我們將來探討關於Android的反編譯。通常來說,我們在開發過程中的apk出於DEBUG狀態,我們並沒有給予APK一個特定的簽名,而是編譯系統預設給apk一個簽名。在釋出到應用商城時,我們會用自己的簽名檔案來簽名apk,以防止被其他人惡意篡改apk。當然,我們也會利用Android的混淆技術或者一些加固技術來防止apk被反編譯造成原始碼洩漏。
所以,本文只能針對於沒有被簽名、混淆、加固過的apk,對於絕大多數市面上的apk來說,如果你想要通過反編譯得到裡面的重要原始碼,那是行不通的。如果apk用了加固技術,那根本要反編譯都很困難。
我先列舉一下我們將會用到的幾個工具:
apktool.jar:檢視apk包下的AndroidManifest.xml和res資料夾內容。
dex2jar.jar:把apk中的classes.dex轉為一個jar包
jdgui:通過上面獲得的jar包,利用這個工具開啟
baksmali.jar:把apk中的classes.dex轉為為smali原始碼
smali:把smali檔案編譯打包成classes.dex的工具
signapk.jar 把我們重新生成的apk重新簽名
廢話少說,我們來自己寫個Demo,編譯出一個apk,這個apk很簡單,我們在AActivity輸入密碼:123456之後才能啟動到BActivity,否則提示密碼錯誤。
原始碼如下:
public class AActivity extends ActionBarActivity {
Button btn;
EditText et;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.bt);
et = (EditText) findViewById(R.id.et);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String pwd = et.getText().toString();
if("123456".equals(pwd)){
startActivity(new Intent(AActivity.this,BActivity.class));
Toast.makeText(AActivity.this , "登入成功", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(AActivity.this, "密碼錯誤", Toast.LENGTH_LONG).show();
}
}
});
}
我們的執行截圖是這樣:
好的,接下來我們來嘗試解開apk裡面的內容。把apk檔案的字尾名改成.zip的壓縮格式,開啟:
從apk的目錄來看:
res:我們的資源目錄
META-INF:一些資訊配置,這裡我們可以不關心。
resources.arsc:編譯資源時生成的檔案,資源能根據配置索引到相應的資源就是依賴了它。
classes.dex:原始碼編譯打包後的檔案。
AndroidManifest.xml:大家都知道了
首先,我們來看下如何檢視apk的原始碼,我們提取出classes.dex,把classes.dex放到dex2jar的資料夾裡面,然後開啟cmd,cd進入dex2jar的檔案目錄,輸入命令:dex2jar.bat classes.dex
可以發現資料夾裡面生成了一個classes_dex2jar.jar,我們把這個jar包提取出來,用jd-gui這個工具來開啟,可以直接將jar包拖曳到jd-gui上開啟,如下:
到此,我們就完成了反編譯的原始碼檢視。
而apk裡面的res目錄一些xml檔案和AndroidManifest.xml,由於已經被編譯成二進位制檔案,我們無法直接開啟檢視。可以由apktool.jar這個工具來反編譯還原成我們能開啟檢視的檔案。
同樣在cmd裡面 cd進入apktool.jar所在的資料夾,把我們的apk放進來,字尾名可以是被我們改成的zip字尾,或者是原先的.apk字尾。
敲入命令:apktool d Demo.zip
在資料夾中生成了一個資料夾,裡面所有的xml檔案我們就可以開啟查看了,
比如檢視AndroidManifest.xml:
到這裡我們已經學會了反編譯檢視apk原始碼。接下來我們再來看看如何修改apk進行二次打包。
在上面我們寫的apk中,需要輸入123456才能登入進第二個介面,。並且會彈出Toast提示。
我們來修改成輸入123即可進入第二個介面。
首先,我們需要把classex.dex轉為smali檔案,利用baksmali.jar這個工具,如下:
我們把classex.dex複製到baksmali.jar所在的資料夾,然後cd進入這個資料夾之後,敲入命令: java -jar baksmali-2.0.3.jar -x classes.dex
可以發現目錄生成了個out資料夾,裡面存放的就是我們的原始碼,不過是smali格式的,如果想要深層次的去修改原始碼則需要先學習smali的語法構造。這裡我們簡單的修改幾個數值,進入out檔案中,依次點開資料夾可以發現好幾個smali檔案,我們發現AActivity的有AActivity.smali檔案和AActivity
開啟後發現都是我們不熟悉的語法,
首先:
.class Lcom/example/demo/AActivity$1; 我們定義的類
.super Ljava/lang/Object; 繼承的超類,預設是Object
.source “AActivity.java” 對應的原始檔
.# interfaces
.implements Landroid/view/View$OnClickListener;
這個是實現的介面
下面的# instance fields、# direct methods、# virtual methods則是這個類定義的欄位、方法了。
我們重點來看onClick方法:
# virtual methods
.method public onClick(Landroid/view/View;)V
.registers 8
.param p1, "v" # Landroid/view/View;
.prologue
const/4 v5, 0x1
.line 27
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
iget-object v1, v1, Lcom/example/demo/AActivity;->et:Landroid/widget/EditText;
invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v1
invoke-interface {v1}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v0
.line 28
.local v0, "pwd":Ljava/lang/String;
const-string v1, "123456"
invoke-virtual {v1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v1
if-eqz v1, :cond_2f
.line 29
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
new-instance v2, Landroid/content/Intent;
iget-object v3, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
const-class v4, Lcom/example/demo/BActivity;
invoke-direct {v2, v3, v4}, Landroid/content/Intent;-><init>(Landroid/content/Context;Ljava/lang/Class;)V
invoke-virtual {v1, v2}, Lcom/example/demo/AActivity;->startActivity(Landroid/content/Intent;)V
.line 30
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
const-string v2, "\u767b\u5f55\u6210\u529f"
invoke-static {v1, v2, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
.line 34
:goto_2e
return-void
.line 32
:cond_2f
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
const-string v2, "\u5bc6\u7801\u9519\u8bef"
invoke-static {v1, v2, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
goto :goto_2e
.end method
再配合我們在jd-gui中開啟檢視到的原始碼。
.line 27
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
iget-object v1, v1, Lcom/example/demo/AActivity;->et:Landroid/widget/EditText;
invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v1
invoke-interface {v1}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v0
首先onClick的這部分程式碼,可以對應我們檢視到的原始碼這行:
“123456”.equals(AActivity.this.et.getText().toString())
可以看出上面幾句smali原始碼是這句程式碼的一個執行順序,首先是有這個AActivity物件,然後得到EditText物件,然後執行getText後執行toString
其後:
.line 28
.local v0, "pwd":Ljava/lang/String;
const-string v1, "123456"
invoke-virtual {v1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
發現載入了一個pwd變數,賦值為v0,v0實際上就是上面 move-result-object v0得到的。然後再有一個字串常量為123456,到此我們就可以把123456修改成123了。
接著執行了equals後,注意到:
move-result v1
if-eqz v1, :cond_2f
把結果move到v1,又判斷v1,如果v1是0的話跳到cond_2f,
不是0則繼續下面,下面的程式碼也可以看出載入順序就是intent啟動的載入順序了,
直到最後彈出了Toast提示,我們可以發現到
const-string v2, "\u767b\u5f55\u6210\u529f"
正是Toast彈出提示的內容,也可以進行修改。
最後面:
:cond_2f
iget-object v1, p0, Lcom/example/demo/AActivity$1;->this$0:Lcom/example/demo/AActivity;
const-string v2, "\u5bc6\u7801\u9519\u8bef"
invoke-static {v1, v2, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/Toast;->show()V
則是上面的if eqz v1後為0跳到這裡來,實際上就是密碼匹配123456不對後跳到這裡來提示了密碼錯誤。
上面的原始碼我們可以修改很多東西,比如修改if條件,加入其他方法執行,但注意不能加入新定義的方法。
這裡我們簡單就修改了密碼為123後儲存檔案
然後重新轉為classex.dex,利用smali.jar工具打包,同樣進入資料夾,敲入命令:
java -jar smali-2.0.3.jar -o classes.dex out
後生成了一個新的classex.dex,我們把它替換到apk中去,
然後重新簽名,利用signapk.jar工具簽名,同樣cd到signapk.jar目錄下,敲入命令:
java -jar signapk.jar platform.x509.pem platform.pk8 Demo.apk DemoSigned.apk
得到了一個DemoSigned.apk,我們把DemoSigned.apk轉載到模擬器上看,輸入命令:
adb uninstall com.example.demo
先解除安裝掉原先的apk,再輸入命令安裝:
adb install DemoSigned.apk
我們來看看執行:
可以發現我們成功修改了apk,現在輸入123456是密碼錯誤了,因為密碼驗證被我們改成了123.
到此就結束了,後面有機會我再寫一些關於smali語法和逆向分析的博文。