Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写

       在Android应用开发中,软件安全和逆向分析非常重要。试想如果一个优秀的APP应用没有建立完善的安全机制,从而很容易被黑客破解修改,一方面泄露了应用程序的核心技术,另一方面势必会对用户带来损害,从而造成大量的用户流失。如何反编译破解apk以及保护自己的软件免受反编译破解,是这个系列文章的主题。
       这篇文章主要从apk反编译破解和java汇编语言读写两个方面进行了Android中逆向分析的简述。俗话说:知己知彼,百战不殆。只有了解了apk的反编译破解过程,才能反方面进行加密处理从而避免自己的应用程序被反编译破解。本文通过使用apktool、dex2jar、jd-gui等工具实现了apk的反编译破解,然后修改破解后的java汇编语言、利用jarsigner进行二次签名从而达到改变apk功能的作用。

一、测试环境和工具

       点击即可下载相应工具

  • windows7 x86
  • jdk1.7.0
  • Android SDK和eclipse:adt-bundle-windows-x86-20140321
  • jd-gui
  • signapk
  • dex2jar
  • apktool

二、编写Android实例

       首先完成一个Android简单实例的编写,主要功能是输入一个序列号,如果输入序列号为1234,输出“恭喜你,注册成功!”,否则输出”对不起,序列号错误!”。
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第1张图片

整个应用程序的工程目录如下,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第2张图片

activity_main.xml文件很简单,就两个控件EditText和Button。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.cracktest.MainActivity" >

    <EditText  android:id="@+id/et_password" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="请输入软件的序列号" />

    <Button  android:id="@+id/click" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="确定" />

</LinearLayout>

MainActivity.java的实现也很简单,显示用Toast实现,

package com.example.cracktest;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    private EditText et;
    private Button eb;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et = (EditText) findViewById(R.id.et_password);
        eb = (Button) findViewById(R.id.click);

        eb.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                String pwd = et.getText().toString().trim();

                if ("1234".equals(pwd)) {
                    Toast.makeText(MainActivity.this, "恭喜你,注册成功!", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "对不起,序列号错误!", 0).show();
                }
            }
        });
    }
}

模拟器上运行这个简单实例如下,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第3张图片

三、apk反编译破解

       假设我们现在要对上述工程生成的apk进行反编译破解,那么该怎么做呢?下面我们进行详细的流程描述。

  1. 导出APK到桌面
    DDMS->Devices->点击stop停止程序->File Explore->data->APP->.apk->点击导出键
    Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第4张图片

  2. .zip解压apk
    得到apk文件以后,若是直接修改后缀.apk为.zip文件然后解压查看,会发现解压完的文件在编辑器打开为乱码,只能寻求更好的方法来查看apk中的文件。

  3. apktool打开apk
    apktool为apk逆向工程的软件,首先拷贝apk到apktool的目录下,然后cmd进入apktool的目录下,执行如下命令即可得到apk的全部的资源素材。

    得到apk资源素材后,点击查看,C:\Users\Administrator\Desktop\apktool\com.example.cracktest-1\smali\com\example\cracktest

    用编辑器打开MainActivity.smali文件如下,

.class public Lcom/example/cracktest/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"


# instance fields
.field private eb:Landroid/widget/Button;

.field private et:Landroid/widget/EditText;


# direct methods
.method public constructor <init>()V
    .locals 0

    .prologue
    .line 11
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method

.method static synthetic access$0(Lcom/example/cracktest/MainActivity;)Landroid/widget/EditText;
    .locals 1
    .parameter

    .prologue
    .line 13
    iget-object v0, p0, Lcom/example/cracktest/MainActivity;->et:Landroid/widget/EditText;

    return-object v0
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 2
    .parameter "savedInstanceState"

    .prologue
    .line 17
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 18
    const/high16 v0, 0x7f03

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->setContentView(I)V

    .line 20
    const/high16 v0, 0x7f08

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->findViewById(I)Landroid/view/View;

    move-result-object v0

    check-cast v0, Landroid/widget/EditText;

    iput-object v0, p0, Lcom/example/cracktest/MainActivity;->et:Landroid/widget/EditText;

    .line 21
    const v0, 0x7f080001

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->findViewById(I)Landroid/view/View;

    move-result-object v0

    check-cast v0, Landroid/widget/Button;

    iput-object v0, p0, Lcom/example/cracktest/MainActivity;->eb:Landroid/widget/Button;

    .line 23
    iget-object v0, p0, Lcom/example/cracktest/MainActivity;->eb:Landroid/widget/Button;

    new-instance v1, Lcom/example/cracktest/MainActivity$1;

    invoke-direct {v1, p0}, Lcom/example/cracktest/MainActivity$1;-><init>(Lcom/example/cracktest/MainActivity;)V

    invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

    .line 34
    return-void
