您的位置:首页 > 移动开发 > Android开发

[Android]反编译查看、修改源码、逆向分析以及二次打包签名

2017-09-05 18:23 716 查看
  本文我们将来探讨关于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重新签名

以上的所有工具打包下载链接:http://download.csdn.net/download/lc_miao/9966230

  废话少说,我们来自己写个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();
}
}
});

}


  我们的运行截图是这样:





好的,接下来我们来尝试解开apk里面的内容。把apk文件的后缀名改成.zip的压缩格式,打开:



  从apk的目录来看:

  res:我们的资源目录

  META-INF:一些信息配置,这里我们可以不关心。

  resources.arsc:编译资源时生成的文件,资源能根据配置索引到相应的资源就是依赖了它。

  classes.dex:源码编译打包后的文件。

  AndroidManifest.xml:大家都知道了

  首先,我们来看下如何查看apk的源码,我们提取出classes.dex,把classes.dex放到dex2jar的文件夹里面,然后打开cmd,cd进入dex2jar的文件目录,输入命令:dex2jar.bat classes.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



  在文件夹中生成了一个文件夹,里面所有的xml文件我们就可以打开查看了,

比如查看AndroidManifest.xml:



  到这里我们已经学会了反编译查看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



  可以发现目录生成了个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语法和逆向分析的博文。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