在Android应用开发中,软件安全和逆向分析非常重要。试想如果一个优秀的APP应用没有建立完善的安全机制,从而很容易被黑客破解修改,一方面泄露了应用程序的核心技术,另一方面势必会对用户带来损害,从而造成大量的用户流失。如何反编译破解apk以及保护自己的软件免受反编译破解,是这个系列文章的主题。
这篇文章主要从apk反编译破解和java汇编语言读写两个方面进行了Android中逆向分析的简述。俗话说:知己知彼,百战不殆。只有了解了apk的反编译破解过程,才能反方面进行加密处理从而避免自己的应用程序被反编译破解。本文通过使用apktool、dex2jar、jd-gui等工具实现了apk的反编译破解,然后修改破解后的java汇编语言、利用jarsigner进行二次签名从而达到改变apk功能的作用。
点击即可下载相应工具
首先完成一个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到桌面
DDMS->Devices->点击stop停止程序->File Explore->data->APP->.apk->点击导出键
.zip解压apk
得到apk文件以后,若是直接修改后缀.apk为.zip文件然后解压查看,会发现解压完的文件在编辑器打开为乱码,只能寻求更好的方法来查看apk中的文件。
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源码,仿真度很高
在上面使用apktool工具生成的.smali文件,打开发现是java汇编语言,很难读懂,下面就上述生成的汇编语言进行简单的说明。apktool生成了两个主要的.smali需要查看,分别为MainActivity.smali和MainActivity$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到模拟器上,
还是失败,因为原来模拟器上安装过这个软件,需要重新卸载掉,然后安装就可以了!
模拟器上卸载程序有两种方法:
卸载之后,重新进行安装如下,
显示结果正好和原来结果相反,证明我们之前修改的.smali汇编语言文件,起了作用。
实际反汇编破解apk过程中,还是用dex2jar工具比较好,如果一个工程应用程序很复杂,那么直接查看汇编语言文件.smali会是项庞大的工程。no zuo no die !
android apk反编译详解
Android APK反编译就这么简单 详解(附图)
Android APK反编译详解(附图)
谈谈android反编译和防止反编译的方法
未完待续。。。