FART 自动化脱壳框架优化实战:Bug 修复与代码改进记录
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
open() 判断不严谨导致 dex 重复 dump
源码:https://github.com/CYRUS-STUDIO/FART/blob/master/fart10/art/runtime/art_method.cc
比如:
int dexfilefp = open(dex_path.c_str(), O_RDONLY);
if (dexfilefp > 0) {
close(dexfilefp);
}
这个判断条件其实是不严谨的,导致 if 中 的 close(dexfilefp); 一直没有执行,dex 会重复 dump。
open() 的返回值语义是:
成功时:返回一个非负整数(即 >= 0),它是打开的文件描述符。
失败时:返回 -1
正确的判断方式应该是:
if (fp >= 0) {
// 成功打开
} else {
// 打开失败,打印错误信息
LOG(ERROR) << "open dex file failed";
}
减少不必要的 I/O,提高 dump 成功率。
dump 目录创建失败(mkdir failed errno: 13)
FART 中通过 mkdir 函数在 sdcard 上创建 dump 文件存放目录,但是这样必须 app 拥有存储卡读写权限。不然 mkdir 会执行失败。
下面是一个 frida 脚本,调用系统 的 mkdir 函数创建目录
function mkdir(path) {
const libc = Module.findExportByName(null, "mkdir");
if (!libc) {
console.error("[-] Cannot find mkdir symbol.");
return;
}
const mkdirNative = new NativeFunction(libc, 'int', ['pointer', 'int']);
const pathStr = Memory.allocUtf8String(path);
const mode = 0o777; // 权限
const result = mkdirNative(pathStr, mode);
if (result === 0) {
console.log("[+] mkdir success:", path);
} else {
const errnoLocation = Module.findExportByName(null, "__errno");
if (errnoLocation) {
const errnoPtr = new NativeFunction(errnoLocation, 'pointer', []);
const errnoValue = Memory.readU32(errnoPtr());
console.error("[-] mkdir failed errno:", errnoValue);
} else {
console.error("[-] mkdir failed");
}
}
}
调用 mkdir 创建目录 /sdcard/fart/com.cyrus.example
mkdir("/sdcard/fart/com.cyrus.example");
结果如下:
[Remote::com.cyrus.example]-> mkdir("/sdcard/fart/com.cyrus.example");
[-] mkdir failed errno: 13
[-] mkdir failed errno: 13 表示 Permission denied(权限被拒绝)。
常见的 errno 错误码:
errno 值 | 含义 |
---|---|
EEXIST | 目录已经存在 |
EACCES | 权限不足 |
ENOENT | 上级目录不存在 |
ENAMETOOLONG | 路径太长 |
ENOSPC | 没有磁盘空间或 inode |
解决方案:把 dump 路径改为:/data/data/<packageName>/fart
该目录无需动态申请存储权限,也不受 MANAGE_EXTERNAL_STORAGE 限制。
art_method.cc 中增加以下方法,创建目录并并打印日志
//add 创建目录
bool ensure_dir_exists(const std::string& path) {
int res = mkdir(path.c_str(), 0777);
if (res == 0 || errno == EEXIST) {
return true;
} else {
LOG(ERROR) << "mkdir failed: " << path << ", errno=" << errno << ", " << errno;
return false;
}
}
修改 dumpDexFileByExecute 和 dumpArtMethod 方法中 dump 文件存放路径
// 创建目录:/data/data/<packageName>/fart
std::string base_dir = "/data/data/";
std::string app_dir = base_dir + szProcName;
std::string fart_dir = app_dir + "/fart";
ensure_dir_exists(app_dir);
ensure_dir_exists(fart_dir);
// 保存 dex 文件
std::string dex_path = fart_dir + "/" + std::to_string(size_int) + "_dex_file.dex";
// 保存 class 列表
std::string class_list_path = fart_dir + "/" + std::to_string(size_int) + "_class_list.txt";
dumpArtMethod 方法中 CodeItem 的保存路径也要修改
// 保存 CodeItem
std::string ins_path = fart_dir + "/" + std::to_string(size_int) + "_ins_" + std::to_string(tid) + ".bin";
修改完成后重新编译刷机。
即使 app 没有存储卡读写权限也能正常 dump 了。
wayne:/data/data/com.cyrus.example/fart # ls
1321896_class_list.txt 1437648_dex_file_execute.dex 1488168_class_list_execute.txt 1605504_ins_4714.bin
1321896_class_list_execute.txt 1437648_ins_4714.bin 1488168_dex_file.dex 198768_class_list.txt
1321896_dex_file.dex 1448488_class_list.txt 1488168_dex_file_execute.dex 198768_class_list_execute.txt
1321896_dex_file_execute.dex 1448488_class_list_execute.txt 1488168_ins_4714.bin 198768_dex_file.dex
1321896_ins_4714.bin 1448488_dex_file.dex 1496608_class_list.txt 198768_dex_file_execute.dex
1351008_class_list.txt 1448488_dex_file_execute.dex 1496608_class_list_execute.txt 198768_ins_4714.bin
1351008_class_list_execute.txt 1448488_ins_4714.bin 1496608_dex_file.dex 3782924_class_list_execute.txt
1351008_dex_file.dex 1461504_class_list.txt 1496608_dex_file_execute.dex 3782924_dex_file_execute.dex
1351008_dex_file_execute.dex 1461504_class_list_execute.txt 1496608_ins_4714.bin 400440_class_list_execute.txt
1351008_ins_4714.bin 1461504_dex_file.dex 1537456_class_list.txt 400440_dex_file_execute.dex
1403328_class_list.txt 1461504_dex_file_execute.dex 1537456_class_list_execute.txt 4376620_class_list_execute.txt
1403328_class_list_execute.txt 1461504_ins_4714.bin 1537456_dex_file.dex 4376620_dex_file_execute.dex
1403328_dex_file.dex 1472352_class_list.txt 1537456_dex_file_execute.dex 590624_class_list.txt
1403328_dex_file_execute.dex 1472352_class_list_execute.txt 1537456_ins_4714.bin 590624_class_list_execute.txt
1403328_ins_4714.bin 1472352_dex_file.dex 1571616_class_list.txt 590624_dex_file.dex
1423432_class_list.txt 1472352_dex_file_execute.dex 1571616_class_list_execute.txt 590624_dex_file_execute.dex
1423432_class_list_execute.txt 1472352_ins_4714.bin 1571616_dex_file.dex 590624_ins_4714.bin
1423432_dex_file.dex 1481472_class_list.txt 1571616_dex_file_execute.dex 7387912_class_list_execute.txt
1423432_dex_file_execute.dex 1481472_class_list_execute.txt 1571616_ins_4714.bin 7387912_dex_file_execute.dex
1423432_ins_4714.bin 1481472_dex_file.dex 1605504_class_list.txt 8391596_class_list_execute.txt
1437648_class_list.txt 1481472_dex_file_execute.dex 1605504_class_list_execute.txt 8391596_dex_file_execute.dex
1437648_class_list_execute.txt 1481472_ins_4714.bin 1605504_dex_file.dex 9085048_class_list_execute.txt
1437648_dex_file.dex 1488168_class_list.txt 1605504_dex_file_execute.dex 9085048_dex_file_execute.dex
Android 编译构建阶段的 dump 调用干扰
在 Android 编译构建阶段的 dex2oatd 工具执行时 调用了 art_method.cc 中的方法,导致出现下面的日志
dex2oatd E 05-27 19:26:24 7330 7330 art_method.cc:151] mkdir failed: /sdcard/Android/data/out/soong/host/linux-x86/bin/dex2oatd, errno=2, 2
dex2oatd E 05-27 19:26:24 7330 7330 art_method.cc:151] mkdir failed: /sdcard/Android/data/out/soong/host/linux-x86/bin/dex2oatd/fart, errno=2, 2
dex2oatd E 05-27 19:26:24 7330 7330 art_method.cc:234] [dumpDexFileByExecute] /sdcard/Android/data/out/soong/host/linux-x86/bin/dex2oatd/fart/4376620_dex_file_execute.dex open failed, fp=-1
dex2oatd E 05-27 19:26:24 7330 7330 art_method.cc:151] mkdir failed: /sdcard/Android/data/out/soong/host/linux-x86/bin/dex2oatd, errno=2, 2
dex2oatd E 05-27 19:26:24 7330 7330 art_method.cc:151] mkdir failed: /sdcard/Android/data/out/soong/host/linux-x86/bin/dex2oatd/fart, errno=2, 2
在 dump 前加上判断是否运行在 Android 环境
//add 跳过 Android 编译构建阶段的 dex2oatd 工具执行时的调用
bool isValidAndroidApp(const char* procName) {
// 排除 host 工具,例如 dex2oat、dex2oatd、aapt2 等
return procName != nullptr &&
strstr(procName, "/") == nullptr && // 不应该包含路径
strstr(procName, "dex2oat") == nullptr && // 排除 dex2oat/dex2oatd
strstr(procName, "soong") == nullptr; // 排除构建系统相关路径
}
//add
extern "C" void dumpDexFileByExecute(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
...
if (szProcName[0] == '\0') {
LOG(WARNING) << "[dumpDexFileByExecute] 获取进程名失败:" << artmethod->PrettyMethod();
return;
}
if (!isValidAndroidApp(szProcName)) {
LOG(WARNING) << "[dumpDexFileByExecute] 当前进程 " << szProcName << " 非法,跳过 dex dump";
return;
}
...
}
//add
extern "C" void dumpArtMethod(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
...
if (szProcName[0] == '\0') {
LOG(WARNING) << "[dumpArtMethod] 获取进程名失败:" << artmethod->PrettyMethod();
return;
}
if (!isValidAndroidApp(szProcName)) {
LOG(WARNING) << "[dumpArtMethod] 当前进程 " << szProcName << " 非法,跳过 dex dump";
return;
}
...
}
完整源码
开源地址:https://github.com/CYRUS-STUDIO/FART
相关文章: