安卓逆向系列教程(二)APK 和 DEX

    APK 是 Android 软件包的分发格式,它本身是个 Zip 压缩包。APK 根目录下可能出现的目录和文件有:

    res 中可能出现的目录如下:

    resources.arsc

    在 APK 中是找不到res/values这个目录的,因为它里面的文件编译后打包成了resources.arsc。为了理解它,我们先看一看原始的res/values

    res/values中保存资源 XML 文件,根节点为<resources>。一般可能会出现以下几种文件:

    res/values中的文件名称是无所谓的,这些名称只是约定。也就是说,任何res/values中的文件中的字符串都会出现在R.strings里面。

    虽然我们在 APK 中无法直接看到这些文件,但是反编译之后就可以了。反编译之后,我们也会找到一个public.xml文件,是res里所有东西的索引:

    DEX

    DEX 即 Dalvik Executable,Dalvik 可执行文件。它的结构如下:

    1. struct DexFile{
    2. DexHeader Header;
    3. DexStringId StringIds[stringIdsSize];
    4. DexTypeId TypeIds[typeIdsSize];
    5. DexFieldId FieldIds[fieldIdsSize];
    6. DexMethodId MethodIds[methodIdsSize];
    7. DexProtoId ProtoIds[protoIdsSize];
    8. DexClassDef ClassDefs[classDefsSize];
    9. DexData Data;
    10. DexLink LinkData;
    11. };

    我们可以看到,它可以分为九个区段,如下:

    大体结构如这张图所示:

    Header 区段

    Header 区段用于储存版本标识、校验和、文件大小、各部分的大小及偏移。结构以及描述如下:

    1. struct DexHeader {
    2. u1 magic[8]; /* 版本标识 */
    3. u4 checksum; /* adler32 检验和 */
    4. u4 fileSize; /* 整个文件大小 */
    5. u4 headerSize; /* Header 区段大小 */
    6. u4 endianTag; /* 字节序标记 */
    7. u4 linkSize; /* 链接区段大小 */
    8. u4 linkOff; /* 链接区段偏移 */
    9. u4 mapOff; /* MapList 的偏移 */
    10. u4 stringIdsSize; /* StringId 的个数 */
    11. u4 stringIdsOff; /* StringIds 区段偏移 */
    12. u4 typeIdsSize; /* TypeId 的个数 */
    13. u4 typeIdsOff; /* TypeIds 区段偏移 */
    14. u4 protoIdsSize; /* ProtoId 的个数 */
    15. u4 protoIdsOff; /* ProtoIds 区段偏移 */
    16. u4 fieldIdsSize; /* FieldId 的个数 */
    17. u4 fieldIdsOff; /* FieldIds 区段偏移 */
    18. u4 methodIdsSize; /* MethodId 的个数 */
    19. u4 methodIdsOff; /* MethodIds 区段偏移 */
    20. u4 classDefsSize; /* ClassDef 的个数 */
    21. u4 classDefsOff; /* ClassDefs 区段偏移 */
    22. u4 dataSize; /* 数据区段的大小 */
    23. u4 dataOff; /* 数据区段的文件偏移 */
    24. };

    有几个条目需要特别提醒。

    • magic:必须为DEX_FILE_MAGIC

      1. ubyte[8] DEX_FILE_MAGIC = { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 }
      2. = "dex\n035\0";
    • checksum:是整个文件除去它本身以及魔数的校验和。

    • signature:是整个文件除去它本身、校验和以及魔数的哈希值。

    • endianTag:有两种顺序,小端和大端,定义如下:

      一般为小端序,反正我还没见过大端的。

    • stringIdsOff:由于前一个区段的偏移加上它的长度一般为后一个区段的偏移,所以这个条目一般也为 70。

    • xxxSize:要注意有几个是个数,后缀也是Size

    StringIds 区段包含stringIdsSizeDexStringId结构,如下:

    1. struct DexStringId {
    2. u4 stringDataOff; /* 字符串内容,字符串数据偏移 */
    3. };

    其中数据偏移指向 Data 区段的字符串数据。

    TypeIds 区段

    TypeIds 包含typeIdsSizeDexTypeId结构,如下:

    1. struct DexTypeId {
    2. u4 descriptorIdx; /* 类型的完全限定符,指向 DexStringId 列表的索引 */
    3. };

    索引是一个从 0 开始的数字,表示对应第几个DexStringId。这些DexStringId指向的字符串都是类型名称,比如ILjava/lang/String;之类的。DexTypeId的索引也会用于后面的结构。

    ProtoIds 区段

    ProtoIds 包含ProtoIdsSizeDexProtoId结构。这里的 Proto 指方法原型,包含返回类型和参数类型。

    1. struct DexProtoId {
    2. u4 shortyIdx; /* 原型缩写,指向 DexStringId 列表的索引 */
    3. u4 returnTypeIdx; /* 返回类型,指向 DexTypeId 列表的索引 */
    4. u4 parametersOff; /* 参数类型列表,指向 DexTypeList 的偏移 */
    5. };
    6. struct DexTypeList {
    7. u4 size; /* 接下来 DexTypeItem 的个数 */
    8. DexTypeItem list[size]; /* DexTypeItem 结构 */
    9. };
    10. struct DexTypeItem {
    11. u2 typeIdx; /* 参数类型,指向 DexTypeId 列表的索引 */
    12. };

    原型缩写是把所有返回类型和参数类型的名称拼在一起,对象的话只写L。比如int(int,int)写为IIIvoid()写为Vvoid(String)写为VL

    参数类型列表一般保存在Data区段中,如果没有,parametersOff为 0。

    TypeIds 包含fieldIdsSizeDexFieldId结构,如下:

    MethodIds 区段

    MethodIds 包含methodIdsSizeDexMethodId结构,如下:

    1. struct DexMethodId {
    2. u2 classIdx; /* 类的类型,指向 DexTypeId 列表的索引 */
    3. u2 protoIdx; /* 方法原型,指向 DexProtoId 列表的索引 */
    4. u4 nameIdx; /* 方法名称,指向 DexStringId 列表的索引 */

    ClassDefs 区段

    ClassDefs 包含classDefsSizeDexClassDef结构,如下:

    1. struct DexClassDef {
    2. u4 classIdx; /* 类的类型,指向 DexTypeId 列表的索引 */
    3. u4 accessFlags; /* 访问标志 */
    4. u4 superclassIdx; /* 父类类型,指向 DexTypeId列表的索引 */
    5. u4 interfacesOff; /* 接口,指向 DexTypeList 的偏移 */
    6. u4 sourceFileIdx; /* 源文件名,指向 DexStringId 列表的索引 */
    7. u4 annotationsOff; /* 注解,指向 DexAnnotationsDirectoryItem 结构 */
    8. u4 classDataOff; /* 指向 DexClassData 结构的偏移 */
    9. u4 staticValuesOff; /* 指向 DexEncodedArray 结构的偏移 */
    10. };
    11. struct DexClassData {
    12. DexClassDataHeader header; /* 各个字段与方法的个数 */
    13. DexField staticFields[staticFieldsSize]; /* 静态字段 */
    14. DexField instanceFields[instanceFieldsSize]; /* 实例字段 */
    15. DexMethod directMethods[directMethodsSize]; /* 直接方法 */
    16. DexMethod virtualMethods[virtualMethodsSize]; /* 虚方法 */
    17. };
    18. struct DexClassDataHeader {
    19. u4 staticFieldsSize; /* 静态字段个数 */
    20. u4 instanceFieldsSize; /* 实例字段个数 */
    21. u4 directMethodsSize; /* 直接方法个数 */
    22. u4 virtualMethodsSize; /* 虚方法个数 */
    23. };
    24. struct DexField {
    25. u4 fieldIdx; /* 指向 DexFieldId 的索引 */
    26. u4 accessFlags; /* 访问标志 */
    27. };
    28. struct DexMethod {
    29. u4 methodIdx; /* 指向 DexMethodId 的索引 */
    30. u4 accessFlags; /* 访问标志 */
    31. u4 codeOff; /* 方法指令,指向DexCode结构的偏移 */
    32. };
    33. struct DexCode {
    34. u2 registersSize; /* 使用的寄存器个数 */
    35. u2 insSize; /* 参数个数 */
    36. u2 outsSize; /* 调用其他方法时使用的寄存器个数 */
    37. u2 triesSize; /* Try/Catch个数 */
    38. u4 debugInfoOff; /* 指向调试信息的偏移 */
    39. u4 insnsSize; /* 指令集个数,以2字节为单位 */
    40. u2 insns[insnsSize]; /* 指令集 */
    41. };

    这个区段中除了存放二级结构和字符串,还有个重要的结构叫做DexMapList,它实际上 DEX 中所有东西的索引,包括各种二级结构、字符串和它本身。DEX 中同类结构都会保存在一起,所以一类结构只占用一个条目。

    1. struct DexMapList {
    2. u4 size; /* 条目个数 */
    3. DexMapItem list[size]; /* 条目列表 */
    4. };
    5. struct DexMapItem {
    6. u2 type; /* 结构类型,kDexType 开头 */
    7. u2 unused; /* 未使用,用于字节对齐 */
    8. u4 size; /* 连续存放的结构个数 */
    9. u4 offset; /* 结构的偏移 */
    10. };
    11. /* 结构类型代码 */
    12. enum {
    13. kDexTypeHeaderItem = 0x0000,
    14. kDexTypeStringIdItem = 0x0001,
    15. kDexTypeTypeIdItem = 0x0002,
    16. kDexTypeProtoIdItem = 0x0003,
    17. kDexTypeFieldIdItem = 0x0004,
    18. kDexTypeMethodIdItem = 0x0005,
    19. kDexTypeClassDefItem = 0x0006,
    20. kDexTypeMapList = 0x1000,
    21. kDexTypeTypeList = 0x1001,
    22. kDexTypeAnnotationSetRefList = 0x1002,
    23. kDexTypeAnnotationSetItem = 0x1003,
    24. kDexTypeClassDataItem = 0x2000,
    25. kDexTypeCodeItem = 0x2001,
    26. kDexTypeStringDataItem = 0x2002,
    27. kDexTypeDebugInfoItem = 0x2003,
    28. kDexTypeAnnotationItem = 0x2004,
    29. kDexTypeEncodedArrayItem = 0x2005,
    30. };

    参考