从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 最佳实践建议

  1. 组合使用多种检测方法:结合传统和新型检测方法提高检测率
  2. 关键点定位:精准定位linker和关键符号位置
  3. 隐蔽性操作:避免直接字符串比对等易被hook的操作
  4. 定时检测:在程序关键节点周期性地执行检测
  5. 异常处理:对检测到Frida的情况进行隐蔽处理

3.3 技术发展趋势

未来Frida检测技术将朝着以下方向发展:

  1. 从Frida实现原理层面寻找检测点
  2. 利用Frida必须修改的系统结构进行检测
  3. 检测Frida运行时的必要内存特征
  4. 结合机器学习分析异常行为模式
  5. 多维度交叉验证提高检测准确性
Frida对SO解析流程分析及检测点挖掘 0x01 Frida寻找符号地址机制分析 Frida hook Native的基础是寻找符号在内存中的地址,主要通过 findExportByName 或枚举符号遍历实现。下面详细分析其实现机制。 1.1 模块基址查找流程 Frida通过以下步骤获取模块基址: 1.2 获取模块句柄实现 1.3 Android无限制dlopen实现 1.4 Linker模块查找机制 Frida通过解析/proc/self/maps查找linker: 关键校验点: 1.5 Linker符号解析 Frida通过解析linker的节头表获取关键函数地址: 关键符号表: 0x02 Frida检测点挖掘 2.1 传统检测方法 方法1:maps文件检测 方法2:soinfo链表检测 方法3:文件路径检测 方法4:ArtMethod标志检测 方法5:Inline Hook检测 2.2 新型检测方法 方法1:ELF头破坏检测 方法2:somain指针清空 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运行时的必要内存特征 结合机器学习分析异常行为模式 多维度交叉验证提高检测准确性