Android 逆向ART 函数抽取加壳 ( ART 下的函数抽取恢复时机 | 禁用 dex2oat 机制源码分析 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 逆向ART 函数抽取加壳 ( ART 下的函数抽取恢复时机 | 禁用 dex2oat 机制源码分析 )相关的知识,希望对你有一定的参考价值。
文章目录
一、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;
以上是关于Android 逆向ART 函数抽取加壳 ( ART 下的函数抽取恢复时机 | 禁用 dex2oat 机制源码分析 )的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向ART 函数抽取加壳 ④ ( 对 libc.so#execve 函数进行内联 HOOK 操作 )
Android 逆向ART 函数抽取加壳 ⑤ ( unistd.h#execve 函数分析 | 使用自定义的 myexecve 函数替换 libc.so#execve 函数 )
Android 逆向ART 函数抽取加壳 ③ ( 禁用 dex2oat 操作 HOOK 点介绍 | 集成 InLineHook )
Android 逆向ART 函数抽取加壳 ② ( 禁用 dex2oat 简介 | TurboDex 中禁用 dex2oat 参考示例 )
Android 逆向加壳技术识别 ( 函数抽取 与 Native 化加壳的区分 | VMP 加壳与 Dex2C 加壳的区分 )
Android 逆向脱壳解决方案 ( DEX 整体加壳 | 函数抽取加壳 | VMP 加壳 | Dex2C 加壳 | Android 应用加固防护级别 )