从Frida对SO的解析流程中寻找检测Frida的点
字数 1035 2025-08-07 00:35:04
Frida对SO解析流程分析及检测点挖掘
0x01 Frida寻找符号地址机制分析
Frida hook Native的基础是寻找符号在内存中的地址,主要通过findExportByName或枚举符号遍历实现。下面详细分析其实现机制。
1.1 模块基址查找流程
Frida通过以下步骤获取模块基址:
GumAddress gum_module_find_export_by_name(const gchar *module_name, const gchar *symbol_name) {
GumAddress result;
void *module;
// Android特殊处理
if (gum_android_get_linker_flavor() == GUM_ANDROID_LINKER_NATIVE
&& gum_android_try_resolve_magic_export(module_name, symbol_name, &result))
return result;
// 获取模块句柄
if (module_name != NULL) {
module = gum_module_get_handle(module_name);
if (module == NULL) return 0;
} else {
module = RTLD_DEFAULT;
}
// 获取符号地址
result = GUM_ADDRESS(gum_module_get_symbol(module, symbol_name));
if (module != RTLD_DEFAULT) dlclose(module);
return result;
}
1.2 获取模块句柄实现
void* gum_module_get_handle(const gchar *module_name) {
#ifdef HAVE_ANDROID
if (gum_android_get_linker_flavor() == GUM_ANDROID_LINKER_NATIVE)
return gum_android_get_module_handle(module_name); // 无限制的dlopen
#endif
return dlopen(module_name, RTLD_LAZY | RTLD_NOLOAD); // 普通dlopen
}
1.3 Android无限制dlopen实现
void* gum_android_get_module_handle(const gchar *name) {
GumGetModuleHandleContext ctx;
ctx.name = name;
ctx.module = NULL;
gum_enumerate_soinfo((GumFoundSoinfoFunc)gum_store_module_handle_if_name_matches, &ctx);
return ctx.module;
}
1.4 Linker模块查找机制
Frida通过解析/proc/self/maps查找linker:
static const GumModuleDetails* gum_try_init_linker_details(void) {
gchar *maps, **lines;
g_file_get_contents("/proc/self/maps", &maps, NULL, NULL);
lines = g_strsplit(maps, "\n", 0);
// 优先查找[vdso]附近的linker
for (i = vdso_index + 1; i != num_lines; i++) {
if (gum_try_parse_linker_proc_maps_line(lines[i], linker_path,
linker_path_pattern, &gum_dl_module, &gum_dl_range)) {
result = &gum_dl_module;
goto beach;
}
}
// 没有vdso则全量搜索
for (i = num_lines - 1; i >= 0; i--) {
if (gum_try_parse_linker_proc_maps_line(...)) {
result = &gum_dl_module;
goto beach;
}
}
}
关键校验点:
static gboolean gum_try_parse_linker_proc_maps_line(...) {
const guint8 elf_magic[] = {0x7f, 'E', 'L', 'F'};
if (memcmp(GSIZE_TO_POINTER(start), elf_magic, sizeof(elf_magic)) != 0)
return FALSE; // ELF魔术头校验
if (perms[0] != 'r') return FALSE; // 可读权限校验
// ...
}
1.5 Linker符号解析
Frida通过解析linker的节头表获取关键函数地址:
void gum_elf_module_enumerate_symbols(GumElfModule *self,
GumElfFoundSymbolFunc func, gpointer user_data) {
gum_elf_module_enumerate_symbols_in_section(self, SHT_SYMTAB, func, user_data);
}
static void gum_elf_module_enumerate_symbols_in_section(...) {
if (!gum_elf_module_find_section_header_by_type(self, section, &scn, &shdr))
return;
// 遍历符号表
for (symbol_index = 0; symbol_index != symbol_count && carry_on; symbol_index++) {
carry_on = func(&details, user_data); // 调用gum_store_linker_symbol_if_needed
}
}
关键符号表:
static gboolean gum_store_linker_symbol_if_needed(...) {
/* Android >= 8.0 */
GUM_TRY_ASSIGN(dlopen, "__dl___loader_dlopen");
GUM_TRY_ASSIGN(dlsym, "__dl___loader_dlvsym");
/* Android >= 7.0 */
GUM_TRY_ASSIGN_OPTIONAL(do_dlopen, "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv");
GUM_TRY_ASSIGN_OPTIONAL(do_dlsym, "__dl__Z8do_dlsymPvPKcS1_S_PS_");
/* 其他关键符号 */
GUM_TRY_ASSIGN(solist_get_head, "__dl__Z15solist_get_headv");
GUM_TRY_ASSIGN_OPTIONAL(solist, "__dl__ZL6solist");
GUM_TRY_ASSIGN(solist_get_somain, "__dl__Z17solist_get_somainv");
GUM_TRY_ASSIGN_OPTIONAL(somain, "__dl__ZL6somain");
}
0x02 Frida检测点挖掘
2.1 传统检测方法
方法1:maps文件检测
void anti3() {
FILE *fp = fopen("/proc/self/maps", "r");
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "frida-agent")) {
__android_log_print(6, "r0ysue", "i find frida from anti3");
}
}
}
方法2:soinfo链表检测
void fridafind() {
// 获取linker地址
int dlopenoff = findsym("/system/bin/linker64", "__dl__Z8__dlopenPKciPKv");
int headeroff = findsym("/system/bin/linker64", "_dl__ZL6solist");
// 遍历soinfo链表
long header = *(long*)((char*)start + headeroff);
for (_QWORD *result = (_QWORD *)header; result; result = (_QWORD *)result[5]) {
if (strstr((const char*)*(_QWORD*)((__int64)result + 408), "frida"))
__android_log_print(6, "r0ysue", "%s",*(_QWORD*)((__int64)result + 408));
}
}
方法3:文件路径检测
void anti4() {
if (access("/data/local/tmp/re.frida.server", 0) == 0)
__android_log_print(6, "r0ysue", "i find frida from anti4");
}
方法4:ArtMethod标志检测
void anti7(__int64 a1) {
if ((~*(_DWORD*)(a1 + 4) & 0x80000) != 0)
__android_log_print(6, "r0ysue", "i find frida %x", (~*(_DWORD*)(a1 + 4) & 0x80000));
}
方法5:Inline Hook检测
void anti6(long *as) {
if (*as == 0xd61f020058000050) {
__android_log_print(6, "r0ysue", "i find frida from anti6");
}
}
2.2 新型检测方法
方法1:ELF头破坏检测
void anti7() {
// 获取linker地址
FILE *fp = fopen("/proc/self/maps", "r");
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "linker64")) {
start = reinterpret_cast<int*>(strtoul(strtok(line, "-"), NULL, 16));
break;
}
}
// 修改ELF头
long *sr = reinterpret_cast<long*>(start);
mprotect(start, PAGE_SIZE, PROT_WRITE|PROT_READ|PROT_EXEC);
*sr = *sr ^ 0x7f; // 破坏ELF魔术头
}
方法2:somain指针清空
void anti7() {
// 获取linker地址
FILE *fp = fopen("/proc/self/maps", "r");
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, "linker64")) {
start = reinterpret_cast<int*>(strtoul(strtok(line, "-"), NULL, 16));
break;
}
}
// 清空somain指针
long off = findsym("/system/bin/linker64", "__dl__ZL6somain");
long *somain = reinterpret_cast<long*>((char*)sr + off);
*somain = 0;
}
0x03 总结
3.1 检测方法对比
| 检测类型 | 检测点 | 优点 | 缺点 | 绕过难度 |
|---|---|---|---|---|
| 传统方法 | maps文件/路径/字符串 | 实现简单 | 易被hook绕过 | 低 |
| 传统方法 | ArtMethod标志 | 原理性检测 | 仅适用于Java Hook | 中 |
| 传统方法 | Inline Hook特征 | 检测Frida必要修改 | 可能有误报 | 中高 |
| 新型方法 | ELF头破坏 | 难以被察觉 | 需精准定位linker | 高 |
| 新型方法 | somain清空 | 影响Frida符号查找 | 需精准定位符号 | 高 |
3.2 最佳实践建议
- 组合使用多种检测方法:结合传统和新型检测方法提高检测率
- 关键点定位:精准定位linker和关键符号位置
- 隐蔽性操作:避免直接字符串比对等易被hook的操作
- 定时检测:在程序关键节点周期性地执行检测
- 异常处理:对检测到Frida的情况进行隐蔽处理
3.3 技术发展趋势
未来Frida检测技术将朝着以下方向发展:
- 从Frida实现原理层面寻找检测点
- 利用Frida必须修改的系统结构进行检测
- 检测Frida运行时的必要内存特征
- 结合机器学习分析异常行为模式
- 多维度交叉验证提高检测准确性