文章目录
- 前言
- 一、DexPrepare.cpp 中 rewriteDex() 方法分析
- 二、DvmDex.cpp 中 dvmDexFileOpenPartial() 方法分析 ( 脱壳点 )
- 三、DexFile.cpp 中 dexFileParse() 方法分析 ( 脱壳点 )
前言
上一篇博客 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 ) 中 , 分析了 DexPrepare.cpp 中 dvmContinueOptimizati() 方法 , 在其中调用了 rewriteDex() 方法 , 重写 DEX 文件 ;
本篇博客继续分析 DexPrepare.cpp 中 rewriteDex() 方法 ;
一、DexPrepare.cpp 中 rewriteDex() 方法分析
第一个参数 u1* addr
是加载到内存中的 dex 文件的首地址 ;
第二个参数 int len
是内存中的 dex 文件的字节长度 ;
static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
DexClassLookup** ppClassLookup, DvmDex** ppDvmDex)
dvmDexFileOpenPartial
函数是 脱壳点 函数 , 通过该函数定位脱壳点 , 然后进行脱壳操作 ;
/*
* 既然可以直接读取DEX文件,那么创建一个DexFile结构
* 为了它。
*/
if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
ALOGE("Unable to create DexFile");
goto bail;
}
DexPrepare.cpp 中 rewriteDex() 方法源码 :
代码语言:javascript复制/*
* 对内存映射的DEX文件执行就地重写。
*
* 如果这是从短期子进程(dexopt)调用的,我们可以
* 疯狂地加载类和分配内存。当天气好的时候
* 调用以准备字节数组中提供的类,我们可能需要
* 要保守一点。
*
* 如果“ppClassLookup”为非空,则为指向新分配的
* DexClassLookup将在成功时返回。
*
* 如果“ppDvmDex”为非空,则将创建新分配的DvmDex结构
* 成功返回。
*/
static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
DexClassLookup** ppClassLookup, DvmDex** ppDvmDex)
{
DexClassLookup* pClassLookup = NULL;
u8 prepWhen, loadWhen, verifyOptWhen;
DvmDex* pDvmDex = NULL;
bool result = false;
const char* msgStr = "???";
/* 如果索引的字节顺序错误,请立即交换它 */
if (dexSwapAndVerify(addr, len) != 0)
goto bail;
/*
* 既然可以直接读取DEX文件,那么创建一个DexFile结构
* 为了它。
*/
if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
ALOGE("Unable to create DexFile");
goto bail;
}
/*
* 创建类查找表。这最终将被追加
* 直到最后。奥德克斯。
*
* 我们从DexFile创建一个临时链接,以便
* 类加载,如下所示。
*/
pClassLookup = dexCreateClassLookup(pDvmDex->pDexFile);
if (pClassLookup == NULL)
goto bail;
pDvmDex->pDexFile->pClassLookup = pClassLookup;
/*
* 如果我们不打算验证或优化这些类,
* 加载它们没有任何价值,所以尽早退出。
*/
if (!doVerify && !doOpt) {
result = true;
goto bail;
}
prepWhen = dvmGetRelativeTimeUsec();
/*
* 加载在此DEX文件中找到的所有类。如果它们无法加载
* 由于某些原因,它们不会得到验证(这是应该的)。
*/
if (!loadAllClasses(pDvmDex))
goto bail;
loadWhen = dvmGetRelativeTimeUsec();
/*
* 创建字节码优化器使用的数据结构。
* 我们需要在几个类中查找方法,因此这可能会导致
* 一点类加载。我们通常在VM初始化期间执行此操作,但是
* 对于dexopt on core。jar操作的顺序变得有点棘手,
* 所以我们把它推迟到这里。
*/
if (!dvmCreateInlineSubsTable())
goto bail;
/*
* 验证并优化DEX文件(命令行)中的所有类
* (如果允许的话)。
*
* 这是最大的努力,所以dexopt真的没有办法
* 在这一点上失败。
*/
verifyAndOptimizeClasses(pDvmDex->pDexFile, doVerify, doOpt);
verifyOptWhen = dvmGetRelativeTimeUsec();
if (doVerify && doOpt)
msgStr = "verify opt";
else if (doVerify)
msgStr = "verify";
else if (doOpt)
msgStr = "opt";
ALOGD("DexOpt: load %dms, %s %dms, %d bytes",
(int) (loadWhen - prepWhen) / 1000,
msgStr,
(int) (verifyOptWhen - loadWhen) / 1000,
gDvm.pBootLoaderAlloc->curOffset);
result = true;
bail:
/*
* 成功后,归还来电者要求的物品。
*/
if (pDvmDex != NULL) {
/* break link between the two */
pDvmDex->pDexFile->pClassLookup = NULL;
}
if (ppDvmDex == NULL || !result) {
dvmDexFileFree(pDvmDex);
} else {
*ppDvmDex = pDvmDex;
}
if (ppClassLookup == NULL || !result) {
free(pClassLookup);
} else {
*ppClassLookup = pClassLookup;
}
return result;
}
源码路径 : /dalvik/vm/analysis/DexPrepare.cpp
二、DvmDex.cpp 中 dvmDexFileOpenPartial() 方法分析 ( 脱壳点 )
该函数中的 参数 const void* addr
是 dex 文件在内存中的起始地址 ;
在调用的 dexFileParse
函数中 , 也可以获取到 dex 文件在内存中的首地址 ;
DvmDex.cpp 中 dvmDexFileOpenPartial() 方法源码 :
代码语言:javascript复制/*
* 为“部分”DEX创建DexFile结构。这是一个在
* 被优化的过程。优化标头未完成
* 我们没有任何辅助数据表,所以我们必须这样做
* 初始化过程略有不同。
*
* 错误时返回非零。
*/
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
int parseFlags = kDexParseDefault;
int result = -1;
/* -- 文件不完整,尚未计算新校验和
if (gDvm.verifyDexChecksum)
parseFlags |= kDexParseVerifyChecksum;
*/
pDexFile = dexFileParse((u1*)addr, len, parseFlags);
if (pDexFile == NULL) {
ALOGE("DEX parse failed");
goto bail;
}
pDvmDex = allocateAuxStructures(pDexFile);
if (pDvmDex == NULL) {
dexFileFree(pDexFile);
goto bail;
}
pDvmDex->isMappedReadOnly = false;
*ppDvmDex = pDvmDex;
result = 0;
bail:
return result;
}
源码路径 : /dalvik/vm/DvmDex.cpp
三、DexFile.cpp 中 dexFileParse() 方法分析 ( 脱壳点 )
代码语言:javascript复制/*
* 解析优化或未优化的。存储在内存中的dex文件。这是
* 在字节排序和结构对齐修复后调用。
*
* 成功后,返回新分配的文件。
*/
DexFile* dexFileParse(const u1* data, size_t length, int flags)
{
DexFile* pDexFile = NULL;
const DexHeader* pHeader;
const u1* magic;
int result = -1;
if (length < sizeof(DexHeader)) {
ALOGE("too short to be a valid .dex");
goto bail; /* bad file format */
}
pDexFile = (DexFile*) malloc(sizeof(DexFile));
if (pDexFile == NULL)
goto bail; /* alloc failure */
memset(pDexFile, 0, sizeof(DexFile));
/*
* Peel off the optimized header.
*/
if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {
magic = data;
if (memcmp(magic 4, DEX_OPT_MAGIC_VERS, 4) != 0) {
ALOGE("bad opt version (0xx x x x)",
magic[4], magic[5], magic[6], magic[7]);
goto bail;
}
pDexFile->pOptHeader = (const DexOptHeader*) data;
ALOGV("Good opt header, DEX offset is %d, flags=0xx",
pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags);
/* parse the optimized dex file tables */
if (!dexParseOptData(data, length, pDexFile))
goto bail;
/* ignore the opt header and appended data from here on out */
data = pDexFile->pOptHeader->dexOffset;
length -= pDexFile->pOptHeader->dexOffset;
if (pDexFile->pOptHeader->dexLength > length) {
ALOGE("File truncated? stored len=%d, rem len=%d",
pDexFile->pOptHeader->dexLength, (int) length);
goto bail;
}
length = pDexFile->pOptHeader->dexLength;
}
dexFileSetupBasicPointers(pDexFile, data);
pHeader = pDexFile->pHeader;
if (!dexHasValidMagic(pHeader)) {
goto bail;
}
/*
* 验证校验和。这相当快,但确实需要
* 触摸DEX文件中的每个字节。基本校验和在
* 字节交换和索引优化。
*/
if (flags & kDexParseVerifyChecksum) {
u4 adler = dexComputeChecksum(pHeader);
if (adler != pHeader->checksum) {
ALOGE("ERROR: bad checksum (x vs x)",
adler, pHeader->checksum);
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV(" adler32 checksum (x) verified", adler);
}
const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
if (pOptHeader != NULL) {
adler = dexComputeOptChecksum(pOptHeader);
if (adler != pOptHeader->checksum) {
ALOGE("ERROR: bad opt checksum (x vs x)",
adler, pOptHeader->checksum);
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV(" adler32 opt checksum (x) verified", adler);
}
}
}
/*
* 验证SHA-1摘要。(通常我们不想这样做--
* 摘要用于唯一标识原始DEX文件,以及
* 无法在索引被字节交换后计算以进行验证
* (并进行了优化。)
*/
if (kVerifySignature) {
unsigned char sha1Digest[kSHA1DigestLen];
const int nonSum = sizeof(pHeader->magic) sizeof(pHeader->checksum)
kSHA1DigestLen;
dexComputeSHA1Digest(data nonSum, length - nonSum, sha1Digest);
if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) {
char tmpBuf1[kSHA1DigestOutputLen];
char tmpBuf2[kSHA1DigestOutputLen];
ALOGE("ERROR: bad SHA1 digest (%s vs %s)",
dexSHA1DigestToStr(sha1Digest, tmpBuf1),
dexSHA1DigestToStr(pHeader->signature, tmpBuf2));
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV(" sha1 digest verified");
}
}
if (pHeader->fileSize != length) {
ALOGE("ERROR: stored file size (%d) != expected (%d)",
(int) pHeader->fileSize, (int) length);
if (!(flags & kDexParseContinueOnError))
goto bail;
}
if (pHeader->classDefsSize == 0) {
ALOGE("ERROR: DEX file has no classes in it, failing");
goto bail;
}
/*
* Success!
*/
result = 0;
bail:
if (result != 0 && pDexFile != NULL) {
dexFileFree(pDexFile);
pDexFile = NULL;
}
return pDexFile;
}
源码路径 : /dalvik/libdex/DexFile.cpp