Dex文件分析,从magic到data的全字段,逐字节分析!小白也能懂

前言:上一次分析了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

类型字节长度说明
magicU8标识类型,列入class的文件是cafebabe,dex则是dex\n035\0
checksumU4(除 magic 和此字段之外的所有内容)的 adler32 校验和
signaturebyte[20]文件剩余内容(除 magic、checksum 和此字段之外的所有内容)的 SHA-1 签名(哈希);用于对文件进行唯一标识
file_sizeU4整个文件(包括标头)的大小,以字节为单位
header_sizeU4默认为0x70头文件(整个区段)的大小,以字节为单位。此项允许至少一定程度的向后/向前兼容性,而不会使格式失效。
endian_tagU4默认为低字符序,取值为0x12345678。如果是big endian则为0x78654321,说明进行过字节交换,这里具体可以查看android关于endian tag的说明
link_sizeU4链接区段的大小;如果此文件未进行静态链接,则该值为 0
link_offU4从文件开头到链接区段的偏移量,如果 link_size == 0,则该值为 0。该偏移量(如果为非零值)应该是到
map_offU4从文件开头到映射项的偏移量。该偏移量(必须为非零值)应该是到 data 区段的偏移量,而数据应采用下文中“map_list”指定的格式。

4.1.1 解析头文件

对应偏移位长度字符节说明
magic0x0000U864 65 78 0a 30 33 35 00长度为8的byte数组,转换成字符之后就是dex 035
checkSum0x0008U485 71 f0 2c其adler32校验和结果就是2CF07185的结果【高位字符序
signature0x000Cbyte[20]~0x10其结果是sha-1的加密,我这里的结果为:6003000070000000785634120000000000000000
file_size0x20U460 03 00 00计算得出为 864,说明当前有864个byte
header_size0x24U470 00 00 00一般情况下都为0x70
endian_tag0x28U478 56 34 12这里说明是已经是进行过交换处理
link_size0x2cU400 00 00 00说明没有静态链接,数量为0【何为静态链接,后面的文章应该会梳理。今天的内容不在此】
link_off0x30U400 00 00 00偏移量为0
map_off0x34U4C0 02 00 00数据位置偏移为704

4.2 索引区的分析

4.2.1 索引区的分析介绍

类型字节长度说明
string_ids_sizeU4字符串的数量
string_ids_offU4从文件开头到标识符的偏移量
type_ids_sizeU4类型标识符列表数量,最多为 65535
type_ids_offU4从文件开头到类型标识符列表的偏移量;如果 type_ids_size == 0(不可否认是一种奇怪的极端情况),则该值为 0。该偏移量(如果为非零值)应该是到 type_ids 区段开头的偏移量。
proto_ids_sizeU4原型标识符列表中的元素数量,最多为 65535
proto_ids_offU4原型从文件开头到标识符的偏移量
field_ids_sizeU4字段数量
field_ids_offU4从文件开头到字段标识符列表的偏移量
method_ids_sizeU4方法标识符列表中的元素数量
method_ids_offU4从文件开头到方法标识符列表的偏移量
class_defs_sizeU4类定义列表中的元素数量
class_defs_offU4方从文件开头到类定义列表的偏移量
data_sizeU4data 区段的大小(以字节为单位)。该数值必须是 sizeof(uint) 的偶数倍
method_ids_offU4从文件开头到 data 区段开头的偏移量

4.2.2 实例分析

