dex_dexdump - CSDN
精华内容
参与话题
  • dex文件解析(第三篇)

    千次阅读 2018-01-02 11:41:03
    一张图搞懂dex 大图这里 图1 dex 当然也可以通过下面的图12 DexFile的文件格式,了解更清楚。 DEX文件详解 什么是dex文件?如何生成一个dex文件dex文件的作用dex文件格式详解 什么是dex文件? dex文件是...

    一张图搞懂dex

    大图这里

    图1 dex

    当然也可以通过下面的图12 DexFile的文件格式,了解更清楚。

    DEX文件详解

    • 什么是dex文件?
    • 如何生成一个dex文件
    • dex文件的作用
    • dex文件格式详解

    什么是dex文件?

    dex文件是Android系统中的一种文件,是一种特殊的数据格式,和APK、jar 等格式文件类似。
    能够被DVM识别,加载并执行的文件格式。
    简单说就是优化后的android版.exe。每个apk安装包里都有。包含应用程序的全部操作指令以及运行时数据。
    相对于PC上的java虚拟机能运行.class;android上的Davlik虚拟机能运行.dex。

    当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右

    图2 apk中的dex文件

    为何要研究dex格式?因为dex里面包含了所有app代码,利用反编译工具可以获取java源码。理解并修改dex文件,就能更好的apk破解和防破解。

    使用dex文件的最大目的是实现安全管理,但在追求安全的前提下,一定要注意对dex文件实现优化处理。

    注意:并不是只有Java才可以生成dex文件,C和C++也可以生成dex文件

    如何生成一个dex文件?

    1. 通过IDE自动帮我们build生成
    2. 手动通过dx命令去生成dex文件
      在待测试的class文件目录下(我将TestMain.class放到了F盘根目录下),执行命令dx --dex --output TestMain.dex TestMain.class,就会生成TestMain.dex文件。
    3. 手动运行dex文件在手机
      在待测试的dex文件目录下(我将TestMain.class放到了F盘根目录下),通过adb push TestMain.dex /storage/emulated/0命令,然后通过adb shell命令进入手机,后执行dalvikvm -cp /sdcard/TestMain.dex TestMain,就会打印出
    Hello World!
    

    如下图3所示(使用AS的终端,没有用Windows的cmd命令)

    图3 手动运行dex文件

    注意

    • 环境变量的配置,dex在SDK目录下的build-tools目录下有很多版本,这里可以选择最新版本目录下dx.bat配置到环境变量path路径下;同样,adb命令也同样配置。
    • 运行完dex文件,可以通过exit退出手机,电脑本地盘符

    查看dex文件

    大图这里

    图4 dex文件概貌

    通过010Editor工具(图片来自网络)
    大图这里

    图5 注:图片来自网络

    下图6是TestMain.dex通过010Editor工具得到的
    大图这里

    图6 010Editor 检测TestMain.dex结果

    图7是通过010Editor工具检测TestMain.dex得到的 Template Result结果
    大图这里

    图7 010Editor检测TemplateResult结果

    通过dexdump 命令查看(注意)
    利用build-tools 下的dexdump 命令查看,dexdump -d -l plain TestMain.dex,得到下面的结果

    F:\>dexdump -d -l plain TestMain.dex
    Processing 'TestMain.dex'...
    Opened 'TestMain.dex', DEX version '035'
    Class #0            -
      Class descriptor  : 'LTestMain;'
      Access flags      : 0x0001 (PUBLIC)
      Superclass        : 'Ljava/lang/Object;'
      Interfaces        -
      Static fields     -
      Instance fields   -
        #0              : (in LTestMain;)
          name          : 'mX'
          type          : 'I'
          access        : 0x0001 (PUBLIC)
      Direct methods    -
        #0              : (in LTestMain;)
          name          : '<init>'
          type          : '()V'
          access        : 0x10001 (PUBLIC CONSTRUCTOR)
          code          -
          registers     : 2
          ins           : 1
          outs          : 1
          insns size    : 7 16-bit code units
    00015c:                                        |[00015c] TestMain.<init>:()V
    00016c: 7010 0400 0100                         |0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0004
    000172: 1200                                   |0003: const/4 v0, #int 0 // #0
    000174: 5910 0000                              |0004: iput v0, v1, LTestMain;.mX:I // field@0000
    000178: 0e00                                   |0006: return-void
          catches       : (none)
          positions     :
            0x0000 line=11
            0x0003 line=3
            0x0006 line=12
          locals        :
            0x0000 - 0x0007 reg=1 this LTestMain;
    
        #1              : (in LTestMain;)
          name          : 'main'
          type          : '([Ljava/lang/String;)V'
          access        : 0x0009 (PUBLIC STATIC)
          code          -
          registers     : 4
          ins           : 1
          outs          : 2
          insns size    : 16 16-bit code units
    00017c:                                        |[00017c] TestMain.main:([Ljava/lang/String;)V
    00018c: 2200 0100                              |0000: new-instance v0, LTestMain; // type@0001
    000190: 7010 0000 0000                         |0002: invoke-direct {v0}, LTestMain;.<init>:()V // method@0000
    000196: 6e10 0200 0000                         |0005: invoke-virtual {v0}, LTestMain;.test:()V // method@0002
    00019c: 6201 0100                              |0008: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001
    0001a0: 1a02 0100                              |000a: const-string v2, "Hello World!" // string@0001
    0001a4: 6e20 0300 2100                         |000c: invoke-virtual {v1, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0003
    0001aa: 0e00                                   |000f: return-void
          catches       : (none)
          positions     :
            0x0000 line=6
            0x0005 line=7
            0x0008 line=8
            0x000f line=9
          locals        :
            0x0005 - 0x0010 reg=0 testMainObject LTestMain;
            0x0000 - 0x0010 reg=3 args [Ljava/lang/String;
    
      Virtual methods   -
        #0              : (in LTestMain;)
          name          : 'test'
          type          : '()V'
          access        : 0x0001 (PUBLIC)
          code          -
          registers     : 1
          ins           : 1
          outs          : 0
          insns size    : 1 16-bit code units
    0001ac:                                        |[0001ac] TestMain.test:()V
    0001bc: 0e00                                   |0000: return-void
          catches       : (none)
          positions     :
            0x0000 line=15
          locals        :
            0x0000 - 0x0001 reg=0 this LTestMain;
    
      source_file_idx   : 8 (TestMain.java)
    
    • registers:Dalvik 最初目标是运行在以ARM 做CPU 的机器上的,ARM 芯片的一个主要特点是寄存器多寄存器多的话有好处,就是可以把操作数放在寄存器里,而不是像传统VM 一样放在栈中。自然,操作寄存器是比操作内存(栈嘛,其实就是一块内存区域)快registers 变量表示该方法运行过程中会使用多少个寄存器
    • ins:输入参数对应的个数
    • outs:此函数内部调用其他函数,需要的参数个数。
    • insns size:以4 字节为单位,代表该函数字节码的长度(类似Class 文件的code[]数组)

    更多内容参考:官网介绍----->Dalvik 可执行文件格式

    dex文件的作用

    记录整个工程中所有类的信息,记住的整个工程所有类的信息

    dex文件格式详解

    • 一种8位字节的二进制流文件
    • 各个数据按顺序紧密的排列,无间隙
    • 整个应用中所有的Java源文件都放在一个dex中
      大图这里

    图8 dex文件结构

    上图中的文件头部分,记录了dex文件的信息,所有字段大致的一个分部;索引区部分,主要包含字符串、类型、方法原型、域、方法的索引;索引区最终又被存储在数据区,其中链接数据区,主要存储动态链接库,so库的信息。

    源码:/dalvik/libdex/DexFile.h:DexFile

    struct DexFile {
        /* directly-mapped "opt" header */
        const DexOptHeader* pOptHeader;
        /* pointers to directly-mapped structs and arrays in base DEX */
        const DexHeader*    pHeader;
        const DexStringId*  pStringIds;
        const DexTypeId*    pTypeIds;
        const DexFieldId*   pFieldIds;
        const DexMethodId*  pMethodIds;
        const DexProtoId*   pProtoIds;
        const DexClassDef*  pClassDefs;
        const DexLink*      pLinkData;
    };
    

    具体可查看Android源码官网的关于dex文件结构的详解,如下图9(大图这里

    图9 dex文件结构详解

    总结

    数据名称 解释
    header dex文件头部,记录整个dex文件的相关属性
    string_ids 字符串数据索引,记录了每个字符串在数据区的偏移量
    type_ids 类似数据索引,记录了每个类型的字符串索引
    proto_ids 原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表
    field_ids 字段数据索引,记录了所属类,类型以及方法名
    method_ids 类方法索引,记录方法所属类名,方法声明以及方法名等信息
    class_defs 类定义数据索引,记录指定类各类信息,包括接口,超类,类数据偏移量
    data 数据区,保存了各个类的真是数据
    link_data 连接数据区

    DEX 文件中会出现的数据类型

    类型 含义
    u1 等同于uint8_t,表示 1 字节的无符号 数
    u2 等同于 uint16_t,表示 2 字节的无符号数
    u4 等同于 uint32_t,表示 4 字节的无符号数
    u8 等同于 uint64_t,表示 8 字节的无符号数
    sleb128 有符号 LEB128,可变长度 1~5 字节
    uleb128 无符号 LEB128,可变长度 1~5 字节
    uleb128p1 无符号 LEB128 值加1,可变长 1~5 字节

    /dalvik/libdex/DexFile.h中定义如下

    typedef uint8_t             u1;
    typedef uint16_t            u2;
    typedef uint32_t            u4;
    typedef uint64_t            u8;
    typedef int8_t              s1;
    typedef int16_t             s2;
    typedef int32_t             s4;
    typedef int64_t             s8;
    

    LEB128
    LEB128(“Little-Endian Base 128”)表示任意有符号或无符号整数的可变长度编码。该格式借鉴了 DWARF3 规范。在 .dex 文件中,LEB128 仅用于对 32 位数字进行编码。

    每个 LEB128 编码值均由 1-5 个字节组成,共同表示一个 32 位的值。每个字节均已设置其最高有效位(序列中的最后一个字节除外,其最高有效位已清除)。每个字节的剩余 7 位均为有效负荷,即第一个字节中有 7 个最低有效位,第二个字节中也是 7 个,依此类推。对于有符号 LEB128 (sleb128),序列中最后一个字节的最高有效负荷位会进行符号扩展,以生成最终值。在无符号情况 (uleb128) 下,任何未明确表示的位都会被解译为 0。
    大图这里

    图10 双字节 LEB128 值的按位图

    变量 uleb128p1 用于表示一个有符号值,其表示法是编码为 uleb128 的值加 1。这使得编码 -1(或被视为无符号值 0xffffffff)成为一个单字节(但没有任何其他负数),并且该编码在下面这些明确说明的情况下非常实用:所表示的数值必须为非负数或 -1(或 0xffffffff);不允许任何其他负值(或不太可能需要使用较大的无符号值)。
    以下是这类格式的一些示例:

    编码序列 As sleb128 As uleb128 As uleb128p1
    00 0 0 -1
    01 1 1 0
    7f -1 127 126
    80 7f -128 16256

    dex文件头
    Dex文件头主要包括校验和以及其他结构的偏移地址和长度信息。
    源码位于 /dalvik/libdex/DexFile.h:DexHeader

    struct DexHeader {
        u1  magic[8];           /* includes version number */
        u4  checksum;           /* adler32 checksum */
        u1  signature[kSHA1DigestLen]; /* SHA-1 hash */
        u4  fileSize;           /* length of entire file */
        u4  headerSize;         /* offset to start of next section */
        u4  endianTag;
        u4  linkSize;
        u4  linkOff;
        u4  mapOff;
        u4  stringIdsSize;
        u4  stringIdsOff;
        u4  typeIdsSize;
        u4  typeIdsOff;
        u4  protoIdsSize;
        u4  protoIdsOff;
        u4  fieldIdsSize;
        u4  fieldIdsOff;
        u4  methodIdsSize;
        u4  methodIdsOff;
        u4  classDefsSize;
        u4  classDefsOff;
        u4  dataSize;
        u4  dataOff;
    };
    

    具体详解如下图5所示
    大图这里

    图11 dex文件头信息

    各个字段详解摘要
    mapOff 字段
    指定 DexMapList 结构距离 Dex 头的偏移
    DexMapList 结构体:

    struct     DexMapList  
    {  
        u4     size;                                   // DexMapItem 的个数  
        DexMapItem     list[1];               // DexMapItem 结构  
    };  
    
    • size:表示接下来有多少个 DexMapItem
    • list:是一个 DexMapItem 结构体数组
      DexMapItem 结构体:
    struct     DexMapItem  
    {  
        u2     type;                    // kDexType 开头的类型  
        u2     unused;               // 未使用,用于对齐  
        u4     size;                     // 指定类型的个数  
        u4     offset;                 // 指定类型数据的文件偏移  
    };  
    

    type:一个枚举常量

    enum  
    {  
        kDexTypeHeaderItem = 0x0000,    // 对应 DexHeader       
        kDexTypeStringIdItem = 0x0001,  // 对应 stringIdsSize 与 stringIdsOff 字段       
        kDexTypeTypeIdItem = 0x0002,    // 对应 typeIdsSize 与 typeIdsOff 字段       
        kDexTypeProtoIdItem = 0x0003,   // 对应 protoIdsSize 与 protoIdsOff 字段       
        kDexTypeFieldIdItem = 0x0004,   // 对应 fieldIdsSize 与 fieldIdsOff 字段       
        kDexTypeMethodIdItem = 0x0005,  // 对应 methodIdsSize 与 methodIdsOff 字段       
        kDexTypeClassDefItem = 0x0006,  // 对应 classDefsSize 与 classDefsOff 字段       
        kDexTypeMapList = 0x1000,       
        kDexTypeTypeList = 0x1001,       
        kDexTypeAnnotationSetRefList = 0x1002,       
        kDexTypeAnnotationSetItem = 0x1003,       
        kDexTypeClassDataItem = 0x2000,       
        kDexTypeCodeItem = 0x2001,       
        kDexTypeStringDataItem = 0x2002,       
        kDexTypeDebugInfoItem = 0x2003,       
        kDexTypeAnnotationItem = 0x2004,      
        kDexTypeEncodeArrayItem = 0x2005,      
        kDexTypeAnnotationsDirectoryItem = 0x2006  
    };  
    
    • size:指定类型的个数
    • offset:指定类型数据的偏移

    DexStringId 结构体(stringIdsSize 与 stringIdsOff 字段)

    typedef struct _DexStringId  
    {  
        u4  stringDataOff;                  // 指向 MUTF-8 字符串的偏移  
    }DexStringId, *PDexStringId;  
    

    MUTF-8 编码:

    1. 使用 1~3 字节编码长度
    2. 大于 16 位的 Unicode 编码 U+10000~U+10FFFF 使用 3 字节来编码
    3. U+0000 采用 2 字节编码
    4. 采用空字符 null 作为结尾
    5. 第一个字节存放字节个数(不包含自已)

    DexTypeId 结构体(typeIdsSize 与 typeIdsOff 字段)
    是一个类型结构体

    typedef struct _DexTypeId  
    {  
        u4     descriptorIdx;   // 指向 DexStringId 列表的索引  
    }DexTypeId, *PDexTypeId;  
    
    • descriptorIdx:指向 DexStringId 列表的索引,它对应的字符串代表了具体类的类型

    DexProtoId 结构体(protoIdsSize 与 protoIdsOff 字段)
    是一个方法声明结构体,方法声明 = 返回类型 + 参数列表

    typedef struct _DexProtoId  
    {  
        u4  shortyIdx;          // 方法声明字符串,指向 DexStringId 列表的索引  
        u4  returnTypeIdx;      // 方法返回类型字符串,指向 DexStringId 列表的索引  
        u4  parametersOff;      // 方法的参数列表,指向 DexTypeList 结构体的偏移  
    }DexProtoId, *PDexProtoId; 
    
    • shortyIdx:方法声明字符串,方法声明 = 返回类型 + 参数列表
    • returnTypeIdx:方法返回类型字符串
    • parametersOff:指向一个 DexTypeList 结构体,存放了方法的参数列表

    DexTypeList 结构体:

    typedef struct _DexTypeList  
    {  
        u4  size;               // 接下来 DexTypeItem 的个数  
        DexTypeItem* list;      // DexTypeItem 结构  
    }DexTypeList, *PDexTypeList;  
    
    • size:接下来 DexTypeItem 的个数
    • list:是一个 DexTypeItem 结构体数组

    DexTypeItem 结构体:

    typedef struct _DexTypeItem  
    {  
        u2  typeIdx;            // 指向 DexTypeId 列表的索引  
    }DexTypeItem, *PDexTypeItem;
    

    typeIdx:DexTypeId 列表的索引

    DexFieldId 结构体(fieldIdsSize 与 fieldIdsOff 字段)
    指明了字段所有的类、字段的类型以及字段名

    typedef struct _DexFieldId  
    {  
        u2  classIdx;           // 类的类型,指向 DexTypeId 列表的索引  
        u2  typeIdx;            // 字段的类型,指向 DexTypeId 列表的索引  
        u4  nameIdx;            // 字段名,指向 DexStringId 列表的索引  
    }DexFieldId, *PDexFieldId;  
    
    • classIdx:类的类型
    • typeIdx:字段的类型
    • nameIdx:字段名

    DexMethodId 结构体(methodIdsSize 与 methodIdsOff 字段)
    方法结构体

    typedef struct _DexMethodId  
    {  
        u2  classIdx;           // 类的类型,指向 DexTypeId 列表的索引  
        u2  protoIdx;           // 声明的类型,指向 DexProtoId 列表的索引  
        u4  nameIdx;            // 方法名,指向 DexStringId 列表的索引  
    }DexMethodId, *PDexMethodId;  
    
    • classIdx:类的类型
    • protoIdx:声明的类型
    • nameIdx:方法名

    DexClassDef 结构体(classDefsSize 和 classDefsOff 字段)
    类结构体

    typedef struct _DexClassDef  
    {  
        u4     classIdx;                 // 类的类型,指向 DexTypeId 列表的索引  
        u4     accessFlags;              // 访问标志  
        u4     superclassIdx;            // 父类类型,指向 DexTypeId 列表的索引  
        u4     interfacesOff;            // 接口,指向 DexTypeList 的偏移,否则为0  
        u4     sourceFileIdx;            // 源文件名,指向 DexStringId 列表的索引  
        u4     annotationsOff;           // 注解,指向 DexAnnotationsDirectoryItem 结构,或者为 0  
        u4     classDataOff;             // 指向 DexClassData 结构的偏移,类的数据部分  
        u4     staticValuesOff;          // 指向 DexEncodedArray 结构的偏移,记录了类中的静态数据,主要是静态方法  
    }DexClassDef, *PDexClassDef;  
    
    • classIdx:类的类型,指向 DexTypeId 列表的索引
    • accessFlags:访问标志,它是以ACC_开头的枚举值
    • superclassIdx:父类类型,指向 DexTypeId 列表的索引
    • interfacesOff:接口,指向 DexTypeList 的偏移,如果没有,则为 0
    • sourceFileIdx:源文件名,指向 DexStringId 列表的索引
    • annotationsOff:注解,指向 DexAnnotationsDirectoryItem 结构,或者为 0
    • classDataOff:指向 DexClassData 结构的偏移,类的数据部分
    • staticValuesOff:指向 DexEncodeArray 结构的偏移,记录了类中的静态数据,没有则为 0

    DexClassData 结构体:

    typedef struct _DexClassData  
    {  
        DexClassDataHeader      header;             // 指定字段与方法的个数  
        DexField*               staticFields;       // 静态字段,DexField 结构  
        DexField*               instanceFields;     // 实例字段,DexField 结构  
        DexMethod*              directMethods;      // 直接方法,DexMethod 结构  
        DexMethod*              virtualMethods;     // 虚方法,DexMethod 结构  
    }DexClassData, *PDexClassData;  
    
    • header:DexClassDataHeader 结构体,指定字段与方法的个数
    • staticFields:静态字段,DexField 结构体数组
    • instanceFields:实例字段,DexField 结构体数组
    • directMethods:直接方法,DexMthod 结构体数组
    • virtualMethods:虚方法,DexMethod 结构体数组

    DexClassDataHeader 结构体:

    typedef struct _DexClassDataHeader  
    {  
        uleb128 staticFieldsSize;                   // 静态字段个数  
        uleb128 instanceFieldsSize;                 // 实例字段个数  
        uleb128 directMethodsSize;                  // 直接方法个数  
        uleb128 virtualMethodsSize;                 // 虚方法个数  
    }DexClassDataHeader, *PDexClassDataHeader;  
    
    • staticFieldsSize:静态字段个数
    • instanceFieldsSize:实例字段个数
    • directMethodsSize:直接方法个数
    • virtualMethodsSize:虚方法个数

    DexField 结构体:

    typedef struct _DexField  
    {  
        uleb128 fieldIdx;           // 指向 DexFieldId 的索引  
        uleb128 accessFlags;        // 访问标志  
    }DexField, *PDexField;  
    
    • fieldIdx:字段描述,指向 DexFieldId 的索引
    • accessFlags:访问标志

    DexMethod 结构体:

    typedef struct _DexMethod  
    {  
        uleb128 methodIdx;          // 指向 DexMethodId 的索引  
        uleb128 accessFlags;        // 访问标志  
        uleb128 codeOff;            // 指向 DexCode 结构的偏移  
    }DexMethod, *PDexMethod;  
    
    • methodIdx:方法描述,指向 DexMethodId 的索引
    • accessFlags:访问标志
    • codeOff:指向 DexCode 结构的偏移

    DexCode 结构体:

    typedef struct _DexCode  
    {  
        u2  registersSize;          // 使用的寄存器个数  
        u2  insSize;                // 参数个数  
        u2  outsSize;               // 调用其他方法时使用的寄存器个数  
        u2  triesSize;              // Try/Catch 个数  
        u4  debbugInfoOff;          // 指向调试信息的偏移  
        u4  insnsSize;              // 指令集个数,以 2 字节为单位  
        u2* insns;                  // 指令集  
    }DexCode, *PDexCode;  
    

    还有一些不太常见的结构体,要用的时候再去看看就行了。Dex 文件的整体结构就这样,就是一个多层索引的结构。

    string_ids(字符串索引)
    这一区域存储的是Dex文件字符串资源的索引信息,该索引信息是目标字符串在Dex文件数据区所在的真实物理偏移量。

    源码位于 /dalvik/libdex/DexFile.h:DexStringId

    struct DexStringId {
        u4 stringDataOff;      /* file offset to string_data_item */
    };
    

    stringDataOff记录了目标字符串在Dex文件中的实际偏移量,虚拟机想读取该字符串时,只需将Dex文件在内存中的起始地址加上stringDataOff所指的偏移量,就是该字符串在内存中的实际物理地址。
    在Dex文件中,每个每个字符串对应一个DexStringId,大小4B。另外虚拟机通过DexHeader中的String_ids_size获得当前Dex文件中的字符串的总数,通过乘法就可对该索引资源进行访问。

    DexLink

    struct DexLink {
        u1  bleargh;
    };
    

    DexFile 在内存中的映射

    在Android系统中, java 源文件会被编译为“ .jar ” 格式的dex类型文件, 在代码中称为dexfile 。在加载Class 之前, 必先读取相应的jar文件。通常我们使用read()函数来读取文件中的内容。但在Dalvik中使用mmap() 函数。和read()不同, mmap()函数会将dex文件映射到内存中,这样通过普通的内存读取操作即可访问dexfile中的内容。

    Dexfile的文件格式如图12 所示, 主要有三部分组成:头部,索引,数据。通过头部可知索引的位置和数同,可知数据区的起始位置。其中classDefsOff 指定了ClassDef 在文件的起始位置, dataOff 指定了数据在文件的起始位置, ClassDef 即可理解为Class 的索引。通过读取ClassDef 可获知Class 的基本信息,其中classDataOff 指定了Class 数据在数据区的位置。
    大图这里

    图12 DexFile的文件格式

    在将dexfile文件映射到内存后,会调用dexFileParse()函数对其分析,分析的结果存放于名为DexFile的数据结构中。DexFile 中的baseAddr指向映射区的起始位置, pClassDefs 指向ClassDefs(即class索引)的起始位置。由于在查找class 时,都是使用class的名字进行查找的,所以为了加快查找速度, 创建了一个hash表。在hash表中对class 名字进行hash,并生成index。这些操作都是在对文件解析时所完成的,这样虽然在加载过程中比较耗时,但是在运行过程中可节省大量查找时间。

    解析完后, 接下来开始加载class文件。在此需要将加载类用ClassObject来保存,所以在此需要先分析和ClassObject 相关的几个数据结构。

    首先在文件Object.h 中可以看到如下对结构体Object 的定义。(android2.3.7源码)

    typedef struct Object {
        /* ptr to class object */
        ClassObject*    clazz;
    
        /*
         * A word containing either a "thin" lock or a "fat" monitor.  See
         * the comments in Sync.c for a description of its layout.
         */
        u4              lock;
    } Object;
    

    通过结构体Object定义了基本类的实现,这里有如下两个变量。

    • lock : 对应Obejct 对象中的锁实现,即notify wait 的处理。
    • clazz : 是结构体指针,姑且不看结构体内容,这里用了指针的定义。

    下面会有更多的结构体定义:

    struct DataObject {
        Object          obj;                /* MUST be first item */
        /* variable #of u4 slots; u8 uses 2 slots */
        u4              instanceData[1];
    };
    struct StringObject {
        Object          obj;                /* MUST be first item */
        /* variable #of u4 slots; u8 uses 2 slots */
        u4              instanceData[1];
    };
    

    我们看到最熟悉的一个词StringObject ,把这个结构体展开后是下面的样子。

    struct StringObject {
        /* ptr t o class object */
    	ClassObject*  clazz ;
        /* variable #of u4 slots; u8 uses 2 slots */
        u4   lock;
        u4              instanceData[1];
    };
    

    由此不难发现, 任何对象的内存结构体中第一行都是Object结构体,而这个结构体第一个总是一个ClassObejct,第二个总是lock 。按照C++中的技巧,这些结构体可以当成Object结构体使用,因此所有的类在内存中都具有“对象”的功能,即可以找到一个类(ClassObject),可以有一个锁(lock) 。

    StringObject是对String类进行管理的数据对象,ArrayObejct是数据相关的管理。

    ClassObject-Class 在加载后的表现形式

    在解析完文件后, 接下来需要加载Class 的具体内容。在Dalvik中, 由数据结构ClassObject负责存放加载的信息。如图13所示,加载过程会在内存中alloc几个区域,分别存放directMethods 、virtualMethods 、sfields 、ifields 。这些信息是从dex 文件的数据区中读取的,首先会读取Class 的详细信息,从中获得directMethod 、virtua!Method 、sfield 、ifield 等的信息,然后再读取。在此需要注意, 在C lassObj ect 结构中有个名为super 的成员,通过super成员可以指向它的超类。
    大图这里

    图13 加载过程

    Android dex 文件优化

    对Android dex 文件进行优化来说, 需要注意的一点是dex文件的结构是紧凑的,但是我们还是要想方设法地进行提高程序的运行速度,我们就仍然需要对dex文件进行进一步优化

    调整所有字段的字节序( LITTLE_ENDIAN),和对齐结构中的每一个域来验证dex文件中的所有类,并对一些特定的类进行优化或对方法里的操作码进行优化。优化后的文件大小会有所增加, 大约是原Android dex文件的1~4 倍。

    优化时机
    优化发生的时机有两个:

    • 对于预置应用来说,可以在系统编译后,生成优化文件,以ODEX 结尾。这样在发布时除APK文件(不包含dex)以外,还有一个相应的Android dex 文件。
    • 对于非预置应用, 包含在APK文件里的dex 文件会在运行时被优化,优化后的文件将被保存在缓存中。

    如下图14所示代码调用流程

    图14 代码调用流程

    每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。虚拟机的线程机制,内存分配和管理, Mutex等都是依赖底层操作系统而实现的。

    所有Android应用的线程都对应一个Linux线程(可参考----理解Android线程创建流程,虚拟机因而可以更多地依赖操作系统的线程调度和管理机制。不同的应用在不同的进程空间里运行,加之对不同来源的应用都使用不同的Linux用户来运行,可以最大限度地保护应用的安全和独立运行。

    Zygote是一个虚拟机进程,同时也是一个虚拟机实例的孵化器,每当系统要求执行一个Android应用程序,Zygote就会孵化出一个子进程来执行该应用程序。这样做的好处显而易见:Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的载,预置类库的加载和初始化等操作,而在系统需要一个新的虚拟机实例时,Zygote通过复制自身,最快速地提供一个虚拟机实例。另外,对于一些只读的系统库,所有虚拟机实例都和Zygote 共享一块内存区域,大大节省了内存开销

    Android 应用所使用的编程语言是Java语言,和Java SE 一样,编译时使用Oracle JDK 将Java源程序编程成标准的Java 字节码文件(. class 文件)。而后通过工具软件DX 把所有的字节码文件转成Android dex 文件(classes . dex) 。最后使用Android 打包工具(aapt)将dex 文件、资源文件以及AndroidManifest.xml 文件(二进制格式)组合成一个应用程序包(APK) 。应用程序包可以被发布到手机上运行。

    图15 Android应用编译及运行流程

    odex 介绍

    odex 是Optimized dex 的简写,也就是优化后的dex 文件。为什么要优化呢?主要还是为了提高Dalvik 虚拟机的运行速度。但是odex 不是简单的、通用的优化,而是在其优化过程中,依赖系统已经编译好的其他模块,简单点说:

    • 从Class 文件到dex 文件是针对Android 平台的一种优化,是一种通用的优化。优化过程中,唯一的输入是Class 文件。
    • odex 文件就是dex 文件具体在某个系统(不同手机,不同手机的OS,不同版本的OS 等)上的优化。odex 文件的优化依赖系统上的几个核心模块( 由BOOTCLASSPATH 环境变量给出, 一般是/system/framework/下的jar 包,尤其是core.jar)。odex 的优化就好像是把中那些本来需要在执行过程中做的类校验、调用其他类函数时的解析等工作给提前处理了。

    通过利用dexopt得到test.odex,接着利用dexdump得到其内容,最后可以利用Beyond Compare比较这两个文件的差异。
    如下图所示

    图16 test.dex 和test.odex 差异

    图16中,绿色框中是test.dex的内容,红色框中是test.odex的内容,这也是两个文件的差异内容:

    • test.dex中,TestMain类仅仅是PUBLIC的,但test.odex则增加了VERIFIED和OPTIMIZED两项。VERIFIED是表示该类被校验过了,至于校验什么东西,以后再说。
    • 然后就是一些方法的不同了。优化后的odex文件,一些字节码指令变成了xxx-quick。比如图中最后一句代码对于的字节码中,未优化前invoke-virtual指令表示从method table指定项(图中是0002)里找到目标函数,而优化后的odex使用了invoke-virtual-quick表示从vtable中找到目标函数(图中是000b)。

    vtable是虚表的意思,一般在OOP实现中用得很多。vtable一定比methodtable快么?那倒是有可能。我个人猜测:

    • method表应该是每个dex文件独有的,即它是基于dex文件的。
    • 根据odex文件的生成方法(后面会讲),我觉得vtable恐怕是把dex文件及依赖的类(比如Java基础类,如Object类等)放一起进行了处理,最终得到一张大的vtable。这个odex文件依赖的一些函数都放在vtable中。运行时直接调用指定位置的函数就好,不需要再解析了。以上仅是我的猜测。

    注意
    odex文件由dexopt生成,这个工具在SDK里没有,只能由源码生成。odex文件的生成有三种方式:

    • preopt:即OEM厂商(比如手机厂商),在制作镜像的时候,就把那些需要放到镜像文件里的jar包,APK等预先生成对应的odex文件,然后再把classes.dex文件从jar包和APK中去掉以节省文件体积。
    • installd:当一个apk安装的时候,PackageManagerService会调用installd的服务,将apk中的class.dex进行处理。当然,这种情况下,APK中的class.dex不会被剔除。
    • dalvik VM:preopt是厂商的行为,可做可不做。如果没有做的话,dalvik VM在加载一个dex文件的时候,会先生成odex。所以,dalvik VM实际上用得是odex文件。以后我们研究dalvik VM的时候会看到这部分内容。

    实际上dex转odex是利用了dalvik vm,里边也会运行dalvik vm的相关方法。

    总结

    1. 以标准角度来看,Class文件是由Java VM规范定义的,所以通用性更广。dex或者是odex只不过是规范在Android平台上的一种具体实现罢了,而且dex/odex在很多地方也需要遵守规范。因为dex文件的来源其实还是Class文件。
    2. 对于初学者而言,我建议了解Class文件的结构为主。另外,关于dex/odex的文件结构,除非有明确需求(比如要自己修改字节码等),否则以了解原理就可以。而且,将来我们看到dalvik vm的实际代码后,你会发现dex的文件内容还是会转换成代码里的那些你很熟悉的类型,数据结构。比如dex存储字符串是一种优化后的方法,但是到vm代码中,还不是只能用字符串来表示吗?
    3. 另外,你还会发现,Class、dex还是odex文件都存储了很多源码中的信息,比如类名、函数名、参数信息、成员变量信息等,而且直接用得是字符串。这和Native的二进制比起来,就容易看懂多了。

    参考链接

    深入理解Android之Java虚拟机Dalvik
    Androidsource之Dalvik 字节码
    Androidsource之Dalvik 可执行文件格式(dex文件)
    Android安全–Dex文件格式详解
    详细描述了dex/odex指令的格式----->Dalvik opcodes
    解释器中对 标号 的使用
    A deep dive into DEX file format
    Dex文件格式详解
    android中Dex文件结构详解
    Dex文件及Dalvik字节码格式解析
    Dex 文件格式详解
    Dex文件格式详解
    Android关于Dex拆分(MultiDex)技术详解

    展开全文
  • Android Dex文件格式(一)

    千次阅读 2018-05-29 20:32:13
    dex是Android平台上(Dalvik虚拟机)的可执行文件, 相当于Windows平台中的exe文件, 每个Apk安装包中都有dex文件, 里面包含了该app的所有源码, 通过反编译工具可以获取到相应的java源码。 为什么需要学习dex文件格式? ...

    dex是Android平台上(Dalvik虚拟机)的可执行文件, 相当于Windows平台中的exe文件, 每个Apk安装包中都有dex文件, 里面包含了该app的所有源码, 通过反编译工具可以获取到相应的java源码。

           为什么需要学习dex文件格式? 最主要的一个原因: 由于通过反编译dex文件可以直接看到java源码, 越来越多的app(包括恶意病毒app)都使用了加固技术以防止app被轻易反编译, 当需要对一个加固的恶意病毒app进行分析或对一个app进行破解时, 就需要了解dex文件格式, 将加固的dex文件还原后(脱壳)再进行反编译获取java源码, 所以要做Android安全方面的深入, dex文件格式是基础中的基础。
       
    通过一个构建简单的dex文件, 来学习和了解dex文件的相关格式, 首先编写一段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!");
        }
    }
    复制代码
    将.java文件编译成.class文件:
    1. javac Hello.java
    将.class文件编译成.dex文件:
    1. dx --dex --output=Hello.dex Hello.class
    dx是Android SDK中继承的工具(dx.bat), 在SDK目录下 AndroidSDK\build-tools\19.1.0中(选择自己的安装版本, 这里就用19.1.0了)
    如果在编译dex时, 出现图上的错误提示, 说明编译.class文件时使用的JDK版本太高了, 使用1.6版本的JDK就可以了, 重新生成.class文件, 然后再使用dx工具生成.dex文件即可:
    javac -source 1.6 -target 1.6 Hello.java
    可以将生成的dex放到Android的虚拟机中运行测试:
    adb push Hello.dex /mnt/sdcard/
    adb shell dalvikvm -cp /mnt/sdcard/Hello.dex Hello
     
    进入正题, 先来看一张dex文件结构图, 来了解一个大概:
    整个dex文件被分成了三个大块
     
    第一块: 文件头
        文件头记录了dex文件的一些基本信息, 以及大致的数据分布. dex文件头部总长度是固定的0x70
    dex_header:
    字段名称偏移量长度(byte)当前例子中字段值字段描述
    magic0x00x8dex 035dex魔术字, 固定信息: dex\n035
    checksum0x80x40x0F828C9Calder32算法, 去除了magic和checksum
    字段之外的所有内容的校验码
    signature0xc0x1458339636BED8A6CC826E
    A09B77D5C3A620262CD
    sha-1签名, 去除了magic、checksum和
    signature字段之外的所有内容的签名
    fileSize0x200x40x0000043C整个dex的文件大小
    headerSize0x240x40x00000070整个dex文件头的大小 (固定大小为0x70)
    endianTag0x280x40x12345678字节序 (大尾方式、小尾方式)
    默认为小尾方式 <--> 0x12345678
    linkSize0x2c0x40x00000000链接段的大小, 默认为0表示静态链接
    linkOff0x300x40x00000000链接段开始偏移
    mapOff0x340x40x0000039Cmap_item偏移
    stringIdsSize0x380x40x00000019字符串列表中的字符串个数
    stringIdsOff0x3c0x40x00000070字符串列表偏移
    typeIdsSize0x400x40x00000009类型列表中的类型个数
    typeIdsOff0x440x40x000000D4类型列表偏移
    protoIdsSize0x480x40x00000006方法声明列表中的个数
    protoIdsOff0x4c0x40x000000F8方法声明列表偏移
    fieldIdsSize0x500x40x00000001字段列表中的个数
    fieldIdsOff0x540x40x00000140字段列表偏移
    methodIdsSize0x580x40x00000009方法列表中的个数
    methodIdsOff0x5c0x40x00000148方法列表偏移
    classDefsSize0x600x40x00000001类定义列表中的个数
    classDefsOff0x640x40x00000190类定义列表偏移
    dataSize0x680x40x0000028C数据段的大小, 4字节对齐
    dataOff0x6c0x40x000001B0数据段偏移
    第二块: 索引区
            索引区中索引了整个dex中的字符串、类型、方法声明、字段以及方法的信息, 其结构体的开始位置和个数均来自dex文件头中的记录(或通过map_list也可以索引到记录)
     
    1. 字符串索引区, 描述dex文件中所有的字符串信息
        //Direct-mapped "string_id_item".
            struct DexStringId {
                u4 stringDataOff;      //file offset to string_data_item
            };
    描述字符串索引的结构体为DexStringId, 里面只有一个成员是指向string_id_item结构的偏移, 在dalvik源码的doc文档(dex-format.html)中可以看到对该结构的描述
    字符串列表中的字符串并非普通的ascii字符串, 它们是由MUTF-8编码表示的
    MUTF-8为Modified UTF-8, 即经过修改的UTF-8编码, 有以下特点:
    ①. MUTF-8使用1~3字节编码长度
    ②. 大于16位的Unicode编码 U+10000~U+10ffff使用3字节来编码
    ③. U+0000采用2字节来编码
    ④. 采用类似于C语言中的空字符null作为字符串的结尾
    string_id_item:
    string_data_item:
    indexstringDataOffutf16_sizedatastring
    00x2520x020x0D, 0x0A , 0x00回车换行
    10x2560x060x3C, 0x69, 0x6E, 0x69, 0x74, 0x3E, 0x00<init>
    20x25E0x0C0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x00Hello World!
    30x26C0x0A0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2E, 0x6A, 0x61, 0x76, 0x61, 0x00Hello.java
    40x2780x010x4C, 0x00L
    50x27B0x070x4C, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x3B, 0x00LHello;
    60x2840x020x4C, 0x4C, 0x00LL
    70x2880x030x4C, 0x4C, 0x4C, 0x00LLL
    80x28D0x150x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x69, 0x6F, 0x2F, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x3B, 0x00Ljava/io/PrintStream;
    90x2A40x120x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x00Ljava/lang/Object;
    100x2B80x120x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x00Ljava/lang/String;
    110x2CC0x190x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x65, 0x72, 0x3B, 0x00Ljava/lang/StringBuilder;
    120x2E70x120x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x3B, 0x00Ljava/lang/System;
    130x2FB0x070x4D, 0x79, 0x50, 0x72, 0x69, 0x6E, 0x74, 0x00MyPrint
    140x3040x010x56, 0x00V
    150x3070x020x56, 0x4C, 0x00VL
    160x30B0x130x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3B, 0x00  [Ljava/lang/Object;
    170x3200x130x5B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B, 0x00  [Ljava/lang/String;
    180x3350x060x61, 0x70, 0x70, 0x65, 0x6E, 0x64, 0x00append
    190x33D0x040x6D, 0x61, 0x69, 0x6E, 0x00main
    200x3430x0D0x6E, 0x69, 0x68, 0x61, 0x6F, 0x2C, 0x20, 0x73, 0x68, 0x69, 0x6A, 0x69, 0x65, 0x00nihao, shijie
    210x3520x030x6F, 0x75, 0x74, 0x00out
    220x3570x060x70, 0x72, 0x69, 0x6E, 0x74, 0x66, 0x00printf
    230x35F0x070x70, 0x72, 0x69, 0x6E, 0x74, 0x6C, 0x6E, 0x00println
    240x3680x080x74, 0x6F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x00toString
    通过源码和字符串列表中的对比可以发现, 我们定义的类的类名, 成员函数名, 函数的参数类型, 字符串, 以及调用的系统函数的名和源码的文件名在字符串列表中都有对应的值
    包括在MyPrint函数中printf的参数 str + "\r\n", 实际被转换为StringBuilder.append的形式在字符串列表中也有所体现
    了解了字符串列表中的信息后, 其实就可以实现一个dex的字符串混淆器了, 把当前有意义的字符串名称替换成像a b c这样无意义的名称
    当前目前只依靠字符串列表就实现混淆是不够的, 因为里面包含了系统函数的名称(System.out.print、main等),像这样的系统函数是不能被混淆的,所以还需要借助其他索引区的信息将一些不能被混淆的字符串排除掉
     
    2. 类型索引区, 描述dex文件中所有的类型, 如类类型、基本类型、返回值类型等
      //Direct-mapped "type_id_item".
            struct DexTypeId {
                u4  descriptorIdx;      //DexStringId中的索引下标
            };
    描述类型索引的结构体为DexTypeId, 里面只有一个成员是指向字符串索引区的下标, 基本上结构体成员中以Idx结尾的都是某个索引列表的下标
    type_id_item:
    indexdescriptorIdxstring
    00x05LHello;
    10x08Ljava/io/PrintStream;
    20x09Ljava/lang/Object;
    30x0ALjava/lang/String;
    40x0BLjava/lang/StringBuilder;
    50x0CLjava/lang/System;
    60x0EV
    70x10[Ljava/lang/Object;
    80x11[Ljava/lang/String;
    源码中的类类型、返回值类型在类型列表中都有对应的值, 在做dex字符串混淆的时间, 可以通过类型索引区过滤掉描述系统类类型、返回值类型的字符串,当然这还是不够的, 还需要借助其他索引区进行相应的排除
     
    3. 方法声明索引区, 描述dex文件中所有的方法声明
          //Direct-mapped "proto_id_item".
            struct DexProtoId {
                u4  shortyIdx;          //DexStringId中的索引下标
                u4  returnTypeIdx;      //DexTypeId中的索引下标
                u4  parametersOff;      //DexTypeList的偏移
            };
    shortyIdx为方法声明字符串
    returnTypeIdx为方法返回类型字符串
    parametersOff指向一个DexTypeList结构体, 存放了方法的参数列表, 如果方法没有参数值为0
    复制代码
          //Direct-mapped "type_item".
            struct DexTypeItem {
                u2  typeIdx;            //DexTypeId中的索引下标
            };
            //rect-mapped "type_list".
            struct DexTypeList {
                u4  size;               //DexTypeItem的个数
                DexTypeItem list[1];    //DexTypeItem变长数组
            };
    复制代码
    proto_id_item:
    type_list:
    proto_it_item:
    indexshortyIdxreturnTypeIdxparametersOffshortyIdx_stringreturnTypeIdx_string
    00x070x010x23CLLLLjava/io/PrintStream;
    10x040x030x0LLjava/lang/String;
    20x060x040x244LLLjava/lang/StringBuilder;
    30x0E0x060x0VV
    40x0F0x060x244VLV
    50x0F0x060x24CVLV
    type_list:
    parametersOfftypeIdxstring
    0x23C0x03Ljava/lang/String;
    0x23C0x07[Ljava/lang/Object;
    0x2440x03Ljava/lang/String;
    0x24C0x08[Ljava/lang/String;
     
    4. 字段索引区, 描述dex文件中所有的字段声明, 这个结构中的数据全部都是索引值, 指明了字段所在的类、字段的类型以及字段名称
     //Direct-mapped "field_id_item".
            struct DexFieldId {
                u2  classIdx;       类的类型, DexTypeId中的索引下标
                u2  typeIdx;        字段类型, DexTypeId中的索引下标      
                u4  nameIdx;        字段名称, DexStringId中的索引下标
            };

    indexclassIdxtypeIdxnameIdxclassIdx_stringtypeIdx_stringnameIdx_string
    00x050x010x15Ljava/lang/System;Ljava/io/PrintStream;out
     
    5. 方法索引区, 描述Dex文件中所有的方法, 指明了方法所在的类、方法的声明以及方法名字
      //Direct-mapped "method_id_item".
            struct DexMethodId{
                u2  classIdx;           类的类型, DexTypeId中的索引下标
                u2  protoIdx;            声明类型, DexProtoId中的索引下标
                u4  nameIdx;            方法名, DexStringId中的索引下标
            };

    indexclassIdprotoIdxnameIdxclassIdx_stringprotoIdx_stringnameIdx_string
    00x000x030x01LHello;void()<init>
    10x000x040x0DLHello;void(Ljava/lang/String;)MyPrint
    20x000x050x13LHello;void([Ljava/lang/String;)main
    30x0x10x000x16Ljava/io/PrintStream;Ljava/io/PrintStream;
    (Ljava/lang/String;,
    [Ljava/lang/Object;)
    printf
    40x010x040x17Ljava/io/PrintStream;void(Ljava/lang/String;)println
    50x020x030x01Ljava/lang/Object;void()<init>
    60x040x030x04Ljava/lang/StringBuilder;void()<init>
    70x040x020x12Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;
    (Ljava/lang/String;)  
    append
    80x040x010x18Ljava/lang/StringBuilder;Ljava/lang/String;()toString
    到此第二块索引区就解析完了, 可以看到解析的步骤非常简单,在解析第三块数据区之前, 补上一个MapList的解析,就是在dex文件头中map_off所指向的位置
    这个DexMapList描述Dex文件中可能出现的所有类型, map_list和dex文件头中的有些数据是重复的, 但比dex文件头中要多, 完全是为了检验作用而存在的
     //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,
    };
    复制代码

    indextypeunusedsizeoffsettype_string
    00x000x000x010x00kDexTypeHeaderItem
    10x010x000x190x70kDexTypeStringIdItem
    20x020x000x090xD4kDexTypeTypeIdItem
    30x030x000x060xF8kDexTypeProtoIdItem
    40x040x000x010x140kDexTypeFieldIdItem
    50x050x000x090x148kDexTypeMethodIdItem
    60x060x000x010x190kDexTypeClassDefItem
    70x20010x000x030x1B0kDexTypeCodeItem
    80x10010x000x030x23CkDexTypeTypeList
    90x20020x000x190x252kDexTypeStringDataItem
    100x20030x000x030x372kDexTypeDebugInfoItem
    110x20000x000x010x388kDexTypeClassDataItem
    120x10000x000x010x39CkDexTypeMapList
    可以看到Dex文件头中的项与在DexMapList中存在的项的描述信息(个数和偏移)是一致的
    当Android系统加载dex文件时,如果比较文件头类型个数与map里类型不一致时,就会停止使用这个dex文件
     
    由于第三块数据区的内容比较多, 所以将Dex文件格式分为(一)(二)两个部分, 在第(二)部分中将对数据区进行详细的解析
    展开全文
  • android反编译工具(dex2jar,apk2java)

    千次下载 热门讨论 2011-08-28 22:30:54
    Android APK反编译得到Java源代码和资源文件的工具 反编译apk生成程序的源代码和图片、XML配置、语言资源等文件的工具
  • 现在市面上的Android手机大部分都是运行的是ART虚拟机了。还记得自己一部Android手机(HUWEIg520),Android4.1系统。那时候还是没有ART虚拟机的。作为Android开发者,我们应该对Android的发展历史有些了解为什么...

    个人博客地址 http://dandanlove.com/

    现在市面上的 Android 手机大部分都是运行的是ART虚拟机了。还记得自己一部 Android手机(HuaweiG520),Android4.1 系统。那时候还是没有 ART虚拟机 的。作为Android开发者,我们应该对 Android 的发展历史有些了解为什么 Android 会经历这么多的变化。Android 是先有 JVM 然后是 Dalvik ,接着是现在的 ART虚拟机 。那么他们之间有什么关系呢?

    Dalvik和ART

    JVM就不用讲述了大家都有了解,不了解的参见JVM百度百科

    DalvikGoogle 公司自己设计用于 Android 平台的虚拟机,是 Google 等厂商合作开发的 Android 移动设备平台的核心组成部分之一。它可以支持已转换为 .dex 格式的 Java 应用程序的运行,.dex 格式是专为Dalvik 设计的一种压缩格式,适合内存和处理器速度有限的系统。

    Dalvik和JVM的主要区别

    首先通过介绍 Dalvik 的时候我们就知道 Dalvik 运行的是 dex 文件,而JVM 运行的是 class 文件。

    Dalvik VM 是基于寄存器的架构,而 JVM 是栈机。所以 Dalvik VM 的好处是可以做到更好的提前优化(ahead-of-time optimization)。 另外基于寄存器架构的VM执行起来更快,但是代价是更大的代码长度。

    基于寄存器架构的虚拟机有这么多的好处,为什么之前设计JAVA的程序员没有采用呢,而是采用现在基于栈的架构开发的呢?

    因为基于栈的虚拟机也有它的优点,它不对 host 平台的 寄存器数量 做假设,有利于移植到不懂的平台,这也符合的Java跨平台的特点。而Dalvik 虚拟机则不关心这些,因为它本来就是为 ARM 这样的多寄存器平台设计的,另外 Dalvik 被移植到 x86 机器上,即使 x86 这种寄存器少的平台,寄存器架构的虚拟机也可以运行。

    一般来说,基于堆栈的机器必须使用指令才能从堆栈上的加载和操作数据,因此,相对基于寄存器的机器,它们需要更多的指令才能实现相同的性能。但是基于寄存器机器上的指令必须经过编码,因此,它们的指令往往更大。

    public class Demo {
        public static void foo() {
            int a = 1;
            int b = 2;
            int c = (a + b) * 5;
         } 
    }
    

    我们可以查看Demo.java在JVM中的class和Dalvik的dex字节码文件:
    详见:使用dx将class转dex总结

    bytecode

    想要了解更多:基于栈的虚拟机 VS 基于寄存器的虚拟机

    Dalvik在JVM上的优化

    • 在编译时提前优化代码而不是等到运行时
    • 虚拟机很小,使用的空间也小;被设计来满足可高效运行多种虚拟机实例。
    • 常量池已被修改为只使用32位的索引,以简化解释器
    • 标准Java字节码实行8位堆栈指令,Dalvik使用16位指令集直接作用于局部变量。局部变量通常来自4位的“虚拟寄存器”区。这样减少了Dalvik的指令计数,提高了翻译速度。

    Dalivk进化之ART

    2014年6月25日,Android L 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除 Dalvik ,代替它的是传闻已久的 ART。在在Android系统4.4 提出,在 Android5.0之后完全弃用 dalvik 全部采用 art为执行环境。

    ART( Android Runtime

    ART 的机制与 Dalvik 不同。在 Dalvik 下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。

    ART的优缺点

    优点:

    • 系统性能的显著提升。
    • 应用启动更快、运行更快、体验更流畅、触感反馈更及时。
    • 更长的电池续航能力。
    • 支持更低的硬件。

    缺点:

    • 机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
    • 应用的安装时间会变长。

    class、dex、odex、ELF相爱相杀

    从执行文件上面进行分析的话,JVM 对应 class 文件,Dalivk 对应 odex 文件,而 ART 对应 oat 文件。

    工具:javac, dx
    .java——>.class——->.dex
    .java 文件经过 javac 编译器生成 .class 字节码 再经过。dx 工具生成 .dex

    为了在 JVM 优化出一个 Dalivk 虚拟机,所以把 JVM 运行的 class 文件进行打包优化为 dex 文件,但其本质还是和 class 文件一样属于字节码文件。但是为了每次启动时都去掉从字节码到机器码的编译过程,Google 又从 Dalivk 中优化出了 ART,在其安装应用的时候将 dex 文件进行预处理生成可执行的 oat 文件。

    JIT的引入

    据说 Android 2.2 的虚拟机 dalvik 使用了 JIT 技术,使其运行速度快了5倍。

    dalvik 解释并执行程序,JIT 技术主要是对多次运行的代码进行编译,当再次调用时使用编译之后的机器码,而不是每次都解释,以节约时间。5倍是测试程序测出的值,并不是说程序运行速度也能达到5倍,这是因为测试程序有很多的重复调用和循环,而一般程序主要是顺序执行的,而且它是一边运行,一边编译,一开始的时候提速不多,所以真正运行程序速度提高不是特别明显。

    每启动一个应用程序,都会相应地启动一个 dalvik 虚拟机,启动时会建立JIT 线程,一直在后台运行。当某段代码被调用时,虚拟机会判断它是否需要编译成机器码,如果需要,就做一个标记,JIT 线程不断判断此标记,如果发现被设定就把它编译成机器码,并将其机器码地址及相关信息放入 entry table 中,下次执行到此就跳到机器码段执行,而不再解释执行,从而提高速度。

    odex(optimized dex)

    因为 apk 实际为 zip 压缩包,虚拟机每次加载都需要从 apk 中读取classes.dex 文件,这样会耗费很多的时间,而如果采用了 odex 方式优化的 dex 文件,他包含了加载 dex 必须的依赖库文件列表,只需要直接加载而不需要再去解析。

    Android N 之前,对于在 dalvik 环境中 使用 dexopt 来对 dex 字节码进行优化生成 odex 文件最终存在手机的 data/dalvik-cache 目录下,最后把 apk 文件中的 dex 文件删除。

    Android O 之后,odex 是从 vdex 这个文件中 提取了部分模块生成的一个新的可执行二进制码文件 , odexvdex 中提取后,vdex 的大小就减少了。

    • 第一次开机就会生成在 /system/app/<packagename>/oat/
    • 在系统运行过程中,虚拟机将其 从 /system/appcopy/data/davilk-cache/ 下;
    • odex + vdex = apk 的全部源码 (vdex 并不是独立于 odex 的文件 odex + vdex 才代表一个 apk );

    AOT(Ahead-of-time)

    ART 推出了预先 (AOT) 编译,可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。该实用工具接受 DEX 文件作为输入,并针对目标设备生成已编译应用的可执行文件。之后打开 App 的时候,不需要额外的翻译工作,直接使用本地机器码运行,因此运行速度提高。

    AOTart 的核心,oat 文件包含 oatdataoatexec。前者包含 dex 文件内容,后者包含生成的本地机器指令,从这里看出 oat 文件回会比 dex 文件占用更大的存储空间。

    因为 oat 文件包含生成的本地机器指令进而可以直接运行,它同样保存在手机的 data/dalvik-cache 目录下 PMS(PackgetManagerService)—>installd(守护进程)——>dex2oat(/system/bin/dex2oat) 。注意存放在 data/dalvik-cache 目录下的后缀名都仍为 .dex 前者其实表示一个优化过的 .dex 文件 后者为 .art 文件。

    push 一个新的 apk 文件覆盖之前 /system/appapk 文件,会触发 PKMS 扫描时下发 force_dex flag ,强行生成新的 vdex文件 ,覆盖之前的vdex 文件,由于某种机制,这个新 vdex 文件会 copy/data/dalvik-cache/ 下,于是 art 文件也变化了。

    混合运行时

    Android N 开发者预览版包含了一个混合模式的运行时。应用在安装时不做编译,而是解释字节码,所以可以快速启动。ART中有一种新的、更快的解释器,通过一种新的 JIT 完成,但是这种 JIT 的信息不是持久化的。取而代之的是,代码在执行期间被分析,分析结果保存起来。然后,当设备空转和充电的时候,ART 会执行针对“热代码”进行的基于分析的编译,其他代码不做编译。为了得到更优的代码,ART 采用了几种技巧包括深度内联。

    对同一个应用可以编译数次,或者找到变“热”的代码路径或者对已经编译的代码进行新的优化,这取决于分析器在随后的执行中的分析数据。这个步骤仍被简称为 AOT,可以理解为“全时段的编译”(All-Of-the-Time compilation)。

    这种混合使用 AOT、解释、JIT 的策略的全部优点如下。

    • 即使是大应用,安装时间也能缩短到几秒;
    • 系统升级能更快地安装,因为不再需要优化这一步;
    • 应用的内存占用更小,有些情况下可以降低 50%;
    • 改善了性能;
    • 更低的电池消耗;

    vdex

    官网回答:ART的运作方式
    dex2oat 工具接受一个 APK 文件,并生成一个或多个编译工件文件,然后运行时将会加载这些文件。文件的个数、扩展名和名称会因版本而异。
    在 Android O 版本中,将会生成以下文件:

    • .vdex:其中包含 APK 的未压缩 DEX 代码,另外还有一些旨在加快验证速度的元数据。
    • .odex:其中包含 APK 中已经过 AOT 编译的方法代码。
    • .art (optional):其中包含 APK 中列出的某些字符串和类的 ART 内部表示,用于加快应用启动速度。
    • 第一次开机就会生成在 /system/app/<packagename>/oat/ 下;
    • 在系统运行过程中,虚拟机将其 从 /system/appcopy/data/davilk-cache/ 下。

    ELF文件

    ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF 是构成众多 xNIX 系统的基础之一。

    apk安装过程

    大家都知道 apk 其实就是 zipapk 安装过程其实就是解压过程。
    用户应用安装涉及以下几个目录:

    • data/app 安装目录 安装时会把 apk 文件 copy 到这里;
    • data/dalvik-cache 如上述描述中的存放.dex ( .odex 无论 davilkdex 还是 artoat 格式);
    • data/data/pkg/ 存放应用程序的数据;

    Android5.1 版本下 oat 文件都以 .dex 文件在 data/dalvik-cache 目录下:

    dalvik-cache.jpeg

    Android8.0 版本下 dex2oat 工具生成的三个.art,.odex,.vdex文件都在 data/dalvik-cache 目录下:

    sogou-dalvik-cache-arm64.png

    Android8.0 版本下搜狗输入法在 data/app/com.sohu.inputmethod.sogou.xiaomi-JAKuH_Jhlk6Y36zKoTUN8Q== 目录下:

    sogou-data-app-xxxxx.jpg

    Android8.0 版本下搜狗输入法在 system/app/SogouInput 目录下:

    sogou-system-app-xxx.jpg

    文中部分内容摘自:
    深入理解JVM-字节码执行引擎
    知乎-Dalvik 虚拟机和 Sun JVM 在架构和执行方面有什么本质区别?
    《Java虚拟机原理图解》4.JVM机器指令集
    Android art-Android dex,odex,oat,vdex,art文件结构学习总结
    Android N 混合使用 AOT 编译,解释和 JIT 三种运行时

    修改历史:
    【第一次发布】2017.09.18 19:00
    【第二次主要修改不oat、vdex和odex相关内容】2020.04.02 22:00

    文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦

    想阅读作者的更多文章,可以查看我 个人博客 和公共号:
    振兴书城

    展开全文
  • 关于android art模式提取的OAT转dex

    千次阅读 2017-04-20 22:26:21
    三星 android 6.0 系统上提取出的三星软件...查了相关资料后发现是art模式会将dex转成oat模式,那如何逆向呢 方案: 1.ExtractDexFromOat + jeb/jax https://github.com/ManyFace/ExtractDexFromOat 2.DEX/AP...

    三星 android 6.0 系统上提取出的三星软件安装包是.odex后缀的

    但是文件头为elf,而不是dey.036

    查了相关资料后发现是art模式会将dex转成oat模式,那如何逆向呢

    方案:

    1.ExtractDexFromOat    +  jeb/jax

    https://github.com/ManyFace/ExtractDexFromOat

     

    2.DEX/APK分析工具GDA

    http://bbs.pediy.com/thread-203916-3.htm

     

    展开全文
  • Android dex、odex、oat、vdex、art区别

    千次阅读 2019-11-30 20:24:23
    1.dex java程序编译成class后,dx工具将所有class文件合成一个dex文件,dex文件是jar文件大小的50%左右. 2.odex(Android5.0之前)全称:Optimized DEX;即优化过的DEX. Android5.0之前APP在安装时会进行验证和优化...
  • Android开机速度 我们知道在L及以后的版本中,当系统升级后,或者恢复出厂设置后,或者第一次开机,其启动时间往往很长,短的有3-4分钟,而长的多达10分钟,那么是原因导致的,我们将分析其原因. ...
  • 理清 .dex、.odex、ART、AOT、OAT 等近似名称的概念和意义。 APK 生成 apk的打包过程 最终 apk 内的代码文件为 .dex 文件。 APK 安装运行 基础概念 AOT:编译方式,运行前编译。 JIT:编译方式,动态编译。 ...
  • 一篇文章带你搞懂DEX文件的结构

    万次阅读 多人点赞 2017-02-20 17:25:03
    DEX文件就是Android Dalvik虚拟机运行的程序,关于DEX文件的结构的重要性我就不多说了。下面,开练! 建议:不要只看,跟着我做。看再多遍不如自己亲自实践一遍来的可靠,别问我为什么知道。泪崩ing..... 首先,...
  • 锁定 22 亿美金数字资产的 Uniswap 并非横空出世,实际上去中心化交易所 Dex 发展至今已历时 7 年,BitMex 深入浅出的总结了从 2013 年至今的 dex 发展史中的里程碑协议: Counterparty → IDEX → Uniswap 最近 1...
  • android .dex文件探究

    千次阅读 2018-10-13 18:21:32
    在我们写Java代码的时候,生成的文件是.java文件,但是JVM并不识别这个,所以会先转成class文件,而在Android端,Android上的Davlik虚拟机能运行.dex。所以dex文件中包含了所有的app代码,可利用反编译工具获取java...
  • dex文件格式学习

    千次阅读 2016-12-21 11:38:50
    一.dex文件的生成 我们可以通过java文件来生成一个简单的dex文件 编译过程: 首先编写java代码如下: (1) 编译成 java class 文件 执行命令 : javac Hello.java 编译完成后 ,目录下生成 Hello.class ...
  • Android Dex文件格式解析

    千次阅读 2016-12-12 17:11:28
    Dex文件是Android虚拟机下的可执行文件,包含了应用程序所用到所有操作指令和运行时数据。在程序编译过程中,java源文件先被编译成class文件,然后通过dx工具将多个class文件整合为一个dex文件。这样的文件结构使得...
  • dex2oat的原理及慢的原因

    万次阅读 2017-10-10 11:05:55
    Overview 众所周知,Android的程序是可以用java来写的,如果可以运行java语言,那么就一定需要一个虚拟机。同时,google为了避开商业纠纷,采用...dex2oat的原理 虚拟机的发生展经历了从初期的dalvik,到中期的d...
  • Android dex分包方案 (多dex)

    千次阅读 2016-06-14 15:09:14
    当一个app的功能越来越复杂,代码量越来越多,也许有一天便会突然遇到下列现象: 1. 生成的apk在2.3以前的机器无法安装,提示INSTALL_FAILED_DEXOPT ...2. 方法数量过多,编译时出错,提示: ...
  • Dex文件结构

    千次阅读 2017-07-03 19:59:28
    前言Class文件:即java的字节码文件,java源码文件编译后生成了字节码文件,然后被jvm执行,字节码文件中有一个非常重要的区域是常量池,编译的过程中,字节码文件并不会保存方法和字段的最终内存布局信息,也就是说...
  • Android Dex分包

    2015-12-07 12:38:49
    腾讯Bugly特约作者:李金涛 一、背景 就在项目灰度测试前不久,爆出了在 Android 3.0以下手机上安装时出现 INSTALL _ FAILED_DEXOPT,导致安装失败。这一问题意味着项目将不能在 Android 3.0以下的手机上安装...
  • ART模式下基于dex2oat脱壳的原理分析

    千次阅读 2017-11-12 18:16:23
    一般情况下,Android Dex文件在加载到内存之前需要先对dex文件进行优化处理(如果Android Dex文件已经优化过,则不需要进行优化处理操作,后面进行加载到内存即可),在Dalvik虚拟机模式下,Android dex文件经过dex2...
  • Android DEX安全攻防战

    万次阅读 多人点赞 2013-08-10 16:14:51
    本文章由Jack_Jia编写,转载请注明出处。  文章链接:http://blog.csdn.net/jiazhijun/article/details/9428861 作者:Jack_Jia 邮箱: 309... 一、谈谈DEX相关知识  1、什么是DEX  DEX是Dalvik EXecutabl
  • 版权声明:本文为博主原创文章,...一个安卓 apk 里面包含有 AndroidManifest.xml、classes.dex、resources.arsc 等文件,java 代码主要存在于 classes.dex 中。 把 classes.dex 文件拆分为两个 dex 文件,其中主 d...
1 2 3 4 5 ... 20
收藏数 57,548
精华内容 23,019
关键字:

dex