1. 程式人生 > >Android反編譯和二次打包

Android反編譯和二次打包

犯錯 解壓 就會 field har stat 生成 https make

參考:APK反編譯

一、工具介紹:

1、解壓工具

2、JDK

3.apktool: aapt.exe,apktool.bat,apktool.jar;三個在同一目錄結合使用,用來反編譯apk,反編譯生成smali字節碼文件,提取apk中的資源文件,apk重新打包

4.dex2jar:該工具作用是將classes.dex文件,翻譯出程序的源代碼、圖片、XML配置、語言資源等文件(如果apk未加固),反編譯出文件,使用jd-gui工具進行查看;

5.jarsigner.exe:簽名工具,將重新打包的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。

1、反編譯apk包。利用apk tool對apk進行反編譯。apktool -f [待反編譯的apk] -o [反編譯之後存放文件夾]  

運行apktool.jar這個jar文件來將apk文件進行反編譯,在java中,運行可執行jar包的命令是:

java -jar jar包名.jar

  命令:java -jar apktools.jar d -f -o test_output_dir ****.apk

  此命令將會反編譯apk包到test_output_dir文件夾下。文件夾下是反編譯出來的各種文件夾。具體參考網上找的圖:

  技術分享圖片

 2、使用dex2jar反編譯apk得到Java源代碼。首先將apk包,解壓成zip文件。得到其中的classes.dex文件(它就是java文件編譯再通過dx工具打包而成的)

  d2j-dex2jar classes.dex

  命令執行完成之後,在當前目錄下就可以看到生成的Jar文件了。

3、可以用jd-gui工具進行查看步驟2內生成的classes_dex2jar.jar。其內容和步驟1內的smali相對應。

  被混淆過的類文件名稱以及裏面的方法名稱都會以a,b,c....之類的樣式命名

  技術分享圖片

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

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

4、在步驟1內生成的文件夾內,找到主要的類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 方法

5、然後新建一個工程,在這個工程中實現想要替換的代碼,我們這裏是希望將原始工程中打印日誌的地方替換為彈出一個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文件進行替換。這樣的修改方式降低了修改的難度也減小了犯錯誤的風險。

  

Android反編譯和二次打包