1. 程式人生 > >[Android]反編譯檢視、修改原始碼、逆向分析以及二次打包簽名

[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(); } } }); }

  我們的執行截圖是這樣:

error
成功

好的,接下來我們來嘗試解開apk裡面的內容。把apk檔案的字尾名改成.zip的壓縮格式,開啟:
zip

  從apk的目錄來看:

  res:我們的資源目錄
  META-INF:一些資訊配置,這裡我們可以不關心。
  resources.arsc:編譯資源時生成的檔案,資源能根據配置索引到相應的資源就是依賴了它。
  classes.dex:原始碼編譯打包後的檔案。
  AndroidManifest.xml:大家都知道了

  首先,我們來看下如何檢視apk的原始碼,我們提取出classes.dex,把classes.dex放到dex2jar的資料夾裡面,然後開啟cmd,cd進入dex2jar的檔案目錄,輸入命令:dex2jar.bat classes.dex

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

res

  在資料夾中生成了一個資料夾,裡面所有的xml檔案我們就可以開啟查看了,
比如檢視AndroidManifest.xml:
mani

  到這裡我們已經學會了反編譯檢視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
baks

  可以發現目錄生成了個out資料夾,裡面存放的就是我們的原始碼,不過是smali格式的,如果想要深層次的去修改原始碼則需要先學習smali的語法構造。這裡我們簡單的修改幾個數值,進入out檔案中,依次點開資料夾可以發現好幾個smali檔案,我們發現AActivity的有AActivity.smali檔案和AActivity1.smali,AActivity1.smali是和匿名內部類有關,這跟我們在開發中打開出匿名內部類的類名是一樣的。由於這裡我們只是用到了點選事件,所以這個AActivity$1.smali就是點選事件的匿名內部類的實現了,我們開啟這個檔案。
  開啟後發現都是我們不熟悉的語法,
首先:

.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語法和逆向分析的博文。