一、什么是odex文件 1.1 定义 odex文件是Android上的可执行文件。odex文件是由apk中的dex文件优化生成的,存放在cache/dalvik-cache目录下或者与apk同一目录下,最后删除apk文件中的classes.dex文件。
注意!!!不要被文件后缀名骗了! .dex
后缀的文件可以是DEX文件或者OAT文件,.odex
不再是ODEX文件(应该是Android 5之后),而是OAT文件,.oat
是OAT文件
1.2 优势
加快应用的运行速度,因为有了odex文件包含了加载所需要的依赖库文件列表,DVM只需要检测并加载所需依赖库即可执行odex文件,大大缩短了读取dex文件所需的时间。
减少空间占用。因为如果没有odex,系统会自动提取dex,这时不仅apk内有dex,/data/dalvik-cache目录下也有dex。
反编译APK文件,只能拿到资源文件,起到了一定的保护作用。
二、odex文件结构 2.1 整体结构
odex文件在dex文件头部添加了一个odex文件头,然后在dex末尾添加了dex文件的依赖库以及一些辅助数据。
由于odex文件在Android源码中并没有定义很多数据结构,仅有寥寥几个,所以在本文中会自定义一些数据结构,方便理解知识点。
在解析dex文件中,整个文件结构在源码中的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct DexFile { const DexOptHeader* pOptHeader; const DexHeader* pHeader; const DexStringId* pStringIds; const DexTypeId* pTypeIds; const DexFieldId* pFieldIds; const DexMethodId* pMethodIds; const DexProtoId* pProtoIds; const DexClassDef* pClassDefs; const DexLink* pLinkData; const DexClassLookup* pClassLookup; const void * pRegisterMapPool; const u1* baseAddr; int overhead; };
是的,在解析dex文件时,有一部分是没有提及到的。其中DexOptHeader就是odex头,DexLink以下的部分被称为auxillary section,即辅助数据段,它记录了dex文件被优化后添加的一些信息。
2.2 odex文件头 odex文件头的数据结构定义在源码目录/dalvik/libdex/DexFile.h中,对应DexOptHeader结构体,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct DexOptHeader { u1 magic[8 ]; u4 dexOffset; u4 dexLength; u4 depsOffset; u4 depsLength; u4 optOffset; u4 optLength; u4 flags; u4 checksum; };
odex标识具体值为dey!
2.3 dex文件 在上一篇文章中讲过,详见dex文件格式解析 - gla2xy’s blog (gal2xy.github.io) 。
然而,在优化成odex的过程中,该部分有些内容会被修改。
2.4 依赖库 依赖库对应的数据结构在Android源码中没有明确定义,借助《Android软件安全与逆向分析》中的定义:
在Android源码中没有定义的结构体,均来自《Android软件安全与逆向分析》
1 2 3 4 5 6 7 8 9 10 11 12 struct Dependences { u4 modWhen; u4 crc; u4 DALVIK_VM_BUILD; u4 numDeps; struct { u4 len; u1 name[len]; u1 signature[kSHA1DigestLen]; }tabel[numDeps]; }
依赖库结构的具体操作函数在Android 2.x~4.x版本的源码才有(应该,反正5及以上没见过),目录为dalvik\vm\analysis\DexPrepare.cpp,这里手打一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static int writeDependencies (int fd, u4 modWhen, u4 crc) { ...... buf = (u1*)malloc (bufLen); set4LE (buf + 0 , modWhen); set4LE (buf + 4 , crc); set4LE (buf + 8 , DALVIK_VM_BUILD); set4LE (buf + 12 , numDeps); ...... u1* ptr = buf + 16 ; for (cpe = gDvm.bootClassPath; cpe->ptr != NULL ; cpe++){ ...... const u1* signature = getSignature (cpe); int len = strlen (cacheFileName) + 1 ; ...... set4LE (ptr, len); ptr += 4 ; memcpy (ptr, cacheFileName, len); ptr += len; memcpy (ptr, signature, KSHA1DigestLen); ptr += KSHA1DigestLen; ...... } ... }
2.5 辅助数据 该部分有三个Chunk块,它们被Dalvik虚拟机加载到一个称为auxillart的段中。这三个Chunk块,都以一个header联合体开头 ,定义如下:
1 2 3 4 5 6 7 union { char raw[8 ]; struct { u4 type; u4 size; }ts; }header;
union是联合体,里面的变量共用同一空间,所以大小为8字节。其中type字段为枚举常量,具体如下:
1 2 3 4 5 eum{ kDexChunkClassLookup = 0x434c4b50 , kDexChunkRegisterMaps = 0x524d4150 , kDexChunkEnd = 0x41454e44 , }
size字段表示需要填充的数据的字节数。
在这三个Chunk块中,首先是ChunkClassLookup结构,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct ChunkClassLookup { header联合体; struct DexClassLookup { int size; int numEntries; struct { u4 classDescriptorHash; int classDescriptorOffset; int classDefOffset; } table[1 ]; }; };
Dalvik虚拟机通过DexClassLookup结构来检索dex文件中所有的类。
然后是ChunkRegisterMapPool结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct ChunkRegisterMapPool { header联合体; struct { struct RegisterMapClassPool { u4 numClasses; u4 classDataOffset[1 ]; }classpool; struct RegisterMapMethodPool { u2 methodCount; u4 methodData[1 ]; }; }MapPool; };
其中methodData对应RegisterMap结构体,该结构体如下:
1 2 3 4 5 6 struct RegisterMap { u1 format; u1 regWidth; u1 numEntries[2 ]; u1 data[1 ]; };
format字段用来指明需要用多少字节来表示方法内的指令地址。
regWidth字段用来指明需要用多少字节来表示方法内各个寄存器的状态。
numEntries字段用来指明保存了多少条寄存器状态的记录。
data字段用来指向保存实际数据的地方。
最后是ChunkEnd结构体:
2.6 整个odex文件结构总览图 参考其他人的图,并根据自己的理解画了个总览图,其中有误的地方烦请告知!感谢!
参考:
https://lief-project.github.io/doc/latest/tutorials/10_android_formats.html
https://newandroidbook.com/files/ArtOfDalvik.pdf
Android系统ODEX文件格式解析-CSDN博客
Dalvik虚拟机中RegisterMap结构解析-CSDN博客
https://baike.baidu.com/item/ODEX
https://www.wuyifei.cc/dex-vdex-odex-art/
《Android软件安全与逆向分析》