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

  本文我们将来探讨关于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();
                }
            }
        });

    }

  我们的运行截图是这样:

error
成功

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

  从apk的目录来看:

  res:我们的资源目录
  META-INF:一些信息配置,这里我们可以不关心。
  resources.arsc:编译资源时生成的文件,资源能根据配置索引到相应的资源就是依赖了它。
  classes.dex:源码编译打包后的文件。
  AndroidManifest.xml:大家都知道了

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

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

res

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

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

  可以发现目录生成了个out文件夹,里面存放的就是我们的源码,不过是smali格式的,如果想要深层次的去修改源码则需要先学习smali的语法构造。这里我们简单的修改几个数值,进入out文件中,依次点开文件夹可以发现好几个smali文件,我们发现AActivity的有AActivity.smali文件和AActivity 1.smali,AActivity 1.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语法和逆向分析的博文。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值