zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

【Android 逆向】整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmContinueOptimizati() 函数分析 )

Android流程 函数 优化 分析 逆向 整体 加固
2023-06-13 09:18:00 时间

文章目录

前言


上一篇博客 【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;
}

源码路径 : /dalvik/vm/analysis/DexPrepare.cpp