Android ART&CLASS,反编译原理
Java虚拟机(JVM)
对于JAVA程序来讲JVM就是一台计算机,和计算机一样它有处理器、内存、堆栈以及指令系统,Java程序在JVM上运行,它不需要考虑真实的物理计算机是什么平台,所以同一个Android APK可以在不同的硬件平台上运行。
Android虚拟机(Dalvik)
Dalvik虚拟机是Google专门为Android系统开发的,它是在apache的java虚拟机基础上针对移动设备做了专门的优化,允许在有限的内存中同时运行多个虚拟机的实例,降低每个实例对CPU和内存的资源占用,确保能够在CPU和内存相对有限的移动设备上也能适用。
ART
ART是在Android 4.4系统新增的一种应用运行模式,但是由于该模式相对于Dalivk会占用更大的存储空间,所以在4.4上默认是关闭的。一直到Android 5.0才默认开放。
Java 的跨平台特性决定了其可执行文件只能是与平台无关的字节码,而不是机器码。而Java虚拟机的主要工作之一就是当应用在运行时,将平台无法识别的字节码翻译成机器码。 ART模式与Dalvik模式最大的不同在于,Dalvik模式是在应用运行时将字节码解释成机器码,而ART模式是在应用安装时就将字节码翻译成机器码,所以应用在运行时效率要比Dalvik高。另外ART是基于寄存器的而Dalvik基于堆栈,和Dalvik相比没有了堆栈操作。
Android 应用开发涉及到的几个文件
- Java文件
应用程序源代码文件。
- Class文件
Java源码编译生成的目标文件,相当于C代码编译出来的.o文件。Java语言为了实现跨平台的目的,所以定义了一套与操作系统和硬件平台无关的字节码
格式,class文件里面记录的就是Java源码编译后的字节码文件。
- Dex文件
Android平台上的可执行文件,相当于windows平台上的.exe文件,该文件专门为在Android Dalvik虚拟机上运行而设计的,它将多个class字节码文件按照固定的格式整合优化成一个dex字节码文件。
- APK文件
Android应用的安装文件,它是由dex可执行文件以及资源文件如图片、布局等文件打包而成。
Class字节码文件分析
文件结构:
类型 |
名称 |
数量 |
u4 |
magic |
1 |
u2 |
minor_version |
1 |
u2 |
major_version |
1 |
u2 |
constant_pool_count |
1 |
cp_info |
constant_pool |
constant_pool_count - 1 |
u2 |
access_flags |
1 |
u2 |
this_class |
1 |
u2 |
super_class |
1 |
u2 |
interfaces_count |
1 |
u2 |
interfaces |
interfaces_count |
u2 |
fields_count |
1 |
field_info |
fields |
fields_count |
u2 |
methods_count |
1 |
method_info |
methods |
methods_count |
u2 |
attribute_count |
1 |
attribute_info |
attributes |
attributes_count |
- Magic:
class文件的魔数,class文件的魔数是一个固定的值:0XCAFEBABE
- minor_version major_version:
文件版本号,对应JDK版本。
- constant_pool_count和constant_pool:
常量池数和常量,常量池中记录了java文件中的所有的常量类型包括字符串、类名、方法名等,除了JAVA源代码中定义的的常量,常量池中还包含下面几种类型:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
除此之外常量池还描述了类的引用信息。
总共有如下12种类型:
入口类型 |
标志值 |
描述 |
CONSTANT_Utf8 |
1 |
JAVA源码中的各种字符串,字符串常量,类的全限定名,字段、方法的描述符,引用的字段和方法的描述符等 |
CONSTANT_Integer |
3 |
int类型字面值 |
CONSTANT_Float |
4 |
float类型字面值 |
CONSTANT_Long |
5 |
long类型字面值 |
CONSTANT_Double |
6 |
double类型字面值 |
CONSTANT_Class |
7 |
对一个类或接口的符号引用 |
CONSTANT_String |
8 |
String类型字面值 |
CONSTANT_Fieldref |
9 |
对一个字段的符号引用 |
CONSTANT_Methodref |
10 |
对一个类中声明的方法的符号引用 |
CONSTANT_InterfaceMethodref |
11 |
对一个接口中声明的方法的符号引用 |
CONSTANT_NameAndType |
12 |
对一个字段或方法的部分符号引用 |
CONSTANT_MethodHandle |
15 |
表示方法句柄 |
CONSTANT_MethodType |
16 |
标志方法类型 |
CONSTANT_InvokeDynamic |
18 |
表示一个动态方法调用点 |
每种常量类型结构表
常量 |
字节项 |
类型 |
描述 |
CONSTANT_Utf8 |
tag |
u1 |
1 |
length |
u2 |
|
|
bytes |
u1 |
|
|
CONSTANT_Integer |
tag |
u1 |
3 |
bytes |
u4 |
|
|
CONSTANT_Float |
tag |
u1 |
4 |
bytes |
u4 |
|
|
CONSTANT_Long |
tag |
u1 |
5 |
bytes |
u8 |
|
|
CONSTANT_Double |
tag |
u1 |
6 |
bytes |
u8 |
|
|
CONSTANT_Class |
tag |
u1 |
7 |
index |
u2 |
|
|
CONSTANT_String |
tag |
u1 |
8 |
index |
u2 |
|
|
CONSTANT_Fieldref |
tag |
u1 |
9 |
index |
u2 |
指向CONSTANT_Class的索引值 |
|
index |
u2 |
指向CONSTANT_NameAndType的索引值 |
|
CONSTANT_Methodref |
tag |
u1 |
10 |
index |
u2 |
指向CONSTANT_Class的索引值 |
|
index |
u2 |
指向CONSTANT_Class的索引值 |
|
CONSTANT_InterfaceMethodref |
tag |
u1 |
11 |
index |
u2 |
指向CONSTANT_Class的索引值 |
|
index |
u2 |
指向CONSTANT_Class的索引值 |
|
CONSTANT_NameAndType |
tag |
u1 |
12 |
index |
u2 |
指向该字段或方法名称常量的索引值 |
|
index |
u2 |
指向该字段或方法描述符常量的索引值 |
|
CONSTANT_MethodHandle |
tag |
u1 |
15 |
reference_kind |
u1 |
值必须1~9,它决定了方法句柄的的类型 |
|
reference_index |
u2 |
对常量池的索引 |
|
CONSTANT_MethodType |
tag |
u1 |
16 |
index |
u2 |
对常量池中方法描述符的索引 |
|
CONSTANT_InvokeDynamic |
tag |
u1 |
18 |
index |
u2 |
对引导方法表的索引 |
|
index |
u2 |
指向CONSTANT_NameAndType的索引值 |
- Access_flags
它定义了java源文件中类或者接口的类型
标志名 |
值 |
含义 |
设置者 |
ACC_PUBLIC |
0x0001 |
public类型 |
类和接口 |
ACC_FINAL |
0x0010 |
类为final类型 |
类 |
ACC_SUPER |
0x0020 |
使用新型的invokespecial语义 |
类和接口 |
ACC_INTERFACE |
0x0200 |
接口类型,不是类类型 |
接口 |
ACC_ABSTRACT |
0x0400 |
abstract类型 |
接口和部分类 |
- this_class
对常量池的索引。在this_class位置的常量池入口必须为CONSTANT_Class表,该表索引指向一个CONSTANT_Utf8,该CONSTANT_Utf8存放该类的全限定名。
- super_class
对常量池索引,在super_class位置的常量池入口是一个指向该类超类全限定名的CONSTANT_Class入口,如果该类直接继承自object类,那索引值为0.
- interfaces_count和interfaces
由该类自己实现的或者该类继承后扩展的接口。
APK安全
根据上面介绍的Class文件格式可以看到,Class文件完整描述了Java源码中的所有信息,所以根据固定的class文件格式,可以反编译出java源码。
我们用一个简单的java源文件举例:
原始的源码文件helloworld.java如下:
编译生成的helloworld.class如下:
反编译器查看helloworld.class的文件如下:
反编译后,我们能看到源码文件的所有实现。
Android的APK安装包中使用的是dex文件,之前我们说过dex文件格式是class文件的合并优化,也有其固定的文件格式。所以同样能够被反编译出来。
如果我们的APK能够被反编译出来,不但泄漏了我们的产品实现原理,而且对方还可以修改源码,篡改功能并重新发布。
目前APK安全主要有以下几种方法:
- 代码native化
利用android的NDK最大化的用C/C++语言来实现功能,比如支持宝。
- 代码混淆技术
class常量池中的CONSTANT_Utf8_info中包含了源码文件中的所有字符信息,比如类的全限定名,方法名,变量名,字符串常量以及代码注释。而这些方法,变量名往往都能代表具体的功能含义,比如读、写操作的方法名中往往带有read、write。反编译者能够根据这些读懂我们的产品功能。
代码混淆技术就是将class文件中这些能够代表具体含义的字符替换成没有意义的字符,比如将read、wirte替换成aaaa、bbbb,从而增加反编译后的阅读难度。
- 虚拟机保护(VMP)技术
VMP是代码混淆的一种衍生技术,虚拟机保护软件首先定义一套自己的字节码指令集,然后用该字节码替换应用中的原始字节码指令,同时为该应用增加一个虚拟机解释引擎,应用在真实使用时,该解释引擎会读取指令并解释执行。整个过程不会对应用的功能产生任何影响。
目前VMP技术有了更进一步的发展,除了上面提到的指令集替换,还可以对dex文件进行加密处理,使整个应用更加安全可靠。目前国内这方面,做的比较好的有爱加密、梆梆等公司。