在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,输出“恭喜你,注册成功!”,否则输出”对不起,序列号错误!”。

整个应用程序的工程目录如下,

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();                }            }        });    }}

模拟器上运行这个简单实例如下,

三、apk反编译破解

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

  1. 导出APK到桌面
    DDMS->Devices->点击stop停止程序->File Explore->data->APP->.apk->点击导出键

  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测试一下这个字符串,

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

假设我们现在得到了一个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目录下,执行命令如下,

生成new.apk新的apk文件后,我们将之部署到模拟器上测试,找到adb安装目录后,cmd执行如下命令,

结果安装解析失败,原因没有签名证书,为什么呢?
点击查看原来的和新的apk文件,我们发现new.apk文件中没有META-INF文件,

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

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

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

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

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

六、参考引用

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

未完待续。。。

更多相关文章

  1. 上传音乐到Android模拟器的SD卡,并在Android模拟器上播放
  2. Java中的Timer和TimerTask在Android中的用法
  3. 自动化代码检查优化Lint
  4. 两个Android工程之间的调用
  5. 长聚微嵌 DMA-210U Android(安卓)入门第一天------烧写uboot、ke
  6. Android中导入工程出现Project has no default.properties file!
  7. android原生POST、httpClient4.X实现向PHP服务器上传文件
  8. android 打开文件的Intent及使用
  9. Android(安卓)利用Java实现压缩与解压缩(zip、gzip)支持中文路径

随机推荐

  1. 在Fragment中设置控件点击方法,执行失败。
  2. use ffmpeg to setup streaming server o
  3. Android(安卓)MediaPlayer 常用方法介绍
  4. Android(安卓)studio3.x 多渠道打包apk
  5. [Android(安卓)NDK]Android(安卓)JNI开发
  6. Android(安卓)Chromium WebView html js
  7. android(NDK+JNI)---Eclipse+CDT+gdb调试
  8. 关于Android(安卓)Studio3.2新建项目Andr
  9. android 获取唯一标识
  10. 浅析Android中的消息机制-解决:Only the o