类型偏移位字节码说明
string_ids_size0x3812 00 00 00说明存在18个字符串
string_ids_off0x3c70 00 00 00从文件开头到标识符的偏移量为112
type_ids_size0x4008 00 00 00类型标识符列表数量为:8
type_ids_off0x44b8 00 00 00从文件开头到类型标识符列表的偏移量为:0xb8
proto_ids_size0x4803 00 00 00原型标识符列表中的元素数量为:3
proto_ids_off0x4cd8 00 00 00原型从文件开头到标识符的偏移量为:0xd8
field_ids_size0x5004 00 00 00字段数量为4
field_ids_off0x54fc 00 00 00从文件开头到字段标识符列表的偏移量为0x54
method_ids_size0x5804 00 00 00方法标识符列表中的元素数量为:4
method_ids_off0x5c1c 01 00 00从文件开头到方法标识符列表的偏移量为:0x011c
class_defs_size0x6001 00 00 00类定义列表中的元素数量为:1
class_defs_off0x643c 01 00 00方从文件开头到类定义列表的偏移量0x013c
data_size0x6804 02 00 00data 区段的大小(以字节为单位)。该数值必须是 sizeof(uint) 的偶数倍,为:516
data_off0x6C5C 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_offU4C2 01 00 00当前string_id_item的索引为0的偏移为:0x01C2
string_data_offU4C6 01 00 00当前string_id_item的索引为1的偏移为:0x01C6
string_data_offU4CE 01 00 00当前string_id_item的索引为2的偏移为:0x01CE
string_data_offU4E8 01 00 00当前string_id_item的索引为3的偏移为:0x01E8
string_data_offU4FF 01 00 00当前string_id_item的索引为4的偏移为:0x01FF
string_data_offU413 02 00 00当前string_id_item的索引为5的偏移为:0x0213
string_data_offU427 02 00 00当前string_id_item的索引为6的偏移为:0x0227
string_data_offU43B 02 00 00当前string_id_item的索引为7的偏移为:0x023B
string_data_offU448 02 00 00当前string_id_item的索引为8的偏移为:0x0248
string_data_offU44b 02 00 00当前string_id_item的索引为9的偏移为:0x024B
string_data_offU44F 02 00 00当前string_id_item的索引为10的偏移为:0x024F
string_data_offU464 02 00 00当前string_id_item的索引为11的偏移为:0x0264
string_data_offU479 02 00 00当前string_id_item的索引为12的偏移为:0x0279
string_data_offU464 02 00 00当前string_id_item的索引为13的偏移为:0x0864
string_data_offU47E 02 00 00当前string_id_item的索引为14的偏移为:0x027E
string_data_offU482 02 00 00当前string_id_item的索引为15的偏移为:0x0282
string_data_offU427 02 00 00当前string_id_item的索引为16的偏移为:0x0227
string_data_offU486 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_idxU402 00 00 00当前type_id_item[0]的类型索引为:0x02
descriptor_idxU403 00 00 00当前type_id_item[1]的类型索引为:0x03
descriptor_idxU404 00 00 00当前type_id_item[2]的类型索引为:0x04
descriptor_idxU405 00 00 00当前type_id_item[3]的类型索引为:0x05
descriptor_idxU406 00 00 00当前type_id_item[4]的类型索引为:0x06
descriptor_idxU408 00 00 00当前type_id_item[5]的类型索引为:0x08
descriptor_idxU40a 00 00 00当前type_id_item[6]的类型索引为:0x0a
descriptor_idxU40b 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_idxU408 00 00 00当前proto_ids_size[0]的类型,简短类型索引为:0x08
return_type_idxU405 00 00 00当前proto_ids_size[0]的类型,返回类型索引为:0x05
parameters_offU400 00 00 00当前proto_ids_size[0]的类型,参数偏移:0x00 ,代表没有任何参数
shorty_idxU409 00 00 00当前proto_ids_size[1]的类型,简短类型索引为:0x09
return_type_idxU405 00 00 00当前proto_ids_size[1]的类型,返回类型索引为:0x05
parameters_offU4B4 01 00 00当前proto_ids_size[1]的类型,参数偏移:0x01B4
shorty_idxU409 00 00 00当前proto_ids_size[2]的类型,简短类型索引为:0x09
return_type_idxU405 00 00 00当前proto_ids_size[2]的类型,返回类型索引为:0x05
parameters_offU4BC 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_idxU200 00当前field_ids_size[0]的类型,类索引为0
type_idxU206 00当前field_ids_size[0]的类型,类型索引为6
name_idxU40C 00 00 00当前field_ids_size[0]的类型,名称索引为12
class_idxU200 00当前field_ids_size[1]的类型,类索引为0
type_idxU203 00当前field_ids_size[1]的类型,类型索引为3
name_idxU40D 00 00 00当前field_ids_size[1]的类型,名称索引为13
class_idxU200 00当前field_ids_size[2]的类型,类索引为0
type_idxU203 00当前field_ids_size[2]的类型,类型索引为3
name_idxU40E 00 00 00当前field_ids_size[2]的类型,名称索引为14
class_idxU204 00当前field_ids_size[3]的类型,类索引为4
type_idxU201 00当前field_ids_size[3]的类型,类型索引为1
name_idxU410 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_idxU200 00当前field_ids_size[0]的类型,类索引为0
proto_idxU200 00当前field_ids_size[0]的类型,proto_idx索引为0
name_idxU401 00 00 00当前field_ids_size[0]的类型,名称索引为1
class_idxU200 00当前field_ids_size[1]的类型,类索引为0
proto_idxU200 02当前field_ids_size[1]的类型,proto_idx索引为2
name_idxU40f 00 00 00当前field_ids_size[1]的类型,名称索引为15
class_idxU201 00当前field_ids_size[2]的类型,类索引为1
proto_idxU201 00当前field_ids_size[2]的类型,proto_idx索引为1
name_idxU411 00 00 00当前field_ids_size[2]的类型,名称索引为17
class_idxU202 00当前field_ids_size[3]的类型,类索引为2
proto_idxU200 00当前field_ids_size[3]的类型,proto_idx索引为0
name_idxU400 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_idxU200 00 00 00当前class_defs_size[0]的类型,类索引为0
access_flagsU201 00 00 00当前class_defs_size[0]的类型,access_flags的值为0x01
superclass_idxU402 00 00 00当前class_defs_size[0]的类型,superclass_idx索引为2
interfaces_offU400 00 00 00当前class_defs_size[0]的类型,interfaces_off偏移位为0
source_file_idxU407 00 00 00当前class_defs_size[0]的类型,source_file_idx偏移位为07
annotations_offU400 00 00 00当前class_defs_size[0]的类型,static_values_off偏移位为0
class_data_offU4A9 02 00 00当前class_defs_size[0]的类型,class_data_off偏移位为0x02A9
static_values_offU400 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数据字节码字符串
00x01C202(说明有2个字符)32 32 0022
10x01C66个字符3c 69 6e 69 74 3e 00< init>
20x01Ce24个字符4c 63 6f 6d 2f 63 6f 6d 70 61 6e 79 2f 6a 76 6d 2f 53 61 6d 70 6c 65 3b 00Lcom/company/jvm/Sample;
30x01e821字符4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b 00Ljava/io/PrintStream;
40x01ff18个字符4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b 00Ljava/lang/Object;
50x021318个字符4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00Ljava/lang/String;
60x022718个字符4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 3b 00Ljava/lang/System;
70x023b11个字符53 61 6d 70 6c 65 2e 6a 61 76 61 00Sample.java
80x02481个字符56v
90x024B2个字符56 4c 00vl
100x024f19个字符5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b 00[Ljava/lang/Object;
110x026419个字符5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 00[Ljava/lang/String;
120x02793个字符61 72 72 00arr
130x027e2个字符6d 31 00m1
140x02822个字符6d 32 00m2
150x02864个字符6d 61 69 6e 00main
160x28c3个字符6f 75 74 00out
170x2917个字符70 72 69 6e 74 6c 6e 00println

6.2 class_data_item

从上面class_defs_size中分析,引用自 class_def_item

6.3 map_list 引用自 header_item

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值