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,点击会打印一段日志。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends AppCompatActivity implements View.OnClickListener {  
  2.     private static final String TAG = "MainActivity";  
  3.     private Button btn;  
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_main);  
  8.   
  9.         btn = (Button) findViewById(R.id.btn);  
  10.         btn.setOnClickListener(this);  
  11.     }  
  12.   
  13.     @Override  
  14.     public void onClick(View v) {  
  15.         Log.d(TAG,"Button is clicked");  
  16.     }  
  17. }  
       将这个工程编译生成的apk解压,取出其中的classes.dex放在dex2jar工具的目录下,然后执行命令


       会在当前目录下生成class-dex2jar.jar文件


       然后打开jd-gui,将class-dex2jar.jar文件拖进去,就可以看到反编译出来的源代码。


       可以看到反编译的代码和原本的代码差别不大,主要差别是原来的资源引用全都变成了数字。

       下面我们来修改这个apk的内容。

       首先我们将apk拷贝到apktool工具目录下,执行命令apktool  d  app-release.apk。


       生成的目录中包含smali文件夹


       然后找到我们的主要的类MainActivity.smali,文件内容如下:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. .class public Lcom/viclee/decompiledemo/MainActivity;  
  2. .super Landroid/support/v7/app/AppCompatActivity;  
  3. .source "MainActivity.java"  
  4.   
  5. # interfaces  
  6. .implements Landroid/view/View$OnClickListener;  
  7.   
  8.   
  9. # static fields  
  10. .field private static final TAG:Ljava/lang/String; = "MainActivity"  
  11.   
  12.   
  13. # instance fields  
  14. .field private btn:Landroid/widget/Button;  
  15.   
  16.   
  17. # direct methods  
  18. .method public constructor <init>()V  
  19.     .locals 0  
  20.   
  21.     .prologue  
  22.     .line 9  
  23.     invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V  
  24.   
  25.     return-void  
  26. .end method  
  27.   
  28.   
  29. # virtual methods  
  30. .method public onClick(Landroid/view/View;)V  
  31.     .locals 2  
  32.     .param p1, "v"    # Landroid/view/View;  
  33.   
  34.     .prologue  
  35.     .line 23  
  36.     const-string v0, "MainActivity"  
  37.   
  38.     const-string v1, "Button is clicked"  
  39.   
  40.     invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I  
  41.   
  42.     .line 24  
  43.     return-void  
  44. .end method  
  45.   
  46. .method protected onCreate(Landroid/os/Bundle;)V  
  47.     .locals 1  
  48.     .param p1, "savedInstanceState"    # Landroid/os/Bundle;  
  49.   
  50.     .prologue  
  51.     .line 14  
  52.     invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V  
  53.   
  54.     .line 15  
  55.     const v0, 0x7f040019  
  56.   
  57.     invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V  
  58.   
  59.     .line 17  
  60.     const v0, 0x7f0c0050  
  61.   
  62.     invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->findViewById(I)Landroid/view/View;  
  63.   
  64.     move-result-object v0  
  65.   
  66.     check-cast v0, Landroid/widget/Button;  
  67.   
  68.     iput-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;  
  69.   
  70.     .line 18  
  71.     iget-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;  
  72.   
  73.     invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V  
  74.   
  75.     .line 19  
  76.     return-void  
  77. .end method  

       其中36-40行是打印日志的位置,文件内容很清晰,每个区域的意义如下:

.class  类名

.super 父类名

.source  文件名

.implements  这个类实现的接口

.field  成员变量

