Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段相关的知识,希望对你有一定的参考价值。

前言


上一篇博客 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 ) 中 , 在 RawDexFile.cpp 中的 dvmRawDexFileOpen() 方法中 , 调用了 DexPrepare.cppdvmOptimizeDexFile() 函数 , 对 DEX 文件进行了优化 ;





一、DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析



dvmOptimizeDexFile 函数的参数说明 : int fd 是打开的 dex 文件标识符 , long dexLength 是打开的 dex 文件大小 ;

在该函数中 , 调用 /bin/dexopt 程序 , 优化 dex 文件 , 最终产生 odex 文件 ;


/*
 * 给定包含DEX数据的文件的描述符,生成
 * 优化版本。
 * 
 * “fd”指向的文件应为锁定的共享资源
 * (或私人);我们不努力实施多进程正确性
 * 在这里。
 * 
 * “文件名”仅用于调试输出。存储“modWhen”和“crc”
 * 在依赖项集中。
 * 
 * “isBootstrap”标志确定优化器和验证器如何处理
 * 包范围访问检查。优化时,我们只加载引导
 * 类DEX文件和目标DEX,因此该标志确定
 * 给目标DEX类一个(合成的)非空类加载器指针。
 * 只有当目标DEX包含声明
 * 与引导类位于同一个包中。
 * 
 * 优化器需要加载目标DEX文件中的每个类。
 * 这通常是不可取的,因此我们启动一个子流程来执行
 * 工作并等待它完成。
 * 
 * 成功时返回“true”。所有数据均已写入“fd”。
 */
bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
    const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)

    const char* lastPart = strrchr(fileName, '/');
    if (lastPart != NULL)
        lastPart++;
    else
        lastPart = fileName;

    ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap);

    pid_t pid;

	/*
	 * 如果我们的bootclasspath中出现了我们认为
	 * 都优化了,被拒绝了。
	 */
    if (gDvm.optimizing) 
        ALOGW("Rejecting recursive optimization attempt on '%s'", fileName);
        return false;
    

    pid = fork();
    if (pid == 0) 
        static const int kUseValgrind = 0;
        // 调用 /bin/dexopt 程序 , 优化 dex 文件 , 最终产生 odex 文件
        static const char* kDexOptBin = "/bin/dexopt";
        static const char* kValgrinder = "/usr/bin/valgrind";
        static const int kFixedArgCount = 10;
        static const int kValgrindArgCount = 5;
        static const int kMaxIntLen = 12;   // '-'+10dig+'\\0' -OR- 0x+8dig
        int bcpSize = dvmGetBootPathSize();
        int argc = kFixedArgCount + bcpSize
            + (kValgrindArgCount * kUseValgrind);
        const char* argv[argc+1];             // last entry is NULL
        char values[argc][kMaxIntLen];
        char* execFile;
        const char* androidRoot;
        int flags;

        /* change process groups, so we don't clash with ProcessManager */
        setpgid(0, 0);

        /* full path to optimizer */
        androidRoot = getenv("ANDROID_ROOT");
        if (androidRoot == NULL) 
            ALOGW("ANDROID_ROOT not set, defaulting to /system");
            androidRoot = "/system";
        
        execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1);
        strcpy(execFile, androidRoot);
        strcat(execFile, kDexOptBin);

        /*
         * Create arg vector.
         */
        int curArg = 0;

        if (kUseValgrind) 
            /* probably shouldn't ship the hard-coded path */
            argv[curArg++] = (char*)kValgrinder;
            argv[curArg++] = "--tool=memcheck";
            argv[curArg++] = "--leak-check=yes";        // check for leaks too
            argv[curArg++] = "--leak-resolution=med";   // increase from 2 to 4
            argv[curArg++] = "--num-callers=16";        // default is 12
            assert(curArg == kValgrindArgCount);
        
        argv[curArg++] = execFile;

        argv[curArg++] = "--dex";

        sprintf(values[2], "%d", DALVIK_VM_BUILD);
        argv[curArg++] = values[2];

        sprintf(values[3], "%d", fd);
        argv[curArg++] = values[3];

        sprintf(values[4], "%d", (int) dexOffset);
        argv[curArg++] = values[4];

        sprintf(values[5], "%d", (int) dexLength);
        argv[curArg++] = values[5];

        argv[curArg++] = (char*)fileName;

        sprintf(values[7], "%d", (int) modWhen);
        argv[curArg++] = values[7];

        sprintf(values[8], "%d", (int) crc);
        argv[curArg++] = values[8];

        flags = 0;
        if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) 
            flags |= DEXOPT_OPT_ENABLED;
            if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)
                flags |= DEXOPT_OPT_ALL;
        
        if (gDvm.classVerifyMode != VERIFY_MODE_NONE) 
            flags |= DEXOPT_VERIFY_ENABLED;
            if (gDvm.classVerifyMode == VERIFY_MODE_ALL)
                flags |= DEXOPT_VERIFY_ALL;
        
        if (isBootstrap)
            flags |= DEXOPT_IS_BOOTSTRAP;
        if (gDvm.generateRegisterMaps)
            flags |= DEXOPT_GEN_REGISTER_MAPS;
        sprintf(values[9], "%d", flags);
        argv[curArg++] = values[9];

        assert(((!kUseValgrind && curArg == kFixedArgCount) ||
               ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));

        ClassPathEntry* cpe;
        for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) 
            argv[curArg++] = cpe->fileName;
        
        assert(curArg == argc);

        argv[curArg] = NULL;

        if (kUseValgrind)
            execv(kValgrinder, const_cast<char**>(argv));
        else
            execv(execFile, const_cast<char**>(argv));

        ALOGE("execv '%s'%s failed: %s", execFile,
            kUseValgrind ? " [valgrind]" : "", strerror(errno));
        exit(1);
     else 
        ALOGV("DexOpt: waiting for verify+opt, pid=%d", (int) pid);
        int status;
        pid_t gotPid;

		/*
		 * 等待优化过程完成。我们进入VMI等待
		 * 模式,这样GC暂停就不必等待我们了。
		 */
        ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
        while (true) 
            gotPid = waitpid(pid, &status, 0);
            if (gotPid == -1 && errno == EINTR) 
                ALOGD("waitpid interrupted, retrying");
             else 
                break;
            
        
        dvmChangeStatus(NULL, oldStatus);
        if (gotPid != pid) 
            ALOGE("waitpid failed: wanted %d, got %d: %s",
                (int) pid, (int) gotPid, strerror(errno));
            return false;
        

        if (WIFEXITED(status) && WEXITSTATUS(status) == 0) 
            ALOGD("DexOpt: --- END '%s' (success) ---", lastPart);
            return true;
         else 
            ALOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed",
                lastPart, status);
            return false;
        
    





