文章目录
前言
一、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() 方法源码 :
/* * 对内存映射的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() 方法源码 :
/* * 为“部分”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() 方法分析 ( 脱壳点 )
/* * 解析优化或未优化的。存储在内存中的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 (0x%02x %02x %02x %02x)", magic[4], magic[5], magic[6], magic[7]); goto bail; } pDexFile->pOptHeader = (const DexOptHeader*) data; ALOGV("Good opt header, DEX offset is %d, flags=0x%02x", 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 (%08x vs %08x)", adler, pHeader->checksum); if (!(flags & kDexParseContinueOnError)) goto bail; } else { ALOGV("+++ adler32 checksum (%08x) verified", adler); } const DexOptHeader* pOptHeader = pDexFile->pOptHeader; if (pOptHeader != NULL) { adler = dexComputeOptChecksum(pOptHeader); if (adler != pOptHeader->checksum) { ALOGE("ERROR: bad opt checksum (%08x vs %08x)", adler, pOptHeader->checksum); if (!(flags & kDexParseContinueOnError)) goto bail; } else { ALOGV("+++ adler32 opt checksum (%08x) 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; }