.method 方法

       然后新建一个工程,在这个工程中实现想要替换的代码,我们这里是希望将原始工程中打印日志的地方替换为弹出一个Toast。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends AppCompatActivity{  
  2.     @Override  
  3.     protected void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.activity_main);  
  6.   
  7.         showToast();  
  8.     }  
  9.   
  10.     public void showToast() {  
  11.         Toast.makeText(this,"我是反编译后进行的修改。",Toast.LENGTH_LONG).show();  
  12.     }  
  13. }  

       然后像前面一样执行apktool命令,生成的smali文件内容如下:

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. .class public Lcom/viclee/decompiledemo/MainActivity;  
  2. .super Landroid/support/v7/app/AppCompatActivity;  
  3. .source "MainActivity.java"  
  4.   
  5.   
  6. # direct methods  
  7. .method public constructor <init>()V  
  8.     .locals 0  
  9.   
  10.     .prologue  
  11.     .line 7  
  12.     invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V  
  13.   
  14.     return-void  
  15. .end method  
  16.   
  17.   
  18. # virtual methods  
  19. .method protected onCreate(Landroid/os/Bundle;)V  
  20.     .locals 1  
  21.     .param p1, "savedInstanceState"    # Landroid/os/Bundle;  
  22.   
  23.     .prologue  
  24.     .line 10  
  25.     invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V  
  26.   
  27.     .line 11  
  28.     const v0, 0x7f040019  
  29.   
  30.     invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V  
  31.   
  32.     .line 13  
  33.     invoke-virtual {p0}, Lcom/viclee/decompiledemo/MainActivity;->showToast()V  
  34.   
  35.     .line 14  
  36.     return-void  
  37. .end method  
  38.   
  39. .method public showToast()V  
  40.     .locals 2  
  41.   
  42.     .prologue  
  43.     .line 17  
  44.     const-string v0, "\u6211\u662f\u53cd\u7f16\u8bd1\u540e\u8fdb\u884c\u7684\u4fee\u6539\u3002"  
  45.   
  46.     const/4 v1, 0x1  
  47.   
  48.     invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;  
  49.   
  50.     move-result-object v0  
  51.   
  52.     invoke-virtual {v0}, Landroid/widget/Toast;->show()V  
  53.   
  54.     .line 18  
  55.     return-void  
  56. .end method  

       上面代码中,33、39-56行就是弹出Toast的代码部分。将上面整个showToast方法拷贝到原始工程的smali文件中,这里要特别注意修改行号,这个行号表示的是代码在原始Java文件中的行号,需要参考两个smali文件的行号来修改。我认为只要保证方法内的行号不乱序,并且方法之间的行号不冲突就可以。然后,需要将原始工程中打印日志的代码替换为显示Toast的代码,也就是将原始smali文件中36-40行修改为新建工程中33、39-56行的内容。修改后的内容如下,主要关注下面内容中36行、75-91行与原始smali文件的差异。

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. .class public Lcom/viclee/decompiledemo/MainActivity;  
  2. .super Landroid/support/v7/app/AppCompatActivity;  
  3. .source "MainActivity.java"  
  4.   
  5. # interfaces  
  6. .implements Landroid/view/View$OnClickListener;  
  7.   
  8.   
  9. # static fields  
  10. .field private static final TAG:Ljava/lang/String; = "MainActivity"  
  11.   
  12.   
  13. # instance fields  
  14. .field private btn:Landroid/widget/Button;  
  15.   
  16.   
  17. # direct methods  
  18. .method public constructor <init>()V  
  19.     .locals 0  
  20.   
  21.     .prologue  
  22.     .line 9  
  23.     invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V  
  24.   
  25.     return-void  
  26. .end method  
  27.   
  28.   
  29. # virtual methods  
  30. .method public onClick(Landroid/view/View;)V  
  31.     .locals 2  
  32.     .param p1, "v"    # Landroid/view/View;  
  33.   
  34.     .prologue  
  35.     .line 23  
  36.     invoke-virtual {p0}, Lcom/viclee/decompiledemo/MainActivity;->showToast()V  
  37.   
  38.     .line 24  
  39.     return-void  
  40. .end method  
  41.   
  42. .method protected onCreate(Landroid/os/Bundle;)V  
  43.     .locals 1  
  44.     .param p1, "savedInstanceState"    # Landroid/os/Bundle;  
  45.   
  46.     .prologue  
  47.     .line 14  
  48.     invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V  
  49.   
  50.     .line 15  
  51.     const v0, 0x7f040019  
  52.   
  53.     invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->setContentView(I)V  
  54.   
  55.     .line 17  
  56.     const v0, 0x7f0c0050  
  57.   
  58.     invoke-virtual {p0, v0}, Lcom/viclee/decompiledemo/MainActivity;->findViewById(I)Landroid/view/View;  
  59.   
  60.     move-result-object v0  
  61.   
  62.     check-cast v0, Landroid/widget/Button;  
  63.   
  64.     iput-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;  
  65.   
  66.     .line 18  
  67.     iget-object v0, p0, Lcom/viclee/decompiledemo/MainActivity;->btn:Landroid/widget/Button;  
  68.   
  69.     invoke-virtual {v0, p0}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V  
  70.   
  71.     .line 19  
  72.     return-void  
  73. .end method  
  74.   
  75. .method public showToast()V  
  76.     .locals 2  
  77.   
  78.     .prologue  
  79.     .line 27  
  80.     const-string v0, "\u6211\u662f\u53cd\u7f16\u8bd1\u540e\u8fdb\u884c\u7684\u4fee\u6539\u3002"  
  81.   
  82.     const/4 v1, 0x1  
  83.   
  84.     invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;  
  85.   
  86.     move-result-object v0  
  87.   
  88.     invoke-virtual {v0}, Landroid/widget/Toast;->show()V  
  89.   
  90.     .line 28  
  91.     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反编译后也可以修改资源,将反编译出来的资源文件修改一通,然后按照之前的方法,重新打包、签名、安装。下面两个页面是修改之前和修改之后的对比图。

                  

       到这里,本文的全部内容就讲解完了,欢迎大家评论交流~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值