.end method

       这就是反编译后生成的java汇编语言,发现读起来很困难,没事,这个在第三部分有讲解,那么我们怎么得到反编译后的java源代码呢?
4. dex2jar生成jar
在apk文件中,修改.apk后缀名为.zip,然后解压得到classes.dex文件,dex2jar工具的作用就是把dex文件转化为jar文件。把得到的classes.dex文件复制到dex2jar目录下,cmd执行命令如下,

5. jd-gui把jar转成java源码
打开jd-gui,直接将生成的classes_dex2jar.jar文件拖入到jd-gui中如下,

点击生成的java包即可查看apk里的java源码,仿真度很高

四、java汇编语言读写

       在上面使用apktool工具生成的.smali文件,打开发现是java汇编语言,很难读懂,下面就上述生成的汇编语言进行简单的说明。apktool生成了两个主要的.smali需要查看,分别为MainActivity.smaliMainActivity$1.smali

.class public Lcom/example/cracktest/MainActivity;//类生命,L表示对象类型
.super Landroid/app/Activity;//当前类的父类
.source "MainActivity.java"//源文件名字

//#就像c语言中的双斜杠,注释的作用
# instance fields//类成员变量
.field private eb:Landroid/widget/Button;

.field private et:Landroid/widget/EditText;


# direct methods
.method public constructor <init>()V //Activity构造方法,返回值为Void
    .locals 0//本地变量声明0个

    .prologue
    .line 11//这两句为自动生成行号等信息
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V//直接调用,p0为this的意思

    return-void
.end method

.method static synthetic access$0(Lcom/example/cracktest/MainActivity;)Landroid/widget/EditText;
    .locals 1
    .parameter

    .prologue
    .line 13
    iget-object v0, p0, Lcom/example/cracktest/MainActivity;->et:Landroid/widget/EditText;

    return-object v0
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V //返回值为Void,null
    .locals 2
    .parameter "savedInstanceState"//方法参数

    .prologue
    .line 17
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 18
    const/high16 v0, 0x7f03

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->setContentView(I)V

    .line 20
    const/high16 v0, 0x7f08

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->findViewById(I)Landroid/view/View;

    move-result-object v0

    check-cast v0, Landroid/widget/EditText;

    iput-object v0, p0, Lcom/example/cracktest/MainActivity;->et:Landroid/widget/EditText;

    .line 21
    const v0, 0x7f080001

    invoke-virtual {p0, v0}, Lcom/example/cracktest/MainActivity;->findViewById(I)Landroid/view/View;

    move-result-object v0

    check-cast v0, Landroid/widget/Button;

    iput-object v0, p0, Lcom/example/cracktest/MainActivity;->eb:Landroid/widget/Button;

    .line 23
    iget-object v0, p0, Lcom/example/cracktest/MainActivity;->eb:Landroid/widget/Button;

    new-instance v1, Lcom/example/cracktest/MainActivity$1;//新实例对象类型为com/example/cracktest/MainActivity$1

    invoke-direct {v1, p0}, Lcom/example/cracktest/MainActivity$1;-><init>(Lcom/example/cracktest/MainActivity;)V

    invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V

    .line 34
    return-void
.end method
.class Lcom/example/cracktest/MainActivity$1;//类生命,L表示对象类型
.super Ljava/lang/Object;//当前类的父类
.source "MainActivity.java"//源文件名字

//#就像c语言中的双斜杠,注释的作用
# interfaces //实现接口
.implements Landroid/view/View$OnClickListener;


# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
    value = Lcom/example/cracktest/MainActivity;->onCreate(Landroid/os/Bundle;)V
.end annotation

.annotation system Ldalvik/annotation/InnerClass;
    accessFlags = 0x0
    name = null
.end annotation


# instance fields//类成员变量
.field final synthetic this$0:Lcom/example/cracktest/MainActivity;


# direct methods //Activity构造方法
.method constructor <init>(Lcom/example/cracktest/MainActivity;)V //Activity构造方法,返回值为Void
    .locals 0 //本地变量声明0个
    .parameter

    .prologue
    .line 1 //这两句为自动生成行号等信息
    iput-object p1, p0, Lcom/example/cracktest/MainActivity$1;->this$0:Lcom/example/cracktest/MainActivity;

    .line 23
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V //直接调用,p0为this、Activity的意思

    return-void
.end method


