天天看點

【Android 逆向】ART 函數抽取加殼 ( ART 下的函數抽取恢複時機 | 禁用 dex2oat 機制源碼分析 )

文章目錄

一、ART 下的函數抽取恢複時機二、禁用 dex2oat 機制源碼分析

  • 1、oat_file_assistant.cc#GenerateOatFileNoChecks 源碼分析
  • 2、oat_file_assistant.cc#Dex2Oat 源碼分析
  • 3、exec_utils.cc#Exec 源碼分析
  • 4、exec_utils.cc#ExecAndReturnCode 源碼分析

一、ART 下的函數抽取恢複時機

​ART 下的函數抽取恢複時機 :​

  • ​恢複抽取函數早于 oat 檔案編譯 :​ 在 ART 虛拟機下 , 需要将 dex 檔案編譯生成為 oat 檔案 , 将 dex 檔案中的 函數指令 抽取出來 , 必須 在 生成 oat 檔案之前 , 将從 抽取的函數指令恢複 ;
  • ​禁用 dex2oat 機制 :​ 如果 禁用 dex2oat 的編譯過程 , 則 恢複 被抽取的 函數指令 , 不在受 該條件限制 , 不是必須在 dex2oat 之前恢複 , 可以稍晚一些再恢複函數指令 ;

如果選擇第一種方案 , 在 dex2oat 之前進行恢複 , 這沒有任何意義 , dex2oat 編譯後 , 生成的 oat 檔案是完整的 , 此時 可以 完整的将 oat 檔案 dump 到 SD 卡中 , 基本等于沒有加強 , 還是一個一代殼 ;

是以 , 大部分加強廠商 , 選擇 禁用 dex2oat 機制 ; 這樣處于安全考慮 , 犧牲了應用的運作效率 ;

二、禁用 dex2oat 機制源碼分析

在之前的部落格章節 【Android 逆向】ART 脫殼 ( DexClassLoader 脫殼 | oat_file_assistant.cc 中涉及的 oat 檔案生成流程 ) 四、oat_file_assistant.cc#GenerateOatFileNoChecks 函數分析​ 中 , 分析到 , dex2oat 編譯過程中 , 需要調用 /art/runtime/oat_file_assistant.cc#GenerateOatFileNoChecks 函數 ;

1、oat_file_assistant.cc#GenerateOatFileNoChecks 源碼分析

在該函數中 , 調用 dex2oat 進行編譯 ;

在 /art/runtime/oat_file_assistant.cc#GenerateOatFileNoChecks​ 函數 中的 核心跳轉語句是 調用了 /art/runtime/oat_file_assistant.cc#Dex2Oat 函數 ;

​oat_file_assistant.cc#GenerateOatFileNoChecks 函數源碼 :​

OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
      OatFileAssistant::OatFileInfo& info, CompilerFilter::Filter filter, std::string* error_msg) {
  CHECK(error_msg != nullptr);

  Runtime* runtime = Runtime::Current();

  // 判斷 Dex2Oat 目前是否可用 , 如果不可用 , 直接傳回 
  if (!runtime->IsDex2OatEnabled()) {
    *error_msg = "Generation of oat file for dex location " + dex_location_
      + " not attempted because dex2oat is disabled.";
    return kUpdateNotAttempted;
  }

  if (info.Filename() == nullptr) {
    *error_msg = "Generation of oat file for dex location " + dex_location_
      + " not attempted because the oat file name could not be determined.";
    return kUpdateNotAttempted;
  }
  const std::string& oat_file_name = *info.Filename();
  const std::string& vdex_file_name = GetVdexFilename(oat_file_name);

  // dex2oat忽略丢失的dex檔案,不報告錯誤。
  // 請在此處明确檢查,以便正确檢測錯誤。
  // TODO:為什麼dex2oat會這樣做?
  struct stat dex_path_stat;
  if (TEMP_FAILURE_RETRY(stat(dex_location_.c_str(), &dex_path_stat)) != 0) {
    *error_msg = "Could not access dex location " + dex_location_ + ":" + strerror(errno);
    return kUpdateNotAttempted;
  }

  // 如果這是odex位置,我們需要建立odex檔案布局 (../oat/isa/..)
  if (!info.IsOatLocation()) {
    if (!PrepareOdexDirectories(dex_location_, oat_file_name, isa_, error_msg)) {
      return kUpdateNotAttempted;
    }
  }

  // 設定oat和vdex檔案的權限。
  // 當組和其他人傳播時,使用者總是被讀取和寫入
  // 原始dex檔案的讀取權限。
  mode_t file_mode = S_IRUSR | S_IWUSR |
      (dex_path_stat.st_mode & S_IRGRP) |
      (dex_path_stat.st_mode & S_IROTH);

  std::unique_ptr<File> vdex_file(OS::CreateEmptyFile(vdex_file_name.c_str()));
  if (vdex_file.get() == nullptr) {
    *error_msg = "Generation of oat file " + oat_file_name
      + " not attempted because the vdex file " + vdex_file_name
      + " could not be opened.";
    return kUpdateNotAttempted;
  }

  if (fchmod(vdex_file->Fd(), file_mode) != 0) {
    *error_msg = "Generation of oat file " + oat_file_name
      + " not attempted because the vdex file " + vdex_file_name
      + " could not be made world readable.";
    return kUpdateNotAttempted;
  }

  std::unique_ptr<File> oat_file(OS::CreateEmptyFile(oat_file_name.c_str()));
  if (oat_file.get() == nullptr) {
    *error_msg = "Generation of oat file " + oat_file_name
      + " not attempted because the oat file could not be created.";
    return kUpdateNotAttempted;
  }

  if (fchmod(oat_file->Fd(), file_mode) != 0) {
    *error_msg = "Generation of oat file " + oat_file_name
      + " not attempted because the oat file could not be made world readable.";
    oat_file->Erase();
    return kUpdateNotAttempted;
  }

  std::vector<std::string> args;
  args.push_back("--dex-file=" + dex_location_);
  args.push_back("--output-vdex-fd=" + std::to_string(vdex_file->Fd()));
  args.push_back("--oat-fd=" + std::to_string(oat_file->Fd()));
  args.push_back("--oat-location=" + oat_file_name);
  args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));

  // ★ 核心跳轉 
  if (!Dex2Oat(args, error_msg)) {
    // 手動删除oat和vdex檔案。這樣可以確定沒有垃圾
    // 如果程序意外死亡,則剩餘。
    vdex_file->Erase();
    unlink(vdex_file_name.c_str());
    oat_file->Erase();
    unlink(oat_file_name.c_str());
    return kUpdateFailed;
  }

  if (vdex_file->FlushCloseOrErase() != 0) {
    *error_msg = "Unable to close vdex file " + vdex_file_name;
    unlink(vdex_file_name.c_str());
    return kUpdateFailed;
  }

  if (oat_file->FlushCloseOrErase() != 0) {
    *error_msg = "Unable to close oat file " + oat_file_name;
    unlink(oat_file_name.c_str());
    return kUpdateFailed;
  }

  // 标記 odex 件已更改,我們應該嘗試重新加載。
  info.Reset();
  return kUpdateSucceeded;
}      

​源碼路徑 : /art/runtime/oat_file_assistant.cc#GenerateOatFileNoChecks

2、oat_file_assistant.cc#Dex2Oat 源碼分析

在 /art/runtime/oat_file_assistant.cc#Dex2Oat​ 函數中 , 調用了 /art/runtime/exec_utils.cc#Exec 函數 ;

bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args,
                               std::string* error_msg) {
  Runtime* runtime = Runtime::Current();
  std::string image_location = ImageLocation();
  if (image_location.empty()) {
    *error_msg = "No image location found for Dex2Oat.";
    return false;
  }

  std::vector<std::string> argv;
  argv.push_back(runtime->GetCompilerExecutable());
  argv.push_back("--runtime-arg");
  argv.push_back("-classpath");
  argv.push_back("--runtime-arg");
  std::string class_path = runtime->GetClassPathString();
  if (class_path == "") {
    class_path = OatFile::kSpecialSharedLibrary;
  }
  argv.push_back(class_path);
  if (runtime->IsJavaDebuggable()) {
    argv.push_back("--debuggable");
  }
  runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);

  if (!runtime->IsVerificationEnabled()) {
    argv.push_back("--compiler-filter=verify-none");
  }

  if (runtime->MustRelocateIfPossible()) {
    argv.push_back("--runtime-arg");
    argv.push_back("-Xrelocate");
  } else {
    argv.push_back("--runtime-arg");
    argv.push_back("-Xnorelocate");
  }

  if (!kIsTargetBuild) {
    argv.push_back("--host");
  }

  argv.push_back("--boot-image=" + image_location);

  std::vector<std::string> compiler_options = runtime->GetCompilerOptions();
  argv.insert(argv.end(), compiler_options.begin(), compiler_options.end());

  argv.insert(argv.end(), args.begin(), args.end());

  std::string command_line(android::base::Join(argv, ' '));
  // ★ 核心跳轉
  return Exec(argv, error_msg);
}      

​源碼路​徑 :​ ​​/art/runtime/oat_file_assistant.cc#Dex2Oat​​

3、exec_utils.cc#Exec 源碼分析

在 /art/runtime/exec_utils.cc#Exec​ 函數中 , 調用了 /art/runtime/exec_utils.cc#ExecAndReturnCode 函數 ;

bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
  // ★ 核心跳轉
  int status = ExecAndReturnCode(arg_vector, error_msg);
  if (status != 0) {
    const std::string command_line(android::base::Join(arg_vector, ' '));
    *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
                              command_line.c_str());
    return false;
  }
  return true;
}      

​源碼路徑 :​ ​​/art/runtime/exec_utils.cc#Exec​​

4、exec_utils.cc#ExecAndReturnCode 源碼分析

在 ​​/art/runtime/exec_utils.cc#ExecAndReturnCode​​ 函數中 , 調用了

execve(program, &args[0], envp);      

函數 , 通過 hook 該 execve 函數 , 可以禁用 dex2oat ;

int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) {
  const std::string command_line(android::base::Join(arg_vector, ' '));
  CHECK_GE(arg_vector.size(), 1U) << command_line;

  // 将參數轉換為字元指針。
  const char* program = arg_vector[0].c_str();
  std::vector<char*> args;
  for (size_t i = 0; i < arg_vector.size(); ++i) {
    const std::string& arg = arg_vector[i];
    char* arg_str = const_cast<char*>(arg.c_str());
    CHECK(arg_str != nullptr) << i;
    args.push_back(arg_str);
  }
  args.push_back(nullptr);

  // fork and exec
  pid_t pid = fork();
  if (pid == 0) {
    // fork和exec之間不允許配置設定

    // 更改流程組,這樣我們就不會被ProcessManager收獲
    setpgid(0, 0);

    // (b/30160149): 保護子程序不受對LD_LIBRARY_路徑等的修改的影響。
    // 使用從建立運作時開始的環境快照。
    char** envp = (Runtime::Current() == nullptr) ? nullptr : Runtime::Current()->GetEnvSnapshot();
    if (envp == nullptr) {
      execv(program, &args[0]);
    } else {
      execve(program, &args[0], envp);
    }
    PLOG(ERROR) << "Failed to execve(" << command_line << ")";
    // _exit to avoid atexit handlers in child.
    _exit(1);
  } else {
    if (pid == -1) {
      *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
                                command_line.c_str(), strerror(errno));
      return -1;
    }

    // 等待子程序完成
    int status = -1;
    pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
    if (got_pid != pid) {
      *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
                                "wanted %d, got %d: %s",
                                command_line.c_str(), pid, got_pid, strerror(errno));
      return -1;
    }
    if (WIFEXITED(status)) {
      return WEXITSTATUS(status);
    }
    return -1;
  }
}