前言:上一次分析了class字节码的文件,然后继续看书的过程中。需要自己制作分析一个dex的工具。制作工具之前,首先是得明白原理,然后再如说是没问题的,今天就此记录我的分析理解。如有不足之处希望大家指出!
1、class和dex文件的宏观区别
dex文件是从class字节码文件中优化出来的产物,class字节码是对于java虚拟机来说的。则dex文件则是对前者优化之后提供给android(安卓davlik虚拟机,应该是安卓4.4版本之前)来说。
2、dex文件和class字节码的基本区别
通过上面的说明,大致我们能明白,dex是建立在class基础之上优化过来的。那两者的区别有哪些呢?
区别 | class字节码 | dex文件 |
---|---|---|
endian(字符序) | 低位在右边,高位在左 | 低位在左,高位在后 |
文件区分不同 | 常量池/data | 头文件/索引区/数据区 |
LEB128 | 无 | 在int基础之上变种的数据类型,高位第7位数(索引为0)据表示是否结束,如果为1则表示后面还有字节。如果第7位表示0,则终止。当第二个或者第三个字节有存在的时候,第二个字节的第0索引,对应的是第一个字节的第七位!每个字节的第七位是判断位 |
3、dex文件实例分析
3.1配置dx环境变量
我相信能看到这里的人,java环境已经是配置好了的。
找到<SDK_HOME>/build-tools/<VERSION>
这里是你会看到一个dx.bat的批处理脚本。
打开我的电脑,环境变量中,找到环境变量中的path,将全路径放进入。
此时,按住win+r输入cmd,打出dx --version
dx --version
dx version 1.16
3.2Java源文件
package com.company.jvm;
public class Sample {
public String m1;
public String m2;
public Object [] arr;
public static void main(String[] args) {
Sample sample = new Sample();
sample.m1="22";
sample.arr=new Object[12];
System.out.println(sample.m1);
}
}
3.3 编译成class二进制码
javac Sample
3.4 将二进制class文件优化成dex文件
dx --dex --output=Sample.dex com/comany/jvm/Sample.class
输入上面的指令之后,有可能会报错,出现的问题为:
class name (com/company/jvm/Sample) does not match path (Sample.class)
...while parsing Sample.class
大致意思就说,找不到路径。此时我们需要到包名的根目录下,进入cmd。
4、分析dex文件
上面已经说过,dex文件基本可以分为三个区域:1、头文件 2、索引区 3、数据区
4.1头文件 header
类型 | 字节长度 | 说明 |
---|---|---|
magic | U8 | 标识类型,列入class的文件是cafebabe,dex则是dex\n035\0 |
checksum | U4 | (除 magic 和此字段之外的所有内容)的 adler32 校验和 |
signature | byte[20] | 文件剩余内容(除 magic、checksum 和此字段之外的所有内容)的 SHA-1 签名(哈希);用于对文件进行唯一标识 |
file_size | U4 | 整个文件(包括标头)的大小,以字节为单位 |
header_size | U4 | 默认为0x70头文件(整个区段)的大小,以字节为单位。此项允许至少一定程度的向后/向前兼容性,而不会使格式失效。 |
endian_tag | U4 | 默认为低字符序,取值为0x12345678。如果是big endian则为0x78654321,说明进行过字节交换,这里具体可以查看android关于endian tag的说明 |
link_size | U4 | 链接区段的大小;如果此文件未进行静态链接,则该值为 0 |
link_off | U4 | 从文件开头到链接区段的偏移量,如果 link_size == 0,则该值为 0。该偏移量(如果为非零值)应该是到 |
map_off | U4 | 从文件开头到映射项的偏移量。该偏移量(必须为非零值)应该是到 data 区段的偏移量,而数据应采用下文中“map_list”指定的格式。 |
4.1.1 解析头文件
对应 | 偏移位 | 长度 | 字符节 | 说明 |
---|---|---|---|---|
magic | 0x0000 | U8 | 64 65 78 0a 30 33 35 00 | 长度为8的byte数组,转换成字符之后就是dex 035 |
checkSum | 0x0008 | U4 | 85 71 f0 2c | 其adler32校验和结果就是2CF07185的结果【高位字符序】 |
signature | 0x000C | byte[20] | ~0x10 | 其结果是sha-1的加密,我这里的结果为:6003000070000000785634120000000000000000 |
file_size | 0x20 | U4 | 60 03 00 00 | 计算得出为 864,说明当前有864个byte |
header_size | 0x24 | U4 | 70 00 00 00 | 一般情况下都为0x70 |
endian_tag | 0x28 | U4 | 78 56 34 12 | 这里说明是已经是进行过交换处理 |
link_size | 0x2c | U4 | 00 00 00 00 | 说明没有静态链接,数量为0【何为静态链接,后面的文章应该会梳理。今天的内容不在此】 |
link_off | 0x30 | U4 | 00 00 00 00 | 偏移量为0 |
map_off | 0x34 | U4 | C0 02 00 00 | 数据位置偏移为704 |
4.2 索引区的分析
4.2.1 索引区的分析介绍
类型 | 字节长度 | 说明 |
---|---|---|
string_ids_size | U4 | 字符串的数量 |
string_ids_off | U4 | 从文件开头到标识符的偏移量 |
type_ids_size | U4 | 类型标识符列表数量,最多为 65535 |
type_ids_off | U4 | 从文件开头到类型标识符列表的偏移量;如果 type_ids_size == 0(不可否认是一种奇怪的极端情况),则该值为 0。该偏移量(如果为非零值)应该是到 type_ids 区段开头的偏移量。 |
proto_ids_size | U4 | 原型标识符列表中的元素数量,最多为 65535 |
proto_ids_off | U4 | 原型从文件开头到标识符的偏移量 |
field_ids_size | U4 | 字段数量 |
field_ids_off | U4 | 从文件开头到字段标识符列表的偏移量 |
method_ids_size | U4 | 方法标识符列表中的元素数量 |
method_ids_off | U4 | 从文件开头到方法标识符列表的偏移量 |
class_defs_size | U4 | 类定义列表中的元素数量 |
class_defs_off | U4 | 方从文件开头到类定义列表的偏移量 |
data_size | U4 | data 区段的大小(以字节为单位)。该数值必须是 sizeof(uint) 的偶数倍 |
method_ids_off | U4 | 从文件开头到 data 区段开头的偏移量 |
4.2.2 实例分析
类型 | 偏移位 | 字节码 | 说明 |
---|---|---|---|
string_ids_size | 0x38 | 12 00 00 00 | 说明存在18个字符串 |
string_ids_off | 0x3c | 70 00 00 00 | 从文件开头到标识符的偏移量为112 |
type_ids_size | 0x40 | 08 00 00 00 | 类型标识符列表数量为:8 |
type_ids_off | 0x44 | b8 00 00 00 | 从文件开头到类型标识符列表的偏移量为:0xb8 |
proto_ids_size | 0x48 | 03 00 00 00 | 原型标识符列表中的元素数量为:3 |
proto_ids_off | 0x4c | d8 00 00 00 | 原型从文件开头到标识符的偏移量为:0xd8 |
field_ids_size | 0x50 | 04 00 00 00 | 字段数量为4 |
field_ids_off | 0x54 | fc 00 00 00 | 从文件开头到字段标识符列表的偏移量为0x54 |
method_ids_size | 0x58 | 04 00 00 00 | 方法标识符列表中的元素数量为:4 |
method_ids_off | 0x5c | 1c 01 00 00 | 从文件开头到方法标识符列表的偏移量为:0x011c |
class_defs_size | 0x60 | 01 00 00 00 | 类定义列表中的元素数量为:1 |
class_defs_off | 0x64 | 3c 01 00 00 | 方从文件开头到类定义列表的偏移量0x013c |
data_size | 0x68 | 04 02 00 00 | data 区段的大小(以字节为单位)。该数值必须是 sizeof(uint) 的偶数倍,为:516 |
data_off | 0x6C | 5C 01 00 00 | 从文件开头到 data 区段开头的偏移量为015C,转换成int为348 |
后文待续,坐了一个多小时了,休息会儿
写一篇文章真坚信,公司的项目出问题,一个电话过去做了个解决方案。到现在已经过去了相当于24个小时了。心里总是有事儿惦记着,记得当时一个朋友(恩师)说过【我不是科班出身】,当你明白一个原理的时候,最简单的方式就是用自己的话去表达处理!啰嗦太多了,继续接着分析数据区。
这里有个坑,看书的朋友或者类似我耍小聪明的人,自认为找到了一定的规律的要注意了。这时候我们一定要看一下官方的文档说明,地址我留一个安卓Dex Source解读。
5、DATA数据区
这里我们来回顾一下,上面的strings_ids_size,它的偏移位是0x70 ,下面我们继续分析:
5.1 string_data_item(18个,承上)
struct string_id_item{
unit string_data_off;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
string_data_off | U4 | C2 01 00 00 | 当前string_id_item的索引为0的偏移为:0x01C2 |
string_data_off | U4 | C6 01 00 00 | 当前string_id_item的索引为1的偏移为:0x01C6 |
string_data_off | U4 | CE 01 00 00 | 当前string_id_item的索引为2的偏移为:0x01CE |
string_data_off | U4 | E8 01 00 00 | 当前string_id_item的索引为3的偏移为:0x01E8 |
string_data_off | U4 | FF 01 00 00 | 当前string_id_item的索引为4的偏移为:0x01FF |
string_data_off | U4 | 13 02 00 00 | 当前string_id_item的索引为5的偏移为:0x0213 |
string_data_off | U4 | 27 02 00 00 | 当前string_id_item的索引为6的偏移为:0x0227 |
string_data_off | U4 | 3B 02 00 00 | 当前string_id_item的索引为7的偏移为:0x023B |
string_data_off | U4 | 48 02 00 00 | 当前string_id_item的索引为8的偏移为:0x0248 |
string_data_off | U4 | 4b 02 00 00 | 当前string_id_item的索引为9的偏移为:0x024B |
string_data_off | U4 | 4F 02 00 00 | 当前string_id_item的索引为10的偏移为:0x024F |
string_data_off | U4 | 64 02 00 00 | 当前string_id_item的索引为11的偏移为:0x0264 |
string_data_off | U4 | 79 02 00 00 | 当前string_id_item的索引为12的偏移为:0x0279 |
string_data_off | U4 | 64 02 00 00 | 当前string_id_item的索引为13的偏移为:0x0864 |
string_data_off | U4 | 7E 02 00 00 | 当前string_id_item的索引为14的偏移为:0x027E |
string_data_off | U4 | 82 02 00 00 | 当前string_id_item的索引为15的偏移为:0x0282 |
string_data_off | U4 | 27 02 00 00 | 当前string_id_item的索引为16的偏移为:0x0227 |
string_data_off | U4 | 86 02 00 00 | 当前string_id_item的索引为17的偏移为:0x0286 |
5.2 type_ids_size(8个,承上,此数据的偏移位为:0xb8)
struct type_id_item{
unit descriptor_idx;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
descriptor_idx | U4 | 02 00 00 00 | 当前type_id_item[0]的类型索引为:0x02 |
descriptor_idx | U4 | 03 00 00 00 | 当前type_id_item[1]的类型索引为:0x03 |
descriptor_idx | U4 | 04 00 00 00 | 当前type_id_item[2]的类型索引为:0x04 |
descriptor_idx | U4 | 05 00 00 00 | 当前type_id_item[3]的类型索引为:0x05 |
descriptor_idx | U4 | 06 00 00 00 | 当前type_id_item[4]的类型索引为:0x06 |
descriptor_idx | U4 | 08 00 00 00 | 当前type_id_item[5]的类型索引为:0x08 |
descriptor_idx | U4 | 0a 00 00 00 | 当前type_id_item[6]的类型索引为:0x0a |
descriptor_idx | U4 | 0b 00 00 00 | 当前type_id_item[7]的类型索引为:0x0b |
5.3 proto_ids_size(3个,承上,此数据的偏移位为:0xd8)
struct proto_id_item{
uint shorty_idx;
uint return_type_idx;
uiny parameters_off;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
shorty_idx | U4 | 08 00 00 00 | 当前proto_ids_size[0]的类型,简短类型索引为:0x08 |
return_type_idx | U4 | 05 00 00 00 | 当前proto_ids_size[0]的类型,返回类型索引为:0x05 |
parameters_off | U4 | 00 00 00 00 | 当前proto_ids_size[0]的类型,参数偏移:0x00 ,代表没有任何参数 |
shorty_idx | U4 | 09 00 00 00 | 当前proto_ids_size[1]的类型,简短类型索引为:0x09 |
return_type_idx | U4 | 05 00 00 00 | 当前proto_ids_size[1]的类型,返回类型索引为:0x05 |
parameters_off | U4 | B4 01 00 00 | 当前proto_ids_size[1]的类型,参数偏移:0x01B4 |
shorty_idx | U4 | 09 00 00 00 | 当前proto_ids_size[2]的类型,简短类型索引为:0x09 |
return_type_idx | U4 | 05 00 00 00 | 当前proto_ids_size[2]的类型,返回类型索引为:0x05 |
parameters_off | U4 | BC 01 00 00 | 当前proto_ids_size[2]的类型,参数偏移:0x01BC |
5.4 field_ids_size(4个,承上,此数据的偏移位为:0xfc)
struct field_ids_size{
ushort class_idx;
ushort type_idx;
uint name_idx;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
class_idx | U2 | 00 00 | 当前field_ids_size[0]的类型,类索引为0 |
type_idx | U2 | 06 00 | 当前field_ids_size[0]的类型,类型索引为6 |
name_idx | U4 | 0C 00 00 00 | 当前field_ids_size[0]的类型,名称索引为12 |
class_idx | U2 | 00 00 | 当前field_ids_size[1]的类型,类索引为0 |
type_idx | U2 | 03 00 | 当前field_ids_size[1]的类型,类型索引为3 |
name_idx | U4 | 0D 00 00 00 | 当前field_ids_size[1]的类型,名称索引为13 |
class_idx | U2 | 00 00 | 当前field_ids_size[2]的类型,类索引为0 |
type_idx | U2 | 03 00 | 当前field_ids_size[2]的类型,类型索引为3 |
name_idx | U4 | 0E 00 00 00 | 当前field_ids_size[2]的类型,名称索引为14 |
class_idx | U2 | 04 00 | 当前field_ids_size[3]的类型,类索引为4 |
type_idx | U2 | 01 00 | 当前field_ids_size[3]的类型,类型索引为1 |
name_idx | U4 | 10 00 00 00 | 当前field_ids_size[3]的类型,名称索引为16 |
5.5 method_ids_size(4个,承上,此数据的偏移位为:0x011c)
struct method_ids_size{
ushort class_idx;
ushort proto_idx;
uint name_idx;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
class_idx | U2 | 00 00 | 当前field_ids_size[0]的类型,类索引为0 |
proto_idx | U2 | 00 00 | 当前field_ids_size[0]的类型,proto_idx索引为0 |
name_idx | U4 | 01 00 00 00 | 当前field_ids_size[0]的类型,名称索引为1 |
class_idx | U2 | 00 00 | 当前field_ids_size[1]的类型,类索引为0 |
proto_idx | U2 | 00 02 | 当前field_ids_size[1]的类型,proto_idx索引为2 |
name_idx | U4 | 0f 00 00 00 | 当前field_ids_size[1]的类型,名称索引为15 |
class_idx | U2 | 01 00 | 当前field_ids_size[2]的类型,类索引为1 |
proto_idx | U2 | 01 00 | 当前field_ids_size[2]的类型,proto_idx索引为1 |
name_idx | U4 | 11 00 00 00 | 当前field_ids_size[2]的类型,名称索引为17 |
class_idx | U2 | 02 00 | 当前field_ids_size[3]的类型,类索引为2 |
proto_idx | U2 | 00 00 | 当前field_ids_size[3]的类型,proto_idx索引为0 |
name_idx | U4 | 00 00 00 00 | 当前field_ids_size[3]的类型,名称索引为0 |
5.6 class_defs_size(1个,承上,此数据的偏移位为:0x013c)
struct class_def{
uint class_idx;
uint access_flags;
uint superclass_idx;
uint interfaces_off;
unit source_file_idx
uint annotations_off;
unit class_data_off;
uint static_values_off;
}
名称 | 长度 | 字节码 | 说明BIG Endian |
---|---|---|---|
class_idx | U2 | 00 00 00 00 | 当前class_defs_size[0]的类型,类索引为0 |
access_flags | U2 | 01 00 00 00 | 当前class_defs_size[0]的类型,access_flags的值为0x01 |
superclass_idx | U4 | 02 00 00 00 | 当前class_defs_size[0]的类型,superclass_idx索引为2 |
interfaces_off | U4 | 00 00 00 00 | 当前class_defs_size[0]的类型,interfaces_off偏移位为0 |
source_file_idx | U4 | 07 00 00 00 | 当前class_defs_size[0]的类型,source_file_idx偏移位为07 |
annotations_off | U4 | 00 00 00 00 | 当前class_defs_size[0]的类型,static_values_off偏移位为0 |
class_data_off | U4 | A9 02 00 00 | 当前class_defs_size[0]的类型,class_data_off偏移位为0x02A9 |
static_values_off | U4 | 00 0 000 00 | 当前class_defs_size[0]的类型,static_values_off偏移位为0 |
6 读取相应数据
6.1string_ids_data(18)
在前面data区介绍中,我们可以看到18个常量,相对偏移量,按照指定的数值进行换算成数据:
struct string_data_item{
uleb128 utf16_size;
ubyte[] data; //在c/c++中byte的字符串,最后一个字符必须是\0,表示字符出结束
}
索引 | 偏移 | utf16_size(byte数据大小要+1)U1 | 数据字节码 | 字符串 |
---|---|---|---|---|
0 | 0x01C2 | 02(说明有2个字符) | 32 32 00 | 22 |
1 | 0x01C6 | 6个字符 | 3c 69 6e 69 74 3e 00 | < init> |
2 | 0x01Ce | 24个字符 | 4c 63 6f 6d 2f 63 6f 6d 70 61 6e 79 2f 6a 76 6d 2f 53 61 6d 70 6c 65 3b 00 | Lcom/company/jvm/Sample; |
3 | 0x01e8 | 21字符 | 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 00 | Ljava/io/PrintStream; |
4 | 0x01ff | 18个字符 | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b 00 | Ljava/lang/Object; |
5 | 0x0213 | 18个字符 | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 | Ljava/lang/String; |
6 | 0x0227 | 18个字符 | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 3b 00 | Ljava/lang/System; |
7 | 0x023b | 11个字符 | 53 61 6d 70 6c 65 2e 6a 61 76 61 00 | Sample.java |
8 | 0x0248 | 1个字符 | 56 | v |
9 | 0x024B | 2个字符 | 56 4c 00 | vl |
10 | 0x024f | 19个字符 | 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b 00 | [Ljava/lang/Object; |
11 | 0x0264 | 19个字符 | 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00 | [Ljava/lang/String; |
12 | 0x0279 | 3个字符 | 61 72 72 00 | arr |
13 | 0x027e | 2个字符 | 6d 31 00 | m1 |
14 | 0x0282 | 2个字符 | 6d 32 00 | m2 |
15 | 0x0286 | 4个字符 | 6d 61 69 6e 00 | main |
16 | 0x28c | 3个字符 | 6f 75 74 00 | out |
17 | 0x291 | 7个字符 | 70 72 69 6e 74 6c 6e 00 | println |
6.2 class_data_item
从上面class_defs_size中分析,引用自 class_def_item