zoukankan      html  css  js  c++  java
  • Android反编译和二次打包

    参考: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, "u6211u662fu53cdu7f16u8bd1u540eu8fdbu884cu7684u4feeu6539u3002"
     
        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, "u6211u662fu53cdu7f16u8bd1u540eu8fdbu884cu7684u4feeu6539u3002"
     
        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文件。

           6、最后还要记得对生成的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文件进行替换。这样的修改方式降低了修改的难度也减小了犯错误的风险。

      

  • 相关阅读:
    POJ3259 Wormholes
    leetCode-Plus One
    leetCode-Pascal's Triangle
    leetCode-Longest Continuous Increasing Subsequence
    leetCode-Missing Number
    leetCode-Maximum Product of Three Numbers
    leetCode-Image Smoother
    leetCode-Contains Duplicate
    机器学习实战笔记-使用Apriori算法进行关联分析
    leetCode-Degree of an Array
  • 原文地址:https://www.cnblogs.com/slysky/p/9779927.html
Copyright © 2011-2022 走看看