# virtual methods
.method public onClick(Landroid/view/View;)V //onClick事件处理函数,V代表返回值为空
    .locals 4 //需要声明4个本地变量
    .parameter "v"//参数为view v

    .prologue
    const/4 v3, 0x0

    .line 25
    //调用p0里面的方法
    iget-object v1, p0, Lcom/example/cracktest/MainActivity$1;->this$0:Lcom/example/cracktest/MainActivity;

    #getter for: Lcom/example/cracktest/MainActivity;->et:Landroid/widget/EditText;
    invoke-static {v1}, Lcom/example/cracktest/MainActivity;->access$0(Lcom/example/cracktest/MainActivity;)Landroid/widget/EditText;

    move-result-object v1

    invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;//返回值为Editable

    move-result-object v1

    invoke-interface {v1}, Landroid/text/Editable;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/String;->trim()Ljava/lang/String;

    move-result-object v0 //由EditText->Editable getText()->toString()->trim(),返回值为v0

    .line 27
    .local v0, pwd:Ljava/lang/String;
    const-string v1, "1234"  //定义常量,名字v1,值为1234

        //比较v1和v0的值
    invoke-virtual {v1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z //相等返回非0

    move-result v1

    if-eqz v1, :cond_0 //如果equal 0,不相等,执行cond_0逻辑

    .line 28
    iget-object v1, p0, Lcom/example/cracktest/MainActivity$1;->this$0:Lcom/example/cracktest/MainActivity;

    const-string v2, "\u606d\u559c\u4f60\uff0c\u6ce8\u518c\u6210\u529f\uff01" //如果相等,执行这个逻辑

    invoke-static {v1, v2, v3}, 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 32
    :goto_0
    return-void

    .line 30
    :cond_0
    iget-object v1, p0, Lcom/example/cracktest/MainActivity$1;->this$0:Lcom/example/cracktest/MainActivity;

    const-string v2, "\u5bf9\u4e0d\u8d77\uff0c\u5e8f\u5217\u53f7\u9519\u8bef\uff01" //可以测试这个字符串,控制台打印

    invoke-static {v1, v2, v3}, 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_0
.end method

在上述分析中,如果v1和v0相等,则执行逻辑
const-string v2, “\u606d\u559c\u4f60\uff0c\u6ce8\u518c\u6210\u529f\uff01” //如果相等,执行这个逻辑
这句话是什么意思呢?我们可以自己写个java Test测试一下这个字符串,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第5张图片

五、修改反编译后的汇编语言文件

       假设我们现在得到了一个apk反编译后的java汇编语言,如果修改它使的改变它的功能。就上面这个列子而言,如果输入字符串为1234,则显示注册成功,否则显示序列号错误。在汇编语言中它实现的逻辑如下,

if-eqz v1, :cond_0 //如果equal 0,不相等,执行cond_0逻辑

如果v1和v0的比较返回值为0,即eqz为equal 0的话,执行显示cond_0,显示序列号错误。。。
修改if从句为

if-nez v1, :cond_0 //如果not equal 0,相等,执行cond_0逻辑

意思是说,如果v1和v0的比较不为0,即v1,v0相等,让它显示序列号错误,相当于和原来的逻辑相反了。
C:\Users\Administrator\Desktop\apktool\com.example.cracktest-1\smali\com\example\cracktest\MainActivity$1.smali文件中按照上述修改if从句。然后cmd进入apktool目录下,执行命令如下,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第6张图片

生成new.apk新的apk文件后,我们将之部署到模拟器上测试,找到adb安装目录后,cmd执行如下命令,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第7张图片

结果安装解析失败,原因没有签名证书,为什么呢?
点击查看原来的和新的apk文件,我们发现new.apk文件中没有META-INF文件,
Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第8张图片

META-INF文件夹为应用程序的签名。

为了解决安装apk失败的问题,我们要给new.apk进行签名。
将new.apk复制到signapk目录下,cmd中执行如下命令生成out.apk文件,

在out.apk中已经生成了META-INF文件夹,最后安装out.apk到模拟器上,
这里写图片描述
还是失败,因为原来模拟器上安装过这个软件,需要重新卸载掉,然后安装就可以了!

模拟器上卸载程序有两种方法:

  • 直接在模拟器中点击menu–>settings –> Applications –> Manage Applications 中点击你需要卸载的apk(就是你的应用软件)–>Uninstall;
  • 进入内嵌的linux,去data/app目录下删除 apk文件,这种方法可以批量删除
    adb shell
    cd data
    cd app
    rm ApplicationName.apk

卸载之后,重新进行安装如下,

最后在模拟器上测试结果展示

Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写_第9张图片

显示结果正好和原来结果相反,证明我们之前修改的.smali汇编语言文件,起了作用。

实际反汇编破解apk过程中,还是用dex2jar工具比较好,如果一个工程应用程序很复杂,那么直接查看汇编语言文件.smali会是项庞大的工程。no zuo no die !

六、参考引用

android apk反编译详解
Android APK反编译就这么简单 详解(附图)
Android APK反编译详解(附图)
谈谈android反编译和防止反编译的方法

未完待续。。。

你可能感兴趣的:(android,反编译,dex2jar,汇编代码)