Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發apk反編譯和二次打包教程

Android開發apk反編譯和二次打包教程

編輯:關於Android編程

作為Android開發者,工作中少不了要反編譯別人的apk,當然主要目的還是為了學習到更多,取彼之長,補己之短。今天就來總結一下Android反編譯和二次打包的一些知識。首先聲明本文的目的是為了通過例子講解反編譯和二次打包的原理和方法,繼而作為後續講解防止二次打包和App安全的依據,並不是鼓勵大家去重新打包別人的App,盜取他人勞動成果。

       本文首先介紹幾種Android反編譯工具的使用,然後實現在不需要知道源代碼的情況下,僅通過修改反編譯得到的smali文件實現修改apk邏輯功能的目的。

       Android中常用的反編譯工具有三個:dex2jar、jd-gui和apktool,這三個工具的作用如下:

dex2jar:將apk中的classes.dex文件轉換成jar文件。

jd-gui:查看由dex2jar轉換成的jar文件,以界面的形式展示反編譯出來的Java源代碼。

apktool:反編譯生成smali字節碼文件,提取apk中的資源文件。

       為了盡可能的把問題講清楚,我們來實現一個很簡單的例子。首先創建一個工程DecompileDemo,在MainActivity中定義一個布局,其中包含一個Button,點擊會打印一段日志。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  private static final String TAG = "MainActivity";
  private Button btn;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(this);
  }

  @Override
  public void onClick(View v) {
    Log.d(TAG,"Button is clicked");
  }
}

       將這個工程編譯生成的apk解壓,取出其中的classes.dex放在dex2jar工具的目錄下,然後執行命令


       會在當前目錄下生成class-dex2jar.jar文件


       然後打開jd-gui,將class-dex2jar.jar文件拖進去,就可以看到反編譯出來的源代碼。


       可以看到反編譯的代碼和原本的代碼差別不大,主要差別是原來的資源引用全都變成了數字。

       下面我們來修改這個apk的內容。

       首先我們將apk拷貝到apktool工具目錄下,執行命令apktool  d  app-release.apk。


       生成的目錄中包含smali文件夾


       然後找到我們的主要的類MainActivity.smali,文件內容如下:

.class public Lcom/viclee/decompiledemo/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"

# interfaces
.implements Landroid/view/View$OnClickListener;


# static fields
.field private static final TAG:Ljava/lang/String; = "MainActivity"


# instance fields
.field private btn:Landroid/widget/Button;


# direct methods
.method public constructor <init>()V
  .locals 0

  .prologue
  .line 9
  invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V

  return-void
.end method


# virtual methods
.method public onClick(Landroid/view/View;)V
  .locals 2
  .param p1, "v"  # Landroid/view/View;

  .prologue
  .line 23
  const-string v0, "MainActivity"

  const-string v1, "Button is clicked"

  invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

  .line 24
  return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
  .locals 1
  .param p1, "savedInstanceState"  # Landroid/os/Bundle;

  .prologue
  .line 14
  invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

  .line 15
  const v0, 0x7f040019

  invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V

  .line 17
  const v0, 0x7f0c0050

  invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->findViewById(I)Landroid/view/View;

  move-result-object v0

  check-cast v0, Landroid/widget/Button;

  iput-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;

  .line 18
  iget-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;

  invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

  .line 19
  return-void
.end method

       其中36-40行是打印日志的位置,文件內容很清晰,每個區域的意義如下:

.class  類名

.super 父類名

.source  文件名

.implements  這個類實現的接口

.field  成員變量

.method 方法

       然後新建一個工程,在這個工程中實現想要替換的代碼,我們這裡是希望將原始工程中打印日志的地方替換為彈出一個Toast。

public class MainActivity extends AppCompatActivity{
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    showToast();
  }

  public void showToast() {
    Toast.makeText(this,"我是反編譯後進行的修改。",Toast.LENGTH_LONG).show();
  }
}

       然後像前面一樣執行apktool命令,生成的smali文件內容如下:

.class public Lcom/viclee/decompiledemo/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"


# direct methods
.method public constructor <init>()V
  .locals 0

  .prologue
  .line 7
  invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V

  return-void
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
  .locals 1
  .param p1, "savedInstanceState"  # Landroid/os/Bundle;

  .prologue
  .line 10
  invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

  .line 11
  const v0, 0x7f040019

  invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V

  .line 13
  invoke-virtual {p0}, Lcom/viclee/decompiledemo/MainActivity;->showToast()V

  .line 14
  return-void
.end method

.method public showToast()V
  .locals 2

  .prologue
  .line 17
  const-string v0, "\u6211\u662f\u53cd\u7f16\u8bd1\u540e\u8fdb\u884c\u7684\u4fee\u6539\u3002"

  const/4 v1, 0x1

  invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

  move-result-object v0

  invoke-virtual {v0}, Landroid/widget/Toast;->show()V

  .line 18
  return-void
.end method

       上面代碼中,33、39-56行就是彈出Toast的代碼部分。將上面整個showToast方法拷貝到原始工程的smali文件中,這裡要特別注意修改行號,這個行號表示的是代碼在原始Java文件中的行號,需要參考兩個smali文件的行號來修改。我認為只要保證方法內的行號不亂序,並且方法之間的行號不沖突就可以。然後,需要將原始工程中打印日志的代碼替換為顯示Toast的代碼,也就是將原始smali文件中36-40行修改為新建工程中33、39-56行的內容。修改後的內容如下,主要關注下面內容中36行、75-91行與原始smali文件的差異。

.class public Lcom/viclee/decompiledemo/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"

# interfaces
.implements Landroid/view/View$OnClickListener;


# static fields
.field private static final TAG:Ljava/lang/String; = "MainActivity"


# instance fields
.field private btn:Landroid/widget/Button;


# direct methods
.method public constructor <init>()V
  .locals 0

  .prologue
  .line 9
  invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V

  return-void
.end method


# virtual methods
.method public onClick(Landroid/view/View;)V
  .locals 2
  .param p1, "v"  # Landroid/view/View;

  .prologue
  .line 23
  invoke-virtual {p0}, Lcom/viclee/decompiledemo/MainActivity;->showToast()V

  .line 24
  return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
  .locals 1
  .param p1, "savedInstanceState"  # Landroid/os/Bundle;

  .prologue
  .line 14
  invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

  .line 15
  const v0, 0x7f040019

  invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V

  .line 17
  const v0, 0x7f0c0050

  invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->findViewById(I)Landroid/view/View;

  move-result-object v0

  check-cast v0, Landroid/widget/Button;

  iput-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;

  .line 18
  iget-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;

  invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

  .line 19
  return-void
.end method

.method public showToast()V
  .locals 2

  .prologue
  .line 27
  const-string v0, "\u6211\u662f\u53cd\u7f16\u8bd1\u540e\u8fdb\u884c\u7684\u4fee\u6539\u3002"

  const/4 v1, 0x1

  invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

  move-result-object v0

  invoke-virtual {v0}, Landroid/widget/Toast;->show()V

  .line 28
  return-void

       然後我們需要將修改後的文件目錄重新打包,執行命令 apktool   b  app-release,就會在app-releae目錄下生成兩個文件夾:build 文件夾裡面是一些中間文件(classes.dex等內容),dist 文件夾裡面存放著重新打包出來的apk文件。

       最後還要記得對生成的apk進行簽名,否則安裝時會報錯。執行下面的命令行:

jarsigner -verbose -keystore viclee.keystore -signedjar app-release-signed.apk app-release.apk viclee.keystore 

-verbose 輸出簽名詳細信息 
-keystore 指定密鑰對的存儲路徑 
-signedjar 後面三個參數分別是簽名後的apk、未簽名的apk和密鑰對的別名

       安裝簽名後的apk,點擊按鈕,確實彈出了Toast,內容和我們所設置的一致,說明我們的修改成功了。

       我們注意到,修改smali文件的時候並不是直接在文件上進行修改,畢竟smali文件的可讀性差,直接修改是十分困難的。我們的解決辦法是新建一個工程將需要增加的代碼實現,最好抽成一個單獨的方法(方便替換),然後將新工程打包產生的apk反編譯,得到對應的smali文件,再用其中的內容對原始smali文件進行替換。這樣的修改方式降低了修改的難度也減小了犯錯誤的風險。


       另外,apk反編譯後也可以修改資源,將反編譯出來的資源文件修改一通,然後按照之前的方法,重新打包、簽名、安裝。下面兩個頁面是修改之前和修改之後的對比圖。


       到這裡,本文的全部內容就講解完了,歡迎大家評論交流~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved