本文我们将来探讨关于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文件和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;->(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 v1if-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语法和逆向分析的博文。

更多相关文章

  1. 解锁Retrofit -- 浅析Retrofit源码
  2. Android中如何通过文件路径判断是否是同一文件
  3. 修改Android中strings.xml文件, 动态改变数据
  4. Android(安卓)Studio官方文档之使用Lint提高你的代码质量篇
  5. 关于android app相关破解技术
  6. android smack源码分析——接收消息以及如何解析消息
  7. Android的App Widget实现
  8. Android中利用“反射”动态加载R文件中的资源
  9. android工程下assets与raw文件夹

随机推荐

  1. Androd学习笔记——Android中Touch事件的
  2. Android(安卓)API 中文 (112) ―― Thumb
  3. android 中给图片加圆角效果
  4. android shape的使用
  5. Android屏幕截图实现 (adbd部分)
  6. 《Android(安卓)获取当前app的版本号和版
  7. 使用Android(安卓)Studio搭建Android集成
  8. 拖动条SeekBar的简单使用
  9. Android(安卓)- ToDoList 详解
  10. Android(安卓)ListView 滚动条的设置详解