Android的代码安全那些事(在咪咕动漫的分享)

Android的代码安全那些事(在咪咕动漫的分享)

Android的代码安全那些事

Android的代码安全那些事

作为一个正经八百的IT码农,我们有很多途径可以提高我们的“码农”能力,比如说,看书,比如说,浏览技术网站,比如说多写代码。其实根据我的经验呢,还有一种方式–读别人的代码。

大家都知道啊,我们中国人,有个能力挺厉害的,你们猜是啥?

读代码也是有很多途径,比如说下载下载Demo,读读github的代码,读读同事的代码。其实还有一种方式的,反编译商业软件。(不要学坏噢。)

所以说,这是一个不正经的技术分享。

这里写图片描述

为啥呢?总结来说,其实就是借鉴。

为啥要反编译,其实真的不是要获取啥机密资料,干些见光死的勾当。我们其实看看别人都干了些啥。

众所周知,我们人的进化,都是从模仿的过程进化而来的(废话)。还有做好我们自身应用的安全工作,防止比如API被破解啊,核心的一些内容被人知道,为了更好的为了自身的安全,所以去学习。

说到反编译呢,咱们就得先说说编译过程。

Java编译过程

Android的虚拟机是Dalvik,那么我们在非Android平台上默认使用的是Hotspot虚拟机,不仅有Hotsport这个虚拟机实现,其他的还包括JRocket(BEA),j9(IBM),Microsoft JVM等等等等。

各种虚拟机在编译方式,指令集的方式,编译优化,内存管理等上的差别是很大。但是都是根据《Java虚拟机规范》约定开发的,他们的基本的数据管理结构是一样的。

java代码在在正常的编译中,需要经过以下一些步骤,形成java字节码
这里写图片描述

而,Dalvik虚拟机和其他Java虚拟机除了以上的编译步骤是相同的,还是有很多差别的,比如:Dalvik 基于寄存器,而 JVM 基于栈,而由于Dalvik采用的是基于寄存器,所以整个优化和加载,就跟其他的JVM的实现有很多不同。Dalvik虚拟机在编译的时候会将Java字节码转为Dalvik虚拟机可运行的Dalvik字节码。

这里写图片描述

详细可参考 http://blog.csdn.net/dd864140130/article/details/52076515

Android的打包编译过程

这里写图片描述

1.Java编译器对工程本身的java代码进行编译,这些java代码有三个来源:app的源代码,由资源文件生成的R文件(aapt工具),以及有aidl文件生成的java接口文件(aidl工具)。产出为.class文件。

①.用AAPT编译R.java文件
②编译AIDL的java文件
③把java文件编译成class文件

2..class文件和依赖的三方库文件通过dex工具生成Dalvik虚拟机可执行的.dex文件,包含了所有的class信息,包括项目自身的class和依赖的class。产出为.dex文件。

3.apkbuilder工具将.dex文件和编译后的资源文件生成未经签名对齐的apk文件。这里编译后的资源文件包括两部分,一是由aapt编译产生的编译后的资源文件,二是依赖的三方库里的资源文件。产出为未经签名的.apk文件。

4.分别由Jarsigner和zipalign对apk文件进行签名和对齐,生成最终的apk文件。

总结为:编译–>DEX–>打包–>签名和对齐

反编译的过程就是前面提到编译的过程的逆向

说了那么多的编译过程,那么如何反编译呢。

首先需要逆向aapt的过程。这时候要用到apktools的工具。

单独的dex要解析成smali文件需要用到 baksmali工具,如果你要需要smali工具。

如果你还要将dex转为jar,需要用到dex2jar工具。

如果你还要查看jar的代码,还需要用到jd-gui。

需要单独解析编译后的AXML(Android Binary XML),需要用到AXMLPrinter去解码

啊。。工具还是有点多的,其实网上有人已经整理了以上的工具,长这样子的

这里写图片描述

这里写图片描述
回到我们文章的开头,我们如果想说去学习里面的一些原理,使用的框架,结构。把dex转为jar,再查看jar里面的代码,jd-gui的破解效率是比较低的。经常看到一些逻辑混乱的情况,其实我们的Android Studio 也是一个非常好用,反编译源代码的利器。

这里写图片描述