二、/bin/dexopt 源码分析



dex 文件优化 , 主要是调用 /bin/dexopt 程序 , 最终产生 odex 文件 ;

其源码路径是 /dalvik/dexopt/ 路径 ,

该 OptMain.cpp 源码是一个有 main 函数 , 可以独立执行的 C++ 程序 , 可以在 Android 命令中执行 ;

加载 dex 文件时 , 执行 fromDex 函数 ;

return fromDex(argc, argv);

在 fromfromDex 函数中 , 先准备优化环境 ,

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) 
        ALOGE("VM init failed");
        goto bail;
    

然后进行正式优化 ;

    /* do the optimization */
    if (!dvmContinueOptimization(fd, offset, length, debugFileName,
            modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
    
        ALOGE("Optimization failed");
        goto bail;
    

真正的优化操作 , 在 dvmContinueOptimization 函数中执行的 ;


核心源码如下 : 源码路径 /dalvik/dexopt/OptMain.cpp

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * 命令行DEX优化和验证入口点。
 * 
 * 有三种方法可以启动此功能:
 * (1)来自虚拟机。这需要十几个参数,其中一个是文件
 * 同时作为输入和输出的描述符。这使我们能够
 * 仍然不知道DEX数据最初来自何处。
 * (2)来自installd或其他本机应用程序。传入文件
 * 用于zip文件的描述符、用于输出的文件描述符,以及
 * 调试消息的文件名。关于这一点,人们做了许多假设
 * 发生了什么(验证+优化已启用,启动
 * 类路径位于BOOTCLASSPATH中,等等)。
 * (3)在构建过程中在主机上进行预优化。这种行为
 * 与(2)几乎相同,只是它采用文件名而不是
 * 文件描述符。
 * 
 * bootclasspath条目存在一些脆弱的方面,原因如下
 * 很大程度上是由于虚拟机在它认为需要的时候进行工作的历史
 * 而不是严格按照要求去做。如果优化引导类路径
 * 条目,始终按照它们在路径中出现的顺序执行。
 */
#include "Dalvik.h"
#include "libdex/OptInvocation.h"

#include "cutils/log.h"
#include "cutils/process_name.h"

#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static const char* kClassesDex = "classes.dex";


/*
*将zipFd中的“classes.dex”提取到“cacheFd”中,留下一点空间
*用于DEX优化收割台的前端。
*/
static int extractAndProcessZip(int zipFd, int cacheFd,
    const char* debugFileName, bool isBootstrap, const char* bootClassPath,
    const char* dexoptFlagStr)

    ZipArchive zippy;
    ZipEntry zipEntry;
    size_t uncompLen;
    long modWhen, crc32;
    off_t dexOffset;
    int err;
    int result = -1;
    int dexoptFlags = 0;        /* bit flags, from enum DexoptFlags */
    DexClassVerifyMode verifyMode = VERIFY_MODE_ALL;
    DexOptimizerMode dexOptMode = OPTIMIZE_MODE_VERIFIED;

    memset(&zippy, 0, sizeof(zippy));

    /* make sure we're still at the start of an empty file */
    if (lseek(cacheFd, 0, SEEK_END) != 0) 
        ALOGE("DexOptZ: new cache file '%s' is not empty", debugFileName);
        goto bail;
    

	/*
	*编写骨架索引优化标头。我们要上课。指数
	*紧跟其后。
	*/
    err = dexOptCreateEmptyHeader(cacheFd);
    if (err != 0)
        goto bail;

    /* record the file position so we can get back here later */
    dexOffset = lseek(cacheFd, 0, SEEK_CUR);
    if (dexOffset < 0)
        goto bail;

	/*
	*打开zip存档,找到DEX条目。
	*/
    if (dexZipPrepArchive(zipFd, debugFileName, &zippy) != 0) 
        ALOGW("DexOptZ: unable to open zip archive '%s'", debugFileName);
        goto bail;
    

    zipEntry = dexZipFindEntry(&zippy, kClassesDex);
    if (zipEntry == NULL) 
        ALOGW("DexOptZ: zip archive '%s' does not include %s",
            debugFileName, kClassesDex);
        goto bail;
    

	/*
	*提取一些关于zip条目的信息。
	*/
    if (dexZipGetEntryInfo(&zippy, zipEntry, NULL, &uncompLen, NULL, NULL,
            &modWhen, &crc32) != 0)
    
        ALOGW("DexOptZ: zip archive GetEntryInfo failed on %s", debugFileName);
        goto bail;
    

    uncompLen = uncompLen;
    modWhen = modWhen;
    crc32 = crc32;

	/*
	*以当前偏移量将DEX数据提取到缓存文件中。
	*/
    if (dexZipExtractEntryToFile(&zippy, zipEntry, cacheFd) != 0) 
        ALOGW("DexOptZ: extraction of %s from %s failed",
            kClassesDex, debugFileName);
        goto bail;
    

    /* Parse the options. */
    if (dexoptFlagStr[0] != '\\0') 
        const char* opc;
        const char* val;

        opc = strstr(dexoptFlagStr, "v=");      /* verification */
        if (opc != NULL) 
            switch (*(opc+2)) 
            case 'n':   verifyMode = VERIFY_MODE_NONE;          break;
            case 'r':   verifyMode = VERIFY_MODE_REMOTE;        break;
            case 'a':   verifyMode = VERIFY_MODE_ALL;           break;
            default:                                            break;
            
        

        opc = strstr(dexoptFlagStr, "o=");      /* optimization */
        if (opc != NULL) 
            switch (*(opc+2)) 
            case 'n':   dexOptMode = OPTIMIZE_MODE_NONE;        break;
            case 'v':   dexOptMode = OPTIMIZE_MODE_VERIFIED;    break;
            case 'a':   dexOptMode = OPTIMIZE_MODE_ALL;         break;
            case 'f':   dexOptMode = OPTIMIZE_MODE_FULL;        break;
            default:                                            break;
            
        

        opc = strstr(dexoptFlagStr, "m=y");     /* register map */
        if (opc != NULL) 
            dexoptFlags |= DEXOPT_GEN_REGISTER_MAPS;
        

        opc = strstr(dexoptFlagStr, "u=");      /* uniprocessor target */
        if (opc != NULL) 
            switch (*(opc+2)) 
            case 'y':   dexoptFlags |= DEXOPT_UNIPROCESSOR;     break;
            case 'n':   dexoptFlags |= DEXOPT_SMP;              break;
            default:                                            break;
            
        
    

	/*
	*准备VM并执行优化。
	*/

    if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode,
            dexoptFlags) != 0)
    
        ALOGE("DexOptZ: VM init failed");
        goto bail;
    

    //vmStarted = 1;

    /* do the optimization */
    if (!dvmContinueOptimization(cacheFd, dexOffset, uncompLen, debugFileName,
            modWhen, crc32, isBootstrap))
    
        ALOGE("Optimization failed");
        goto bail;
    

    /* we don't shut the VM down -- process is about to exit */

    result = 0;

bail:
    dexZipCloseArchive(&zippy);
    return result;


/*
*普通设备端处理的通用功能以及
*预优化。
*/
static int processZipFile(int zipFd, int cacheFd, const char* zipName,
        const char *dexoptFlags)

    char* bcpCopy = NULL;

    /*
     * Check to see if this is a bootstrap class entry. If so, truncate
     * the path.
     */
    const以上是关于Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段的主要内容,如果未能解决你的问题,请参考以下文章

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