dex是Android平台上(Dalvik虚拟机)的可执行文件, 相当于Windows平台中的exe文件, 每个Apk安装包中都有dex文件, 里面包含了该app的所有源码, 通过反编译工具可以获取到相应的java源码。
public class Hello { public static void MyPrint(String str) { System.out.printf(str + "\r\n"); } public static void main(String[] argc) { MyPrint("nihao, shijie"); System.out.println("Hello World!"); } }
javac Hello.java
dx --dex --output=Hello.dex Hello.class
javac -source 1.6 -target 1.6 Hello.java
adb push Hello.dex /mnt/sdcard/
adb shell dalvikvm -cp /mnt/sdcard/Hello.dex Hello
字段名称 | 偏移量 | 长度(byte) | 当前例子中字段值 | 字段描述 |
magic | 0x0 | 0x8 | dex 035 | dex魔术字, 固定信息: dex\n035 |
checksum | 0x8 | 0x4 | 0x0F828C9C |
alder32算法, 去除了magic和checksum 字段之外的所有内容的校验码 |
signature | 0xc | 0x14 |
58339636BED8A6CC826E A09B77D5C3A620262CD |
sha-1签名, 去除了magic、checksum和 signature字段之外的所有内容的签名 |
fileSize | 0x20 | 0x4 | 0x0000043C | 整个dex的文件大小 |
headerSize | 0x24 | 0x4 | 0x00000070 | 整个dex文件头的大小 (固定大小为0x70) |
endianTag | 0x28 | 0x4 | 0x12345678 |
字节序 (大尾方式、小尾方式) 默认为小尾方式 <--> 0x12345678 |
linkSize | 0x2c | 0x4 | 0x00000000 | 链接段的大小, 默认为0表示静态链接 |
linkOff | 0x30 | 0x4 | 0x00000000 | 链接段开始偏移 |
mapOff | 0x34 | 0x4 | 0x0000039C | map_item偏移 |
stringIdsSize | 0x38 | 0x4 | 0x00000019 | 字符串列表中的字符串个数 |
stringIdsOff | 0x3c | 0x4 | 0x00000070 | 字符串列表偏移 |
typeIdsSize | 0x40 | 0x4 | 0x00000009 | 类型列表中的类型个数 |
typeIdsOff | 0x44 | 0x4 | 0x000000D4 | 类型列表偏移 |
protoIdsSize | 0x48 | 0x4 | 0x00000006 | 方法声明列表中的个数 |
protoIdsOff | 0x4c | 0x4 | 0x000000F8 | 方法声明列表偏移 |
fieldIdsSize | 0x50 | 0x4 | 0x00000001 | 字段列表中的个数 |
fieldIdsOff | 0x54 | 0x4 | 0x00000140 | 字段列表偏移 |
methodIdsSize | 0x58 | 0x4 | 0x00000009 | 方法列表中的个数 |
methodIdsOff | 0x5c | 0x4 | 0x00000148 | 方法列表偏移 |
classDefsSize | 0x60 | 0x4 | 0x00000001 | 类定义列表中的个数 |
classDefsOff | 0x64 | 0x4 | 0x00000190 | 类定义列表偏移 |
dataSize | 0x68 | 0x4 | 0x0000028C | 数据段的大小, 4字节对齐 |
dataOff | 0x6c | 0x4 | 0x000001B0 | 数据段偏移 |
//Direct-mapped "string_id_item". struct DexStringId { u4 stringDataOff; //file offset to string_data_item };
index | stringDataOff | utf16_size | data | string |
0 | 0x252 | 0x02 | 0x0D, 0x0A , 0x00 | 回车换行 |
1 | 0x256 | 0x06 | 0x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x00 | <init> |
2 | 0x25E | 0x0C | 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x00 | Hello World! |
3 | 0x26C | 0x0A | 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2E, 0x6A, 0x61, 0x76, 0x61, 0x00 | Hello.java |
4 | 0x278 | 0x01 | 0x4C, 0x00 | L |
5 | 0x27B | 0x07 | 0x4C, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x3B, 0x00 | LHello; |
6 | 0x284 | 0x02 | 0x4C, 0x4C, 0x00 | LL |
7 | 0x288 | 0x03 | 0x4C, 0x4C, 0x4C, 0x00 | LLL |
8 | 0x28D | 0x15 | 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x00 | Ljava/io/PrintStream; |
9 | 0x2A4 | 0x12 | 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x00 | Ljava/lang/Object; |
10 | 0x2B8 | 0x12 | 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x00 | Ljava/lang/String; |
11 | 0x2CC | 0x19 | 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x65, 0x72, 0x3B, 0x00 | Ljava/lang/StringBuilder; |
12 | 0x2E7 | 0x12 | 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x3B, 0x00 | Ljava/lang/System; |
13 | 0x2FB | 0x07 | 0x4D, 0x79, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x00 | MyPrint |
14 | 0x304 | 0x01 | 0x56, 0x00 | V |
15 | 0x307 | 0x02 | 0x56, 0x4C, 0x00 | VL |
16 | 0x30B | 0x13 | 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x00 | [Ljava/lang/Object; |
17 | 0x320 | 0x13 | 0x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x00 | [Ljava/lang/String; |
18 | 0x335 | 0x06 | 0x61, 0x70, 0x70, 0x65, 0x6E, 0x64, 0x00 | append |
19 | 0x33D | 0x04 | 0x6D, 0x61, 0x69, 0x6E, 0x00 | main |
20 | 0x343 | 0x0D | 0x6E, 0x69, 0x68, 0x61, 0x6F, 0x2C, 0x20, 0x73, 0x68, 0x69, 0x6A, 0x69, 0x65, 0x00 | nihao, shijie |
21 | 0x352 | 0x03 | 0x6F, 0x75, 0x74, 0x00 | out |
22 | 0x357 | 0x06 | 0x70, 0x72, 0x69, 0x6E, 0x74, 0x66, 0x00 | printf |
23 | 0x35F | 0x07 | 0x70, 0x72, 0x69, 0x6E, 0x74, 0x6C, 0x6E, 0x00 | println |
24 | 0x368 | 0x08 | 0x74, 0x6F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x00 | toString |
//Direct-mapped "type_id_item". struct DexTypeId { u4 descriptorIdx; //DexStringId中的索引下标 };
index | descriptorIdx | string |
0 | 0x05 | LHello; |
1 | 0x08 | Ljava/io/PrintStream; |
2 | 0x09 | Ljava/lang/Object; |
3 | 0x0A | Ljava/lang/String; |
4 | 0x0B | Ljava/lang/StringBuilder; |
5 | 0x0C | Ljava/lang/System; |
6 | 0x0E | V |
7 | 0x10 | [Ljava/lang/Object; |
8 | 0x11 | [Ljava/lang/String; |
//Direct-mapped "proto_id_item". struct DexProtoId { u4 shortyIdx; //DexStringId中的索引下标 u4 returnTypeIdx; //DexTypeId中的索引下标 u4 parametersOff; //DexTypeList的偏移 };
//Direct-mapped "type_item". struct DexTypeItem { u2 typeIdx; //DexTypeId中的索引下标 }; //rect-mapped "type_list". struct DexTypeList { u4 size; //DexTypeItem的个数 DexTypeItem list[1]; //DexTypeItem变长数组 };
index | shortyIdx | returnTypeIdx | parametersOff | shortyIdx_string | returnTypeIdx_string |
0 | 0x07 | 0x01 | 0x23C | LLL | Ljava/io/PrintStream; |
1 | 0x04 | 0x03 | 0x0 | L | Ljava/lang/String; |
2 | 0x06 | 0x04 | 0x244 | LL | Ljava/lang/StringBuilder; |
3 | 0x0E | 0x06 | 0x0 | V | V |
4 | 0x0F | 0x06 | 0x244 | VL | V |
5 | 0x0F | 0x06 | 0x24C | VL | V |
parametersOff | typeIdx | string |
0x23C | 0x03 | Ljava/lang/String; |
0x23C | 0x07 | [Ljava/lang/Object; |
0x244 | 0x03 | Ljava/lang/String; |
0x24C | 0x08 | [Ljava/lang/String; |
//Direct-mapped "field_id_item". struct DexFieldId { u2 classIdx; 类的类型, DexTypeId中的索引下标 u2 typeIdx; 字段类型, DexTypeId中的索引下标 u4 nameIdx; 字段名称, DexStringId中的索引下标 };
index | classIdx | typeIdx | nameIdx | classIdx_string | typeIdx_string | nameIdx_string |
0 | 0x05 | 0x01 | 0x15 | Ljava/lang/System; | Ljava/io/PrintStream; | out |
//Direct-mapped "method_id_item". struct DexMethodId{ u2 classIdx; 类的类型, DexTypeId中的索引下标 u2 protoIdx; 声明类型, DexProtoId中的索引下标 u4 nameIdx; 方法名, DexStringId中的索引下标 };
index | classId | protoIdx | nameIdx | classIdx_string | protoIdx_string | nameIdx_string |
0 | 0x00 | 0x03 | 0x01 | LHello; | void() | <init> |
1 | 0x00 | 0x04 | 0x0D | LHello; | void(Ljava/lang/String;) | MyPrint |
2 | 0x00 | 0x05 | 0x13 | LHello; | void([Ljava/lang/String;) | main |
3 | 0x0x1 | 0x00 | 0x16 | Ljava/io/PrintStream; | Ljava/io/PrintStream; (Ljava/lang/String;, [Ljava/lang/Object;) |
printf |
4 | 0x01 | 0x04 | 0x17 | Ljava/io/PrintStream; | void(Ljava/lang/String;) | println |
5 | 0x02 | 0x03 | 0x01 | Ljava/lang/Object; | void() | <init> |
6 | 0x04 | 0x03 | 0x04 | Ljava/lang/StringBuilder; | void() | <init> |
7 | 0x04 | 0x02 | 0x12 | Ljava/lang/StringBuilder; | Ljava/lang/StringBuilder; (Ljava/lang/String;) |
append |
8 | 0x04 | 0x01 | 0x18 | Ljava/lang/StringBuilder; | Ljava/lang/String;() | toString |
//Direct-mapped "map_list". struct DexMapList { u4 size; //DexMapItem的个数 DexMapItem list[1]; //变长数组 };
struct DexMapItem { u2 type; //kDexType开头的类型 u2 unused; //未使用, 用于字节对齐 u4 size; //指定类型的个数 u4 offset; //指定类型数据的文件偏移 };
/* map item type codes */ enum { kDexTypeHeaderItem = 0x0000, kDexTypeStringIdItem = 0x0001, kDexTypeTypeIdItem = 0x0002, kDexTypeProtoIdItem = 0x0003, kDexTypeFieldIdItem = 0x0004, kDexTypeMethodIdItem = 0x0005, kDexTypeClassDefItem = 0x0006, kDexTypeMapList = 0x1000, kDexTypeTypeList = 0x1001, kDexTypeAnnotationSetRefList = 0x1002, kDexTypeAnnotationSetItem = 0x1003, kDexTypeClassDataItem = 0x2000, kDexTypeCodeItem = 0x2001, kDexTypeStringDataItem = 0x2002, kDexTypeDebugInfoItem = 0x2003, kDexTypeAnnotationItem = 0x2004, kDexTypeEncodedArrayItem = 0x2005, kDexTypeAnnotationsDirectoryItem = 0x2006, };
index | type | unused | size | offset | type_string |
0 | 0x00 | 0x00 | 0x01 | 0x00 | kDexTypeHeaderItem |
1 | 0x01 | 0x00 | 0x19 | 0x70 | kDexTypeStringIdItem |
2 | 0x02 | 0x00 | 0x09 | 0xD4 | kDexTypeTypeIdItem |
3 | 0x03 | 0x00 | 0x06 | 0xF8 | kDexTypeProtoIdItem |
4 | 0x04 | 0x00 | 0x01 | 0x140 | kDexTypeFieldIdItem |
5 | 0x05 | 0x00 | 0x09 | 0x148 | kDexTypeMethodIdItem |
6 | 0x06 | 0x00 | 0x01 | 0x190 | kDexTypeClassDefItem |
7 | 0x2001 | 0x00 | 0x03 | 0x1B0 | kDexTypeCodeItem |
8 | 0x1001 | 0x00 | 0x03 | 0x23C | kDexTypeTypeList |
9 | 0x2002 | 0x00 | 0x19 | 0x252 | kDexTypeStringDataItem |
10 | 0x2003 | 0x00 | 0x03 | 0x372 | kDexTypeDebugInfoItem |
11 | 0x2000 | 0x00 | 0x01 | 0x388 | kDexTypeClassDataItem |
12 | 0x1000 | 0x00 | 0x01 | 0x39C | kDexTypeMapList |
由于第三块数据区的内容比较多, 所以将Dex文件格式分为(一)(二)两个部分, 在第(二)部分中将对数据区进行详细的解析
------------------2-----------------------------
一段Dalvik汇编代码由一系列Dalvik指令组成,指令语法由指令的位描述与指令格式 标识来决定。位描述约定如下:
每16位的字采用空格分隔开来
每个字母表示四位,每个字母按顺序从高字节开始,排列到低字节。每四位之间可 能使用竖线“丨”来表示不同的内容
顺序采用A〜Z的单个大写字母作为一个4位的操作码,op表示一个8位的操作码
¢来表示这字段所有位为0值
以指令格式“A|G|op BBBB F|E|D|C”为例:
指令中间有两个空格,每个分开的部分大小为16位,所以这条指令由三个16位的字组 成。第一个16位是“A|G|op”,高8位由A与G组成,低字节由操作码op组成。第二个16 位由BBBB组成,它表示一个16位的偏移值。第三个16位分别由F、E、D、C共四个4 字节组成,在这里它们表示寄存器参数
单独使用位标识还无法确定一条指令,必须通过指令格式标识来指定指令的格式编码。 它的约定如下:
指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母
第一个数字是表示指令有多少个16位的字组成
第二个数字是表示指令最多使用寄存器的个数。特殊标记“r”标识使用一定范围内的寄存器
第三个字母为类型码,表示指令用到的额外数据的类型
例子:22x
第一个数字2表示指令有两个16位字组成,第二个数字2表示指令使用到2个寄存器, 第三个字母x表示没有使用到额外的数据
Dalvik指令对语法约定
每条指令从操作码开始,后面紧跟参数,参数个数不定,每个参数之间采用逗号分开
每条指令的参数从指令第一部分开始,op位于低8位,高8位可以是一个8位的 参数,也可以是两个4位的参数,还可以为空,如果指令超过16位,则后面部分 依次作为参数
如果参数采用“vX”的方式表示,表明它是一个寄存器,如v0、v1等。这里采用v而不用r是为了避免与基于该虚拟机架构本身的寄存器命名产生冲突,如ARM 架构寄存器命名采用I开头
如果参数采用“#+X”的方式表示,表明它是一个常量数字
如果参数采用“+X”的方式表示,表明它是一个相对指令的地址偏移
如果参数采用“[email protected]”的方式表示,表明它是一个常量池索引值。其中kind表 示常量池类型,它可以是“string”(字符串常量池索引)、“type”(类型常量池索引)、“field”(字段常量池索引)或者“meth”(方法常量池索引)
例子:op vAA, [email protected]
指令用到了 1个寄存器参数vAA,并且还附加了一个字符串常量池索引[email protected]
Android 源码 Dalvik/docs 目录下提供了一份文档 insmction-formats.html,里面详细列举了Dalvik指令的所有格式
在Android Dex文件格式(二)中有详细的指令格式解析, 通过解析Hello.dex的指令格式还原出smali代码
------------------------------------3 ------------------------------------
//Direct-mapped "class_def_item". struct DexClassDef { u4 classIdx; //类的类型, DexTypeId中的索引下标 u4 accessFlags; //访问标志 u4 superclassIdx; //父类类型, DexTypeId中的索引下标 u4 interfacesOff; //接口偏移, 指向DexTypeList的结构 u4 sourceFileIdx; //源文件名, DexStringId中的索引下标 u4 annotationsOff; //注解偏移, 指向DexAnnotationsDirectoryItem的结构 u4 classDataOff; //类数据偏移, 指向DexClassData的结构 u4 staticValuesOff; //类静态数据偏移, 指向DexEncodedArray的结构 }; struct DexClassData { DexClassDataHeader header; //指定字段与方法的个数 DexField* staticFields; //静态字段 DexField* instanceFields; //实例字段 DexMethod* directMethods; //直接方法 DexMethod* virtualMethods; //虚方法 }; struct DexClassDataHeader { uleb128 staticFieldsSize; //静态字段个数 uleb128 instanceFieldsSize; //实例字段个数 uleb128 directMethodsSize; //直接方法个数 uleb128 virtualMethodsSize; //虚方法个数 }; struct DexMethod { uleb128 methodIdx; //指向DexMethodId的索引 uleb128 accessFlags; //访问标志 uleb128 codeOff; //指向DexCode结构的偏移 }; struct DexCode { u2 registersSize; 使用的寄存器个数 u2 insSize; 参数个数 u2 outsSize; 调用其他方法时使用的寄存器个数 u2 triesSize; Try/Catch个数 u4 debugInfoOff; 指向调试信息的偏移 u4 insnsSize; 指令集个数, 以2字节为单位 u2 insns[1]; 指令集 //followed by optional u2 padding //followed by try_item[triesSize] //followed by uleb128 handlersSize //followed by catch_handler_item[handlersSize] };
index | classIdx | accessFlags | superclassIdx | interfacesOff | sourceFileIdx | annotationsOff | classDataOff | staticValuesOff |
0 | 0x00 | 0x01 | 0x02 | 0x00 | 0x03 | 0x00 | 0x388 | 0x00 |
0_string | LHello; | public | Ljava/lang/Object; | no interfaces | Hello.java | no annotations |
在程序中,一般使用32位比特位来表示一个整型的数值。不过,一般能够使用到的整数值都不会太大,使用32比特位来表示就有点太浪费了。
对于普通计算机来说,这没什么问题,毕竟存储空间那么大。但是,对于移动设备来说,存储空间和内存空间都非常宝贵,不能浪费,能省就省。
DEX_INLINE int readUnsignedLeb128(const u1** pStream) { const u1* ptr = *pStream; int result = *(ptr++); if (result > 0x7f) { int cur = *(ptr++); result = (result & 0x7f) | ((cur & 0x7f) << 7); if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 14; if (cur > 0x7f) { cur = *(ptr++); result |= (cur & 0x7f) << 21; if (cur > 0x7f) { cur = *(ptr++); result |= cur << 28; } } } } *pStream = ptr; return result; }
index | staticFieldsSize | instanceFieldsSize | directMethodsSize | virtualMethodsSize |
0 | 0 | 0 | 3 | 0 |
index | methodIdx | accessFlags | codeOff |
0 | 0x0 | 0x10001 | 0x1B0 |
0_string | void LHello;-><init>() | public|constructor | |
1 | 0x01 | 0x09 | 0x1C8 |
1_string | void LHello;->MyPrint(Ljava/lang/String;) | public|static | |
2 | 0x01(看010Editor解析此处也是0x01) | 0x09 | 0x210 |
2_string | 但是字符串描述也是下表为0x02的信息 | public|static |
DexMethod_index | registersSize | insSize | outsSize | triesSize | debugInfoOff | insnsSize | insns |
0 | 0x01 | 0x01 | 0x01 | 0x00 | 0x372 | 0x04 | 0x70, 0x10, 0x05, 0x00, 0x00, 0x00, 0x0E, 0x00 |
1 | 0x04 | 0x01 | 0x03 | 0x00 | 0x377 | 0x1C | 0x62, 0x00, 0x00, 0x00, 0x22, 0x01, 0x04, 0x00, 0x70, 0x10, 0x06, 0x00, 0x01, 0x00, 0x6E, 0x20, 0x07, 0x00, 0x31, 0x00, 0x0C, 0x01, 0x1A, 0x02, 0x00, 0x00, 0x6E, 0x20, 0x07, 0x00, 0x21, 0x00, 0x0C, 0x01, 0x6E, 0x10, 0x08, 0x00, 0x01, 0x00, 0x0C, 0x01, 0x12, 0x02, 0x23, 0x22, 0x07, 0x00, 0x6E, 0x30, 0x03, 0x00, 0x10, 0x02, 0x0E, 0x00 |
2 | 0x03 | 0x01 | 0x02 | 0x00 | 0x380 | 0x0D | 0x1A, 0x00, 0x14, 0x00, 0x71, 0x10, 0x01, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x1A, 0x01, 0x02, 0x00, 0x6E, 0x20, 0x04, 0x00, 0x10, 0x00, 0x0E, 0x00 |
单独使用位标识还无法确定一条指令,必须通过指令格式标识来指定指令的格式编码。 它的约定如下:
指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母
第一个数字是表示指令有多少个16位的字组成
第二个数字是表示指令最多使用寄存器的个数。特殊标记“r”标识使用一定范围内的寄存器
第三个字母为类型码,表示指令用到的额外数据的类型
解析完dex之后我们有很多事都可以做了
1、我们可以检测一个apk中是否包含了指定系统的api(当然这些api没有被混淆),同样也可以检测这个apk是否包含了广告,以前我们可以通过解析AndroidManifest.xml文件中的service,activity,receiver,meta等信息来判断,因为现在的广告sdk都需要添加这些东西,如果我们可以解析dex的话,那么我们可以得到他的所有字符串内容,就是string_ids池,这样就可以判断调用了哪些api。那么就可以判断这个apk的一些行为了,当然这里还有一个问题,假如dex加密了我们就蛋疼了。好吧,那就牵涉出第二件事了。
2、我们在之前说过如何对apk进行加固,其实就是加密apk/dex文件内容,那么这时候我们必须要了解dex的文件结构信息,因为我们先加密dex,然后在动态加载dex进行解密即可
3、我们可以更好的逆向工作,其实说到这里,我们看看apktool源码也知道,他内部的反编译原理就是这些,只是他会将指令翻译成smail代码,这个网上是有相对应的jar包api的,所以我们知道了dex的数据结构,那么原理肯定就知道了,同样还有一个dex2jar工具原理也是类似的
4
、 .....等等等等
Android Dex文件格式学习笔记pdf版下载:
链接: http://pan.baidu.com/s/1eR745Rs
密码: qqqk