相对而言,Android studio 带的反编译工具,逻辑能够原模原样的还原出来,但是仍会存在一些文件,反编译不出来。

这时候,记得从dex入手,前面说过,dalvik虚拟机是基于寄存器(register)的,那么他的运行指令是跟寄存器挂钩的。Dex不仅可以反编译成字节码文件,还可以反编译成为一种跟寄存器相关的文件—-Smali。通俗的说,smali语言是Dalvik的反汇编语言。

在学习smali之前,对寄存器有点了解是能够更好的学习,分析逻辑。

以下是一个我写的Demo的原先代码。

package test.lemon.decompile;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
    Button button;
    String string = "显示一段话";
    public static int count = 0 ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.bt_check);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                test(MainActivity.this,string);
            }
        });
    }
    public void test(Context context,String msg){
        msg +=string;
        button.setText(msg);
    }
}123456789101112131415161718192021222324252627

界面是这样子的,点击按钮之后,方法中的一句话跟MainActivity当中的String拼接。这里写图片描述

这是一个反编译之后的代码

三十行的代码反编译成smali之后一百二十行,去掉换行,源代码跟smali代码两倍多的代码的差距。

smali还是一种比较麻烦而且复杂的语法,但是细细的分析下去,我们能看到一些编译过程之中的优化和语法的含义,还有虚拟机初始化的过程。

比如:

所做的优化

源代码
public void test(Context context,String msg){
    msg +=string;
    button.setText(msg);

}123456

smali

.method public test(Landroid/content/Context;Ljava/lang/String;)V
    .locals 2
    .param p1, "context"    # Landroid/content/Context;
    .param p2, "msg"    # Ljava/lang/String;

    .prologue
    .line 35

    #注意,注意,原来的 string +=msg;在这里进行了优化,变成了StringBuilder的实现方式,提高了效率

    new-instance v0, Ljava/lang/StringBuilder;

    invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

    invoke-virtual {v0, p2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v0

    iget-object v1, p0, Ltest/lemon/decompile/MainActivity;->string:Ljava/lang/String;

    invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v0

    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object p2
    .line 36
    iget-object v0, p0, Ltest/lemon/decompile/MainActivity;->button:Landroid/widget/Button;

    invoke-virtual {v0, p2}, Landroid/widget/Button;->setText(Ljava/lang/CharSequence;)V #将值 赋给了setText()

    .line 38
    return-void
.end method1234567891011121314151617181920212223242526272829303132333435

该方法当中原来简单的一句话 msg +=string; 已经被优化成为StringBuilder的实现方式,从原来的一句话,变成了句操作。

再比如,我们细细的阅读Smali代码,尝试去理解的时候 ,会发现两个构造函数

# direct methods
.method static constructor <clinit>()V

.method public constructor <init>()V1234

他们之间所代表的意义是这样子的。

构造函数是静态的 ,里面包含的一些代码,也是对静态的count字段进行初始化,所代表的意义是“ 装载一个类初始化的时候调用”。就是虚拟机在load一个类的时候,就调用的。。。哦~,原来静态变量,是有单独的构造函数初始化的。

而 也是一个构造函数,这个就是我们常见的构造函数。里面也是对我们类块中的初始化,放到了构造函数当中进行初始化。

这里写图片描述

看了Smali的语法之后,其实干点“调试”的事。

我们原来的test方法中,拼接前面一串字符串,然后丢给button进行显示。现在如果说不丢给button显示,而是要用toast进行显示,我们在test函数下,修改相应的smali文件,重新打包。即可实现。

.line 28
const/4 v0, 0x0
# 调用Toast的静态方法 将参数 p1,p2,v0喂给makeText
 invoke-static {p1, p2, v0}, Landroid/widget/Toast;-  >makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;

 move-result-object v0

 invoke-virtual {v0}, Landroid/widget/Toast;->show()V
12345678

这里写图片描述

即重新生成了新的程序,包含了我们hook之后的代码。

可见,一个软件要被篡改是多么的容易!!!

不仅如此,常见的so库,也可以用IDEA PRO去侦探逻辑,修改逻辑。

所以,可见一个应用的安全是很重要的。

文章如果错误或者描述错误,请指出。

文章部分参考文献

《深入理解Java虚拟机 JVM高级特性与最佳实践》2版 周志明