【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )

2023-03-30 09:27:22 浏览数 (1)

文章目录

  • 前言
  • 一、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 文件的字节长度 ;

代码语言:javascript复制
static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
    DexClassLookup** ppClassLookup, DvmDex** ppDvmDex)

dvmDexFileOpenPartial 函数是 脱壳点 函数 , 通过该函数定位脱壳点 , 然后进行脱壳操作 ;

代码语言:javascript复制
	/*
	 * 既然可以直接读取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

0 人点赞