作者:郭嘉
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
github:https://github.com/AllenWells
【Android SDK程序逆向分析与破解系列】章节索引
【Android SDK程序逆向分析与破解系列】之一:Android安装程序APK分析
【Android SDK程序逆向分析与破解系列】之二:Android可执行程序DEX分析(一)
【Android SDK程序逆向分析与破解系列】之三:Android可执行程序DEX分析(二)
【Android SDK程序逆向分析与破解系列】之四:Android可执行程序ODEX分析
【Android SDK程序逆向分析与破解系列】之五:Android APK的静态分析
ODEX(OptimizedDEX)表示经过优化的DEX文件。
一 生成ODEX文件
ODEX文件有两种存在方式
从APK程序中提取出来,与APK文件放在用一个目录中,文件后缀名为”.odex”的文件。这类ODEX文件多为Android ROM的系统程序。
dalvik-cache缓存文件,文件后缀为“.dex”,保存在cache/dalvik-cache目录下,保存形式为“apk路径@apk名@classes.dex”,比如“system@[email protected]@classes.dex”表示安装在system/app目录下的Calculator.apk程序的ODEX文件。
二 ODEX文件结构
ODEX文件可以理解为DEX文件的一个超集,具体的结构如下所示:
三 ODEX结构体
3.1 DexOptHeader结构体
DexOptHeader是ODEX文件的文件头
/*
* Header added by DEX optimization pass. Values are always written in
* local byte and structure padding. The first field (magic + version)
* is guaranteed to be present and directly readable for all expected
* compiler configurations; the rest is version-dependent.
*
* Try to keep this simple and fixed-size.
*/
struct DexOptHeader {
u1 magic[8]; /* ODEX版本标识 */
u4 dexOffset; /* DEX文件头偏移 */
u4 dexLength; /* DEX文件总长度 */
u4 depsOffset; /* ODEX依赖库列表偏移 */
u4 depsLength; /* ODEX依赖库列表总长度 */
u4 optOffset; /* 辅助数据偏移 */
u4 optLength; /* 辅助数据总长度 */
u4 flags; /* 标志 */
u4 checksum; /* 依赖库与辅助数据的检验和 */
/* pad for 64-bit alignment if necessary */
};
3.2 Dependences结构体
Dependences结构体并不会被加载进内存,它的结构如下所示:
struct Dependences{
u4 modWhen; /* 时间戳 */
u4 crc; /* 检验 */
u4 DALVIK_VM_BUILD; /* Dalvik虚拟机版本号 */
u4 numDeps; /* 依赖库个数 */
struct{
u4 len; /* name字符串长度 */
u1 name[len]; /* 依赖库名称 */
kSHA1DigestLen signature;/* SHA-1哈希值 */
}table[numDeps];
};
- modWhen:用来记录优化前classes.dex的时间戳。
- crc:优化前classes.dex的crc校验值。
- DALVIK_VM_BUILD:Dalvik虚拟机版本号,不同版本的系统定义不同,Android2.2.3:19,Android2.3.3-2.3.7:23,Android4.0-4.1:27。
- numDeps:依赖库的个数。
- table:table结构的连续个数是由numDeps字段决定的,每个table结构由3个字段组成,用来描述一个依赖库的文件信息。
Dependences结构体结构体的操作函数为DexPrepare.cpp的writeDependencies()函数,代码片段如下所示:
/*
* Write the dependency info to "fd" at the current file position.
*/
static int writeDependencies(int fd, u4 modWhen, u4 crc)
{
u1* buf = NULL;
int result = -1;
ssize_t bufLen;
ClassPathEntry* cpe;
int numDeps;
/*
* Count up the number of completed entries in the bootclasspath.
*/
numDeps = 0;
bufLen = 0;
for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
const char* cacheFileName =
dvmPathToAbsolutePortion(getCacheFileName(cpe));
assert(cacheFileName != NULL); /* guaranteed by Class.c */
ALOGV("+++ DexOpt: found dep '%s'", cacheFileName);
numDeps++;
bufLen += strlen(cacheFileName) +1;
}
bufLen += 4*4 + numDeps * (4+kSHA1DigestLen);
buf = (u1*)malloc(bufLen);
set4LE(buf+0, modWhen);
set4LE(buf+4, crc);
set4LE(buf+8, DALVIK_VM_BUILD);
set4LE(buf+12, numDeps);
// TODO: do we want to add dvmGetInlineOpsTableLength() here? Won't
// help us if somebody replaces an existing entry, but it'd catch
// additions/removals.
u1* ptr = buf + 4*4;
for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
const char* cacheFileName =
dvmPathToAbsolutePortion(getCacheFileName(cpe));
assert(cacheFileName != NULL); /* guaranteed by Class.c */
const u1* signature = getSignature(cpe);
int len = strlen(cacheFileName) +1;
if (ptr + 4 + len + kSHA1DigestLen > buf + bufLen) {
ALOGE("DexOpt: overran buffer");
dvmAbort();
}
set4LE(ptr, len);
ptr += 4;
memcpy(ptr, cacheFileName, len);
ptr += len;
memcpy(ptr, signature, kSHA1DigestLen);
ptr += kSHA1DigestLen;
}
assert(ptr == buf + bufLen);
result = sysWriteFully(fd, buf, bufLen, "DexOpt dep info");
free(buf);
return result;
}
四 DEX文件的验证和优化工具dexopt的工作过程
Dalvik虚拟机在加载一个DEX文件时,通过指定的验证和优化选项来调用dexopt工具来进行相应的验证和优化。
处理流程如下所示:
- classes.dex的处理函数是extractAndProcessZip(),extractAndProcessZip()函数会陆续