构造Dex文件
Dex文件就是Dalvik可执行文件,实际上它就是一个优化后的java字节码文件,因此构造这类文件需要先写个java文件
Pino.java
1 | public class Pino { |
然后编译
1 | javac Pino.java |
之后得到了Pino.class文件,之后我们用dx工具,该工具需要安装Android SDK才能有的工具
1 | dex --dex --output=Pino.dex Pino.class |
这样就得到了一个dex文件了,之后我们利用010editor工具来进行分析。
Dex文件整体结构
那我们从头开始分析
Dex文件分析
首先,我们来看一下Dex文件头的结构体
1 | struct DexHeader { |
magic[8] 由8个u1类型的数据,内容是“64 65 78 0A 30 33 35 00”,u1就是1个字节的无符号数,这个是dex文件的标志,用来识别dex文件的。
checksum 没什么好说的,就是dex文件的校验和
signature[kSHA1DigestLen],就是整个dex文件进行SHA-1哈希计算得到的字符串,一般来说20个字节
fileSize, 整个dex文件的大小
headerSize, dex文件的头的大小
endianTag, dex文件的字节序标记,用于指定dex文件运行环境的cpu,预设值为0x12345678,在010editor的话就是“78 56 34 12”(小端序)
linkSize和linkOff, 链接段的大小和文件偏移,通常为0,linkSize为0表示静态链接
mapOff, DexMapList的文件偏移
stringIdsSize和stringIdsOff,这两个是DexStringId的个数和文件偏移
这里stringIdSize的值为0E,10进制就是14,也就是说这个dex文件的字符串的个数为14个,文件偏移是70,我们到70的位置看一下
蓝色部分就是DexStringId的内容了,每个字符串4字节,总共14个,我们先看一下第一组“76 01 00 00”,这个值并不是字符串的具体内容,而是字符串所在位置的文件偏移,我们去看一下176h这个位置
蓝色部分我一共选中了8个字节,其中第一个字节06代表的是之后多少个字节属于字符串,也就是“3C 69 6E 69 74 3E”,而最后一个字节的00其实是字符串结尾的空字节,但是计数的时候并没有算上而已,总结一下这个dex文件中所有的字符串如下:
序号|字符串
-|-
0|
1|Hello World
2|LPino;
3|Ljava/io/PrintStream;
4|Ljava/lang/Object;
5|Ljava/lang/String;
6|Ljava/lang/System;
7|Pino.java
8|V
9|VL
10|[Ljava/lang/String;
11|main
12|out
13|println
- typeIdSize和typeIdOff,就是类的类型个数和文件偏移,可以根据之前字符串的进行类比
typeIdSize的值为07,也就是说由7个类型,typeIdOff的值是A8h,我们到A8的位置看一下
蓝色选中的部分都是类型,但是这个一种数据结构1
2
3struct DexTypeId {
u4 descriptorIdx; //指向DexStringId列表的索引
}
先看一下第一个4字节的值“02 00 00 00 ”,对照之前我们整理的字符串的表格,就是LPino;即Pino类型的,整理一下所有的类型,如下
序号|类的类型
-|-
0|LPino;
1|Ljava/io/PrintStream;
2|Ljava/lang/Object;
3|Ljava/lang/String;
4|Ljava/lang/System;
5|V
6|[Ljava/lang/String;
- protoIdSize和protoIdOff,这两个是方法原型的个数和位置偏移
这里数量就是3,位置偏移为C4,跟过去看下
蓝色选中的部分就是所有的方法原型的结构了,这里又涉及到了一个新的数据结构
1 | struct DexProtoId { |
这三个属性分别是第一个是方法声明的字符串,第二个是方法的返回类型,第三个是方法的参数列表,其中DexTypeList是新的数据结构
1 | struct DexTypeList { |
1 | struct DexTypeItem { |
回过头来看一下蓝色部分,12个字节,第一个4字节为8,说明DexStringId列表的索引是8,也就是V,第二个4字节是5,也就是V,最后一个是0,也就是没有参数,第一个方法就是void (),整理一下其他的如下:
序号|方法原型
-|-
0|void()
1|void (java.lang.String)
2|void (java.lang.String[])
- fieldIdSize和fieldIdOff这两个是字段的数量和位置偏移
这里字段数是1,位置偏移为E8,字段也有新的数据结构1
2
3
4
5struct DexFieldId{
u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/
u2 typeIdx; /*字段类型,指向DexTypeId列表的索引*/
u4 nameIdx; /*字段名,指向DexStringId列表的索引*/
}
也就是一个DexFieldId是8个字节
classIdx的值是4,也就是Ljava/lang/System;,typeIdx的值是1,也就是Ljava/io/PrintStream;,nameIdx的值是C,也就是out,总结一下字段如下:
序号|字段
-|-
0|java.io.PrintStream java.lang.System.out
- methodIdSize和methodIdOff这两个分别是方法的数量和位置偏移
fieldIdSize的值是4,也就是有4个方法,fieldIdOff是F0,跟过去看下
新的数据结构如下:1
2
3
4
5struct DexMethodId{
u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/
u2 protoIdx; /*声明类型,指向DexProtoId列表的索引*/
u4 nameIdx; /*方法名,指向DexStringId列表的索引*/
}
也就是说每个DexMethodId占8个字节,第一个8字节中的classIdx的值是0,也就是LPino;,protoIdx的值也是0,也就是void(),第三nameIdx也是0,也就是
序号|方法
-|-
0|void Pino.
1|void Pino.main(java.lang.String[])
2|void java.io.PrintStream.println(java.lang.String)
3|void java.lang.Object.
- classDefsSize和classDefsOff是类的定义的数量和位置偏移
classDefsSize的值为1,说明就定义了一个类,然后在到110h的位置看看,但是这里还是有新的结构体1
2
3
4
5
6
7
8
9
10struct DexClassDef{
u4 classIdx; /*类的类型,指向DexTypeId列表的索引*/
u4 accessFlags; /*访问标志,就是表示是public还是private等等*/
u4 superclassIdx; /*父类类型,指向DexTypeId列表的索引*/
u4 interfacesOff; /*接口,指向DexTypeList的偏移*/
u4 sourceFileIdx; /*源文件名,指向DexStringId列表的索引*/
u4 annotationsOff; /*注解,指向DexAnnotationsDirectoryItem结构*/
u4 classDataOff; /*指向DexClassData结构的偏移*/
u4 staticValuesOff; /*指向DexEncodedArray结构的偏移*/
}
上面的数据结构28个字节,内容的话看注释也能看懂,我们直接上实例,在这里,classIdx是1,也就是LPino;,第二个accessFlags是1,也就是public,第三个superclassIdx是2,也就是父类是java.lang.Object,第四个interfacesOff是0,就是没有,第五个是sourceFileIdx是7,也就是Pino.java,第六个是annotationOff,是0,没有,第七个classData是22D,也就是DexClassData的偏移是22D,我们先来看看DexClassData的结构体
1 | struct DexClassData{ |
这里面又涉及到了其他三种结构体
1 | struct DexClassDataHeader{ |
这里需要注意的一点的就是这里的u4并不是值4字节,而是值uleb128的类型,具体是什么可以自行百度。
现在我们再去22D的位置看看
从这里可以判断姿态字段0个,实例字段0个,直接方法2个,虚方法0个。因为staticFields和instanceFields都是0个,所以直接从directMethods来看了,methodIdx为0,也就是void Pino.
表示这个方法是public的并且是构造方法,然后是codeOff的值是“B0 02”,转换为16进制就是130h。第二个函数的methodIdx的值是1,也就是void Pino.main(java.lang.String[]),accessFlags的值是09h,也就是ACC_PUBLIC和ACC_STATIC,codeOff的值是“CB 02”,转换为16进制就是14Bh,也就是位置偏移在14Bh处。