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

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )(代码片相关的知识,希望对你有一定的参考价值。

前言


上一篇博客 【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 ) 中 , 分析了 DexPrepare.cppdvmContinueOptimizati() 方法 , 在其中调用了 rewriteDex() 方法 , 重写 DEX 文件 ;

本篇博客继续分析 DexPrepare.cpprewriteDex() 方法 ;





一、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;

源码路径 : /dalvik/libdex/DexFile.cpp

以上是关于Android 逆向整体加固脱壳 ( DEX 优化流程分析 | dvmDexFileOpenPartial | dexFileParse | 脱壳点 | 获取 dex 文件在内存中的首地址 )(代码片的主要内容,如果未能解决你的问题,请参考以下文章

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 函数分析 )