Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 )相关的知识,希望对你有一定的参考价值。
前言
上一篇博客 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 ) 中 , DexPrepare.cpp 中的 dvmOptimizeDexFile() 方法是用于优化 dex 文件的 , 其中调用了 /bin/dexopt 可执行程序优化 dex 文件 ; 在 /dalvik/dexopt/OptMain.cpp 源码中的 main 函数的 dex 优化分支中 , 调用了 fromDex() 函数 , 在该函数中 , 又调用了 DexPrepare.cpp 中的 dvmContinueOptimizati() 方法 , 执行真正的 dex 优化操作 ;
一、DexPrepare.cpp 中 dvmContinueOptimizati() 方法分析
先判断 DEX 文件是否合法 , 如果文件的长度比 DEX 文件头长度还小 , 这个 DEX 文件肯定不合法 , 直接返回 ;
/* 快速测试,这样我们就不会在空文件上报错 */
if (dexLength < (int) sizeof(DexHeader))
ALOGE("too small to be DEX");
return false;
调用 mmap()
函数对当前 dex 文件内容进行映射 ;
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
调用 rewriteDex()
方法 , 重写 dex 文件 , 其中 第一个参数 ((u1*) mapAddr) + dexOffset
是映射到内存中的起始地址 , 第二个参数 dexLength
是 dex 文件的长度 ;
/*
* 重写文件。字节重新排序,结构重新排列,
* 类验证和字节码优化都被执行
* 在这里。
*
* 从理论上讲,文件可能会改变大小,位可能会四处移动。
* 在实践中,这将是烦人的处理,所以文件
* 布局的设计使其始终可以就地重写。
*
* 这将创建类查找表作为处理的一部分。
*
* 第一个参数 ((u1*) mapAddr) + dexOffset 是映射到
* 内存中的起始地址
*
* 第二个参数 dexLength 是 dex 文件的长度
*/
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
doVerify, doOpt, &pClassLookup, NULL);
DexPrepare.cpp 中 dvmContinueOptimizati() 方法源码 :
/*
* 进行实际的优化。这是在dexopt进程中执行的。
*
* 为了更好地利用磁盘/内存,我们希望提取一次并执行
* 优化到位。如果文件必须展开或收缩
* 为了匹配本地结构填充/对齐预期,我们需要
* 将重写作为提取的一部分,而不是提取
* 放入临时文件并将其恢复。(b)结构调整
* 当前对所有平台都是正确的,但这并不是预期的
* 更改,因此我们应该可以将其提取出来。)
*
* 成功时返回“true”。
*/
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
DexClassLookup* pClassLookup = NULL;
RegisterMapBuilder* pRegMapBuilder = NULL;
assert(gDvm.optimizing);
ALOGV("Continuing optimization (%s, isb=%d)", fileName, isBootstrap);
assert(dexOffset >= 0);
/*
快速测试,这样我们就不会在空文件上报错 ,
先判断 DEX 文件是否合法 , 如果文件的长度比 DEX 文件头长度还小 ,
这个 DEX 文件肯定不合法 , 直接返回 ;
*/
if (dexLength < (int) sizeof(DexHeader))
ALOGE("too small to be DEX");
return false;
if (dexOffset < (int) sizeof(DexOptHeader))
ALOGE("not enough room for opt header");
return false;
bool result = false;
/*
* 把这个放到一个全球数据库里,这样我们就不必到处传递了。我们可以
* 还向DexFile添加一个字段,但因为它只属于DEX
* 可能没有意义的创造。
*/
gDvm.optimizingBootstrapClass = isBootstrap;
/*
* 映射整个文件(这样我们就不必担心页面
* 对齐)。期望输出文件包含
* 我们的DEX数据加上一个小标题的空间。
*/
bool success;
void* mapAddr;
/* 调用 mmap 函数对当前 dex 文件内容进行映射 */
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if (mapAddr == MAP_FAILED)
ALOGE("unable to mmap DEX cache: %s", strerror(errno));
goto bail;
bool doVerify, doOpt;
if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
doVerify = false;
else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
doVerify = !gDvm.optimizingBootstrapClass;
else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/
doVerify = true;
if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE)
doOpt = false;
else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||
gDvm.dexOptMode == OPTIMIZE_MODE_FULL)
doOpt = doVerify;
else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/
doOpt = true;
/*
* 重写文件。字节重新排序,结构重新排列,
* 类验证和字节码优化都被执行
* 在这里。
*
* 从理论上讲,文件可能会改变大小,位可能会四处移动。
* 在实践中,这将是烦人的处理,所以文件
* 布局的设计使其始终可以就地重写。
*
* 这将创建类查找表作为处理的一部分。
*
* 第一个参数 ((u1*) mapAddr) + dexOffset 是映射到
* 内存中的起始地址
*
* 第二个参数 dexLength 是 dex 文件的长度
*/
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
doVerify, doOpt, &pClassLookup, NULL);
if (success)
DvmDex* pDvmDex = NULL;
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0)
ALOGE("Unable to create DexFile");
success = false;
else
/*
* 如果配置为这样做,则生成寄存器映射输出
* 对于所有已验证的类。登记册地图是
* 在验证期间生成,现在将序列化。
*/
if (gDvm.generateRegisterMaps)
pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
if (pRegMapBuilder == NULL)
ALOGE("Failed generating register maps");
success = false;
DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
updateChecksum(dexAddr, dexLength, pHeader);
dvmDexFileFree(pDvmDex);
/* 取消映射读写版本,强制写入磁盘 */
if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0)
ALOGW("msync failed: %s", strerror(errno));
// weird, but keep going
#if 1
/*
* 这会导致clean shutdown失败,因为我们已经加载了类
* 这一点很重要。对于优化器来说,这不是问题,
* 因为简单地退出流程更有效。
* 为valgrind执行清洁关机时排除此代码。
*/
if (munmap(mapAddr, dexOffset + dexLength) != 0)
ALOGE("munmap failed: %s", strerror(errno));
goto bail;
#endif
if (!success)
goto bail;
/* 获取起始偏移量,并调整deps start以进行64位对齐 */
off_t depsOffset, optOffset, endOffset, adjOffset;
int depsLength, optLength;
u4 optChecksum;
depsOffset = lseek(fd, 0, SEEK_END);
if (depsOffset < 0)
ALOGE("lseek to EOF failed: %s", strerror(errno));
goto bail;
adjOffset = (depsOffset + 7) & ~(0x07);
if (adjOffset != depsOffset)
ALOGV("Adjusting deps start from %d to %d",
(int) depsOffset, (int) adjOffset);
depsOffset = adjOffset;
lseek(fd, depsOffset, SEEK_SET);
/*
* 附加依赖项列表。
*/
if (writeDependencies(fd, modWhen, crc) != 0)
ALOGW("Failed writing dependencies");
goto bail;
/* 计算deps长度,然后调整64位对齐的opt start */
optOffset = lseek(fd, 0, SEEK_END);
depsLength = optOffset - depsOffset;
adjOffset = (optOffset + 7) & ~(0x07);
if (adjOffset != optOffset)
ALOGV("Adjusting opt start from %d to %d",
(int) optOffset, (int) adjOffset);
optOffset = adjOffset;
lseek(fd, optOffset, SEEK_SET);
/*
* 附加任何优化的预计算数据结构。
*/
if (!writeOptData(fd, pClassLookup, pRegMapBuilder))
ALOGW("Failed writing opt data");
goto bail;
endOffset = lseek(fd, 0, SEEK_END);
optLength = endOffset - optOffset;
/* compute checksum from start of deps to end of opt area */
if (!computeFileChecksum(fd, depsOffset,
(optOffset+optLength) - depsOffset, &optChecksum))
goto bail;
/*
* 输出“opt”标题,并填写所有值和正确的
* 神奇的数字。
*/
DexOptHeader optHdr;
memset(&optHdr, 0xff, sizeof(optHdr));
memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
optHdr.dexOffset = (u4) dexOffset;
optHdr.dexLength = (u4) dexLength;
optHdr.depsOffset = (u4) depsOffset;
optHdr.depsLength = (u4) depsLength;
optHdr.optOffset = (u4) optOffset;
optHdr.optLength = (u4) optLength;
#if __BYTE_ORDER != __LITTLE_ENDIAN
optHdr.flags = DEX_OPT_FLAG_BIG;
#else
optHdr.flags = 0;
#endif
optHdr.checksum = optChecksum;
fsync(fd); /* ensure previous writes go before header is written */
lseek(fd, 0, SEEK_SET);
if (sysWriteFully(fd, &optHdr, sizeof(optHdr), "DexOpt opt header") != 0)
goto bail;
ALOGV("Successfully wrote DEX header");
result = true;
//dvmRegisterMapDumpStats();
bail:
dvmFreeRegisterMapBuilder(pRegMapBuilder);
free(pClassLookup);
return result;
以上是关于Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 )的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段
Android 逆向整体加固脱壳 ( 脱壳点简介 | 修改系统源码进行脱壳 )
Android 逆向整体加固脱壳 ( 脱壳起点 : 整体加固脱壳 | Dalvik 脱壳机制 : 利用 DexClassLoader 加载过程进行脱壳 | 相关源码分析 )
Android 逆向脱壳解决方案 ( DEX 整体加壳 | 函数抽取加壳 | VMP 加壳 | Dex2C 加壳 | Android 应用加固防护级别 )
Android 逆向整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )(代
Android 逆向整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 构造函数分析 | makeDexElements 函数分析 )