zoukankan      html  css  js  c++  java
  • ART模式下基于dex2oat脱壳的原理分析

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78513483


    一般情况下,Android Dex文件在加载到内存之前需要先对dex文件进行优化处理(如果Android Dex文件已经优化过,则不需要进行优化处理操作,后面进行加载到内存即可),在Dalvik虚拟机模式下,Android dex文件经过dex2oat进程优化为odex文件,在ART虚拟机模式下,Android dex文件经过dex2oat进程优化为oat文件-OAT文件为一种私有的ELF格式文件,和一般的ELF文件稍有一些不同。Dalvik虚拟机模式下,Android Dex文件优化为odex文件的处理比较简单,即使Android Dex文件被Android加固所加固处理,只要Android应用运行时能保证dex文件的类方法的数据是完整的就没有问题,Dalvik虚拟机模式下Android dex文件的优化对Android加固的处理影响不是很大;ART虚拟机模式下,Android dex文件的优化处理比价复杂,在Android dex文件优化处理时要保证dex文件类方法的数据完整,因此ART虚拟机模式下的dex文件优化对Android加固还是有比较大的影响,ART虚拟机模式下dex文件的加载也比较复杂,也致使一些Android加固厂商对ART虚拟机模式下dex文件优化的疏忽,最终导致ART虚拟机模式下可以在dex文件优化时进行dex文件的内存dump操作。


    ART模式下基于dex2oat脱壳的文章整理:

    Dex2oatHunter  github:https://github.com/spriteviki/Dex2oatHunter

    360加固成功脱壳

    分享一个360加固脱壳模拟器(2017/07/17更新)

    记一次APP脱壳重打包过程》主要讲360加固dex2oat脱壳后的重打包修复,对于360加固的重打包修复有必要学习研究和实践一下。


    ART虚拟机模式下,Android dex文件的加载 openDexFileNative函数 对应的Native实现函数为Jni函数动态注册的 DexFile_openDexFileNative函数 ,也就是说ART虚拟机模式下Android dex文件的加载最终调用的是DexFile_openDexFileNative函数来实现的。

    static jint DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
    
      // 得到需要加载的dex文件的文件路径
      ScopedUtfChars sourceName(env, javaSourceName);
      if (sourceName.c_str() == NULL) {
        return 0;
      }
    
      // 构建dex文件加载的路径字符串
      std::string dex_location(sourceName.c_str());
      // dex文件优化后的存放路径字符串
      NullableScopedUtfChars outputName(env, javaOutputName);
      if (env->ExceptionCheck()) {
        return 0;
      }
      ScopedObjectAccess soa(env);
    
      // 保存dex文件的Checksum
      uint32_t dex_location_checksum;
      // 获取dex文件的Checksum进行校验检查
      if (!DexFile::GetChecksum(dex_location, &dex_location_checksum)) {
        LOG(WARNING) << "Failed to compute checksum: " << dex_location;
        ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
        soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                       "Unable to get checksum of dex file: %s", dex_location.c_str());
        return 0;
      }
    
      // 获取当前进程的运行时的ClassLinker实例对象
      ClassLinker* linker = Runtime::Current()->GetClassLinker();
    
      // dex文件格式的简单结构体描述?
      const DexFile* dex_file;
      // 判断存放优化后dex文件的文件路径是否为NULL
      if (outputName.c_str() == NULL) {
         // 获取优化后的oat文件并加载到内存,返回dex文件加载到内存后的描述结构体dex_file
        dex_file = linker->FindDexFileInOatFileFromDexLocation(dex_location, dex_location_checksum);
      } else {
    
        // 构建存放优化后的dex文件oat的路径字符串
        std::string oat_location(outputName.c_str());
        // 获取优化后的oat文件并加载到内存,返回dex文件加载到内存后的描述结构体dex_file
        dex_file = linker->FindOrCreateOatFileForDexLocation(dex_location, dex_location_checksum, oat_location);
      }
      if (dex_file == NULL) {
        LOG(WARNING) << "Failed to open dex file: " << dex_location;
        ThrowLocation throw_location = soa.Self()->GetCurrentLocationForThrow();
        soa.Self()->ThrowNewExceptionF(throw_location, "Ljava/io/IOException;",
                                       "Unable to open dex file: %s", dex_location.c_str());
        return 0;
      }
      // 设置函数返回值,返回dex文件加载到内存后的描述结构体DexFile*指针
      return static_cast<jint>(reinterpret_cast<uintptr_t>(dex_file));
    }

    ART虚拟机模式下,Android dex文件的加载有DexFile_openDexFileNative函数来实现,在dex文件被 加载之前,先进行dex文件的校验处理,dex文件的校验通过之后,当没有指定dex文件优化后的文件路径,调用 FindDexFileInOatFileFromDexLocation函数 进行dex文件的优化和加载处理;当指定了dex文件优化后的文件路径则调用 函数FindOrCreateOatFileForDexLocation 进行dex文件的优化和加载处理。FindDexFileInOatFileFromDexLocation函数 和 FindOrCreateOatFileForDexLocation函数 在被加载的dex文件没被优化时,最终都会调用 GenerateOatFile函数创建dex2oat进程执行dex文件的优化处理,将原始的dex文件优化处理为私有ELF文件格式的oat文件。




    FindOrCreateOatFileForDexLocationLocked函数 的实现代码如下,首先创建的新文件用于存放优化的oat文件并保持该文件锁定,然后进行优化的oat文件的查找,如果没有找到dex文件优化后的oat文件,则进行dex文件的优化处理得到oat文件,后面进行oat文件加载到内存的处理,其他的操作暂时不关注。

    const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,
                                                                        uint32_t dex_location_checksum,
                                                                        const std::string& oat_location) {
      // We play a locking game here so that if two different processes
      // race to generate (or worse, one tries to open a partial generated
      // file) we will be okay. This is actually common with apps that use
      // DexClassLoader to work around the dex method reference limit and
      // that have a background service running in a separate process.
      ScopedFlock scoped_flock;
      // 打开或者创建优化后oat文件并保持oat文件锁定
      if (!scoped_flock.Init(oat_location)) {
        LOG(ERROR) << "Failed to open locked oat file: " << oat_location;
        return NULL;
      }
    
      // Check if we already have an up-to-date output file
      // 判断优化后的oat文件是否存在
      const DexFile* dex_file = FindDexFileInOatLocation(dex_location,
                                                         dex_location_checksum,
                                                         oat_location);
      if (dex_file != NULL) {
        // 存在,直接返回优化后的oat文件的描述结构体信息
        return dex_file;
      }
    
      // Generate the output oat file for the dex file
      VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
      // 创建dex2oat进程优化dex文件为oat文件
      if (!GenerateOatFile(dex_location, scoped_flock.GetFile().Fd(), oat_location)) {
        LOG(ERROR) << "Failed to generate oat file: " << oat_location;
        return NULL;
      }
      // 加载oat文件到内存中
      const OatFile* oat_file = OatFile::Open(oat_location, oat_location, NULL,
                                              !Runtime::Current()->IsCompiler());
      if (oat_file == NULL) {
        LOG(ERROR) << "Failed to open generated oat file: " << oat_location;
        return NULL;
      }
      RegisterOatFileLocked(*oat_file);
      const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location, &dex_location_checksum);
      if (oat_dex_file == NULL) {
        LOG(ERROR) << "Failed to find dex file " << dex_location
                   << " (checksum " << dex_location_checksum
                   << ") in generated oat file: " << oat_location;
        return NULL;
      }
      const DexFile* result = oat_dex_file->OpenDexFile();
      CHECK_EQ(dex_location_checksum, result->GetLocationChecksum())
              << "dex_location=" << dex_location << " oat_location=" << oat_location << std::hex
              << " dex_location_checksum=" << dex_location_checksum
              << " DexFile::GetLocationChecksum()=" << result->GetLocationChecksum();
      return result;
    }

    有关ART虚拟机模式下,OAT文件加载到Android进程内存中的详细分析,可以参考老罗的博客《Android运行时ART加载OAT文件的过程分析》。GenerateOatFile函数 的代码实现分析如下。

    // dex_filename为被加载的dex文件的路径字符串
    // oat_fd为dex文件优化的oat文件的文件指针
    // oat_cache_filename为存放优化后dex文件oat的文件路径
    bool ClassLinker::GenerateOatFile(const std::string& dex_filename,
                                      int oat_fd,
                                      const std::string& oat_cache_filename) {
      // 获取Android系统的根路径"/system"
      std::string dex2oat_string(GetAndroidRoot());
      // 拼接字符串得到优化dex文件的程序dex2oat文件的路径字符串
      dex2oat_string += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");
      // 获取到优化dex文件的程序dex2oat文件的路径字符串
      const char* dex2oat = dex2oat_string.c_str();
    
      // 获取到当前进程的ClassPath的路径字符串
      const char* class_path = Runtime::Current()->GetClassPathString().c_str();
    
      // 获取到当前进程的堆Heap指针
      gc::Heap* heap = Runtime::Current()->GetHeap();
      // 得到boot_image的优化字符串参数选项"--boot-image="
      std::string boot_image_option_string("--boot-image=");
      // 设置imageSpace文件的路径字符串
      boot_image_option_string += heap->GetImageSpace()->GetImageFilename();
      const char* boot_image_option = boot_image_option_string.c_str();
    
      std::string dex_file_option_string("--dex-file=");
      // 设置需要优化的dex文件的路径字符串
      dex_file_option_string += dex_filename;
      const char* dex_file_option = dex_file_option_string.c_str();
    
      std::string oat_fd_option_string("--oat-fd=");
      // 设置被优化的oat文件的文件指针
      StringAppendF(&oat_fd_option_string, "%d", oat_fd);
      const char* oat_fd_option = oat_fd_option_string.c_str();
    
      std::string oat_location_option_string("--oat-location=");
      // 设置被优化后的dex文件oat的存放路径
      oat_location_option_string += oat_cache_filename;
      const char* oat_location_option = oat_location_option_string.c_str();
    
      std::string oat_compiler_filter_string("-compiler-filter:");
      // 设置编译选项参数
      switch (Runtime::Current()->GetCompilerFilter()) {
        case Runtime::kInterpretOnly:
          oat_compiler_filter_string += "interpret-only";
          break;
        case Runtime::kSpace:
          oat_compiler_filter_string += "space";
          break;
        case Runtime::kBalanced:
          oat_compiler_filter_string += "balanced";
          break;
        case Runtime::kSpeed:
          oat_compiler_filter_string += "speed";
          break;
        case Runtime::kEverything:
          oat_compiler_filter_string += "everything";
          break;
        default:
          LOG(FATAL) << "Unexpected case.";
      }
      const char* oat_compiler_filter_option = oat_compiler_filter_string.c_str();
    
      // fork and exec dex2oat
      // fork新进程对dex文件进行优化处理
      pid_t pid = fork();
      if (pid == 0) {
        // no allocation allowed between fork and exec
    
        // change process groups, so we don't get reaped by ProcessManager
        setpgid(0, 0);
    
        // gLogVerbosity.class_linker = true;
        VLOG(class_linker) << dex2oat
                           << " --runtime-arg -Xms64m"
                           << " --runtime-arg -Xmx64m"
                           << " --runtime-arg -classpath"
                           << " --runtime-arg " << class_path
                           << " --runtime-arg " << oat_compiler_filter_option
    #if !defined(ART_TARGET)
                           << " --host"
    #endif
                           << " " << boot_image_option
                           << " " << dex_file_option
                           << " " << oat_fd_option
                           << " " << oat_location_option;
    
        // 创建dex2oat进程对dex文件进行优化为oat文件
        execl(dex2oat, dex2oat,
              "--runtime-arg", "-Xms64m",
              "--runtime-arg", "-Xmx64m",
              "--runtime-arg", "-classpath",
              "--runtime-arg", class_path,
              "--runtime-arg", oat_compiler_filter_option,
    #if !defined(ART_TARGET)
              "--host",
    #endif
              boot_image_option, // imageSpace文件的路径
              dex_file_option, // 被优化的dex文件的路径
              oat_fd_option, // 优化后oat文件的文件指针
              oat_location_option, // 优化后dex文件oat的存放文件路径
              NULL);
    
        PLOG(FATAL) << "execl(" << dex2oat << ") failed";
        return false;
      } else {
        // wait for dex2oat to finish
        // 等待dex文件被优化为oat成功完成
        int status;
        pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
        if (got_pid != pid) {
          PLOG(ERROR) << "waitpid failed: wanted " << pid << ", got " << got_pid;
          return false;
        }
        if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
          LOG(ERROR) << dex2oat << " failed with dex-file=" << dex_filename;
          return false;
        }
      }
      return true;
    }

    ART模式下基于dex2oat脱壳就是在创建dex2oat进程进行dex文件优化具体处理之前进行dex文件的内存dump处理,实现Android加固的dex文件脱壳。以Android 4.4.4 r1的源码为例,ART虚拟机模式下,dex文件的优化处理进程dex2oat的源码实现在文件 /art/dex2oat/dex2oat.cc  中,在调用 dex2oat函数 进行dex的优化之前会先判断dex文件是否可写。因此,选择在这个代码点进行原始dex文件的内存dump处理。

    http://androidxref.com/4.4.4_r1/xref/art/dex2oat/dex2oat.cc


    Dex2oatHunter脱壳工具添加的dump dex文件的代码如下所示,主要是针对早些时候的360加固和腾讯乐加固的脱壳处理。Dex2oatHunter脱壳工具作者提供的脱壳代码是应用在Android 4.4系统的ART虚拟机模式下的,因此在编译后修改后的dex2aot程序后,在进行360加固和腾讯乐加固的脱壳时,需要将Android系统切换到ART虚拟机模式下才能生效。

    https://github.com/spriteviki/Dex2oatHunter/blob/master/art/dex2oat/dex2oat.cc

        for (const auto& dex_file : dex_files) {
          if (!dex_file->EnableWrite()) {
            PLOG(ERROR) << "Failed to make .dex file writeable '" << dex_file->GetLocation() << "'
    ";
          }
    	  ///////////////////////////////////////////
          std::string dex_name = dex_file->GetLocation();
          LOG(INFO) << "Finding:dex file name-->" << dex_name;
    	  // dump 360加固宝的dex文件
          if (dex_name.find("jiagu") != std::string::npos)
          {
           LOG(INFO) << "Finding:dex file from qihoo-->" << dex_name;
              int len = dex_file->Size();
              char filename[256] = {0};
              sprintf(filename, "%s_%d.dex", dex_name.c_str(), len);
              int fd = open(filename , O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
              if (fd > 0)
              {
                  if (write(fd, (char*)dex_file->Begin(), len) <= 0)
                  {
                     LOG(INFO) << "Finding:write target dex file failed-->" << filename;
                  }
                     LOG(INFO) << "Finding:write target dex file successfully-->" << filename;
                 close(fd);
              }else
              {
                 LOG(INFO) << "Finding:open target dex file failed-->" << filename;
              }
          }
    	  // dump 腾讯乐固的dex文件
          if (tx_oat_filename.find("libshellc") != std::string::npos)
          {
           LOG(INFO) << "Finding:dex file from legu-->" << dex_name;
              int len = dex_file->Size();
              char filename[256] = {0};
              sprintf(filename, "%s_%d.dex", tx_oat_filename.c_str(), len);
              int fd = open(filename , O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
              if (fd > 0)
              {
                  if (write(fd, (char*)dex_file->Begin(), len) <= 0)
                  {
                     LOG(INFO) << "Finding:write target dex file failed-->" << filename;
                  }
                     LOG(INFO) << "Finding:write target dex file successfully-->" << filename;
                 close(fd);
              }else
              {
                 LOG(INFO) << "Finding:open target dex file failed-->" << filename;
              }
    		  ///////////////////////////////////////////
          }
        }

    经过测试,上面添加的脱壳代码在Android 5.1系统的源码上也适用,并且修改的代码点位置也是这个地方。尽管上面添加的脱壳代码能对360加固和腾讯乐固进行dump脱壳,但是感觉灵活性不够,只能对360加固和腾讯加固进行处理,过滤的字符串是固定的,一旦修改后的dex2oat程序编译好,过滤字符串一改变又得重新编译dex2oat程序,重新打包Android的镜像文件。基于这些麻烦和通用性不够的问题,对上面的代码进行了修改,增加了脱壳的通用性,动态的配置脱壳的过滤字符串,代码实现如下。在没有设置脱壳过滤字符串的情况下,默认只支持对360加固进行脱壳;如果需要配置脱壳过滤字符串,可以构建和编辑  /data/dex_dump_filter 配置文件 在Android 5.1 的系统源码上测试通过。

        /******Android加固dex文件的dump*******/
        // 获取被优化的Android dex文件的文件路径
        std::string dex_file_name = dex_file->GetLocation();
        LOG(INFO) << "Fly---get Dex File Name: " << dex_file_name;
    
        // dex dump的过滤词
        std::string str_dex_dump_filter;
        FILE *fp = NULL;
    
        std::string filter_path_name = "/data/dex_dump_filter";
        if (access(filter_path_name.c_str(), F_OK) == 0) {
          // 打开配置文件/data/dex_dump_filter
          fp = fopen("/data/dex_dump_filter", "r");
          if (fp == NULL) {
            LOG(INFO) << "Fly---get /data/dex_dump_filter ";
    
            // 默认dump 360加固dex的oat文件
            str_dex_dump_filter = ".jiagu";
    
           } else {
             char szFilterBuffer[128] = {0};
    
             // 读取文件中的第1行字符串---dex dump的过滤词
             fgets(szFilterBuffer, strlen(szFilterBuffer), fp);
             szFilterBuffer[strlen(szFilterBuffer) - 1]=0;
    
             // 进行字符串的拷贝
             str_dex_dump_filter.copy(szFilterBuffer, 0, strlen(szFilterBuffer) - 1);
             fclose(fp);
             fp = NULL;
           }
        } else {
          // 默认dump 360加固dex的oat文件
          str_dex_dump_filter = ".jiagu";
        }
        // 进行dex dump的过滤
        if (dex_file_name.find(str_dex_dump_filter.c_str()) != std::string::npos) {
           // 获取优化后的oat文件的大小
           int nLenth = dex_file->Size();
    
           char szBuffer[256] = {0};
           // 格式化字符串得到dump的dex文件(OAT)的名称
           sprintf(szBuffer, "%s_%d.dex", dex_file_name.c_str(), nLenth);
    
           // 打开或者创建文件
           int fopen = open(szBuffer, O_WRONLY | O_CREAT | O_TRUNC , S_IRWXU);
           // 判断文件是否打开成功
           if (fopen > 0) {
             // 将优化后dex的oat文件保存到指定名称文件中。
             if (write(fopen, (char*)(dex_file->Begin()), nLenth) <= 0) {
               LOG(INFO) << "Fly---write target dex file failed: " << szBuffer;
             } else {
               // 将优化后dex的oat文件保存到dex文件优化目录下成功
               LOG(INFO) << "Fly---write target dex file OK: " << szBuffer;
             }
             // 关闭文件
             close(fopen);
            } else {
             LOG(INFO) << "Fly---open target dex file failed: " << szBuffer;
            }
        }
        /******Android加固dex文件的dump*******/

    ART虚拟机模式下基于dex2oat脱壳是有必要条件的:1. 需要脱壳的Android加固应用必须运行ART虚拟机模式下;2.需要脱壳的Android加固应用通过DexClassLoader加载的dex文件必须是没有经过优化处理的,比如 梆梆加固加载的dex文件是已经经过优化处理的oat文件,因此针对这类情况的Android加固应用使用ART虚拟机模式下基于dex2oat脱壳是无效的。


    在Android 5.1系统的源码情况下,按照上面我提供的脱壳代码修改Android 5.1系统的 dex2oat的源码,然后编译生成Android 5.1模拟器的系统镜像文件,进行360加固Android应用的脱壳测试,结果如下图所示:



    Android系统的 /data/data/com.emate.shop/.jiagu 目录下360加固 dump出来的dex文件截图如下:



    经过验证dex文件发现 classes.dex_7766528.dex 文件 就是原始的dex文件,如下图:




    后记:尽管这种脱壳的方法不是很通用也不能脱最新版的360加固和腾讯乐固了,但是提供了一种ART虚拟机模式下基于dex2oat脱壳的思路。知识很多,整理真的很需要时间~


  • 相关阅读:
    Pascal's Triangle II
    Pascal's Triangle
    Best Time to Buy and Sell Stock II
    Best Time to Buy and Sell Stock
    Populating Next Right Pointers in Each Node
    path sum II
    Path Sum
    [转载]小波时频图
    [转载]小波时频图
    [转载]Hilbert变换及谱分析
  • 原文地址:https://www.cnblogs.com/csnd/p/11800601.html
Copyright © 2011-2022 走看看