全AI分析DexH*lperAnti-Frida深度检测机制

admin 2026-04-13 05:23:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深度分析libDexH*lper.so的反Frida检测机制,发现5个检测点中仅0x5f388(sub5ED80)函数patch后可阻止Frida退出。该函数通过监控/proc/pid/status的TracerPid字段,使用syscall直调openat读取追踪进程cmdline并进行手动特征匹配,检测到Frida后直接调用kill+exit终止进程。文章详细解析了线程创建、检测循环、反hook技巧等实现细节。 综合评分: 85 文章分类: 移动安全,逆向分析,恶意软件,渗透测试,二进制安全


cover_image

全AI分析DexH*lper Anti-Frida深度检测机制

原创

吾爱小白 吾爱小白

小白技术社

2026年4月10日 13:19 江西

DexH*lper.so Anti-Frida 检测机制深度分析

概述

libDexH*lper.so 实现了多层安全检测机制,其中 5 个关键 patch 点分别对应不同的检测逻辑。只有 0x5f388 (sub_5ED80) 被 patch 后 Frida 才不会退出,因为它是唯一一个通过 TracerPid 精准识别 Frida 进程并执行 kill + _exit 的函数。本文全是Claude code 分析得来,后边继续发剩余部分。

关键结论

| Patch 点 | 函数 | 检测类型 | Patch 后阻止 Frida 退出? | | — | — | — | — | | 0x5f388 | sub_5ED80 | TracerPid + Frida cmdline 匹配 | YES – 核心检测点 | | 0x57278 | sub_57278 | /proc/self/maps 内存扫描 | NO | | 0x53d30 | sub_53A8C | 代码完整性校验 | NO | | 0x5f8c4 | sub_5F8C4 | 线程枚举检测 | NO | | 0x60ffc | sub_60B70 | APK 签名块校验 | NO |


0x5f388 — sub_5ED80: TracerPid Frida 检测 (核心杀死点)

为什么 patch 这个函数 Frida 就不退出

Frida 默认模式使用 ptrace(PTRACE_ATTACH) 附加到目标进程,这会在 /proc/<pid>/status 中设置 TracerPid 字段为 Frida-server 的 PID。sub_5ED80 是唯一一个持续监控 TracerPid 并匹配 Frida 特征然后执行 kill 的函数

线程创建入口 — sub_5EC84 (0x5EC84)

// sub_5EC84: 创建检测线程
__int64&nbsp;sub_5EC84(_BYTE *a1,&nbsp;int&nbsp;a2)&nbsp;{
&nbsp; &nbsp;&nbsp;// 分配参数结构体 (16 bytes)
&nbsp; &nbsp; v5 =&nbsp;malloc(16);
&nbsp; &nbsp; *(int*)(v5 +&nbsp;4) = a2; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 存储目标 PID
&nbsp; &nbsp; *(char**)(v5 +&nbsp;8) = strdup(a1);&nbsp;// 存储匹配模式(解密后的 Frida 特征串)

&nbsp; &nbsp;&nbsp;// 创建 pthread,入口为 sub_5ED80
&nbsp; &nbsp;&nbsp;// 失败则重试最多 30 次,每次 sleep(1)
&nbsp; &nbsp; result = pthread_create(&tid,&nbsp;NULL, sub_5ED80, v5);
&nbsp; &nbsp;&nbsp;if&nbsp;(result) {
&nbsp; &nbsp; &nbsp; &nbsp; retry_count =&nbsp;-29;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;do&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sleep(1);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = pthread_create(&tid,&nbsp;NULL, sub_5ED80, v5);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;while&nbsp;(result && ++retry_count);
&nbsp; &nbsp; }
}

核心检测逻辑 — sub_5ED80 (0x5ED80)

// sub_5ED80: Frida TracerPid 检测主循环
__int64&nbsp;sub_5ED80(__int64 a1)&nbsp;{
&nbsp; &nbsp;&nbsp;int&nbsp;target_pid = *(int*)(a1 +&nbsp;4); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 目标进程 PID
&nbsp; &nbsp;&nbsp;char&nbsp;*frida_pattern = *(char**)(a1 +&nbsp;8); &nbsp; &nbsp;// Frida 特征串(已解密)

&nbsp; &nbsp;&nbsp;// [1] 初始化 Frida 特征缓冲区
&nbsp; &nbsp;&nbsp;char&nbsp;frida_sig[512];
&nbsp; &nbsp;&nbsp;memset(frida_sig,&nbsp;0,&nbsp;sizeof(frida_sig));
&nbsp; &nbsp; sub_2F5B0(frida_sig); &nbsp;// 解密/构造 Frida 特征字符串到 frida_sig
&nbsp; &nbsp;&nbsp;// frida_sig 中包含解密后的 Frida 进程特征 (如 "frida-server", "frida-agent" 等)

&nbsp; &nbsp;&nbsp;free(*(char**)(a1 +&nbsp;8));
&nbsp; &nbsp;&nbsp;free(a1);

&nbsp; &nbsp;&nbsp;// [2] 栈上构造 /proc 路径 (避免静态字符串被搜索到)
&nbsp; &nbsp;&nbsp;char&nbsp;proc_paths[48];
&nbsp; &nbsp;&nbsp;// proc_paths[0..15] &nbsp;= "/proc/self/status" &nbsp;(v47[0])
&nbsp; &nbsp;&nbsp;// proc_paths[16..31] = "/proc/%ld/cmdline" &nbsp;(v47[1])
&nbsp; &nbsp;&nbsp;// proc_paths[32..47] = "/proc/%ld/status" &nbsp; (v47[2])
&nbsp; &nbsp; qmemcpy(proc_paths,&nbsp;"/proc/self/statu/proc/%ld/cmdlin/proc/%ld/status",&nbsp;48);

&nbsp; &nbsp;&nbsp;// ==================== 无限检测循环 ====================
&nbsp; &nbsp;&nbsp;while&nbsp;(1) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [3] 构造 /proc/<pid>/status 路径并打开
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;status_path[256];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;snprintf(status_path,&nbsp;256,&nbsp;"/proc/%ld/status", target_pid);
&nbsp; &nbsp; &nbsp; &nbsp; FILE *fp = fopen(status_path,&nbsp;"r");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!fp) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 回退到 /proc/self/status
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fp = fopen("/proc/self/status",&nbsp;"r");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!fp)&nbsp;goto&nbsp;sleep_and_retry;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [4] 逐行读取,查找 "State:" 行
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;line[1024];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;state_key[] =&nbsp;"State:";
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(fgets(line,&nbsp;1024, fp)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(strstr(line, state_key))&nbsp;break; &nbsp;// 找到 State: 行
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [5] 继续查找 "TracerPid:" 行
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;tracer_key[] =&nbsp;"TracerPid:";
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;int&nbsp;tracer_pid =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(fgets(line,&nbsp;1024, fp)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(strstr(line, tracer_key)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [6] 提取 TracerPid 数值
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sscanf(line,&nbsp;"%s %d", dummy, &tracer_pid);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [7] 判断是否被 ptrace
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; TracerPid == 0: 没有被追踪
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; TracerPid == getpid(): 自己追踪自己(正常的反调试占坑)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(tracer_pid ==&nbsp;0&nbsp;|| tracer_pid == getpid()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fclose(fp);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;sleep_and_retry; &nbsp;// 无威胁,继续循环
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ========== 发现 Tracer!开始验证是否为 Frida ==========

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [8] 打开 /proc/<TracerPid>/cmdline (用 openat 系统调用,避免 libc hook)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;cmdline_path[256];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;snprintf(cmdline_path,&nbsp;256,&nbsp;"/proc/%ld/cmdline", tracer_pid);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;cmd_fd = syscall(__NR_openat, AT_FDCWD, cmdline_path, O_RDONLY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(cmd_fd <&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; detected =&nbsp;1; &nbsp;// 打不开 = 可疑
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;handle_detection;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [9] 打开 /proc/<TracerPid>/status (用 openat 系统调用)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;tracer_status_path[256];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;snprintf(tracer_status_path,&nbsp;256,&nbsp;"/proc/%ld/status", tracer_pid);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;status_fd = syscall(__NR_openat, AT_FDCWD, tracer_status_path, O_RDONLY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(status_fd <&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; detected =&nbsp;1;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;handle_detection;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [10] 读取 tracer 的 cmdline
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;cmdline_buf[1024];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memset(cmdline_buf,&nbsp;0,&nbsp;1024);
&nbsp; &nbsp; &nbsp; &nbsp; FILE *cmd_fp = fdopen(status_fd,&nbsp;"r");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(cmd_fp &&&nbsp;fscanf(cmd_fp,&nbsp;"%s", cmdline_buf)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [11] 在 cmdline 中搜索 Frida 特征
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; &nbsp;frida_sig 包含解密后的 "frida" 相关特征字符串
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; &nbsp;使用手动子串匹配 (非 strstr,避免被 hook)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;*match = manual_strstr(cmdline_buf, frida_sig);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; detected = (match ==&nbsp;NULL); &nbsp;// NULL = 未匹配 = 不是 Frida
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 注意: detected == 0 表示匹配到了 Frida 特征
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; fclose(cmd_fp);
&nbsp; &nbsp; &nbsp; &nbsp; close(status_fd);

&nbsp; &nbsp; handle_detection:
&nbsp; &nbsp; &nbsp; &nbsp; fclose(fp);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!detected) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;sleep_and_retry; &nbsp;// 不是 Frida,继续监控
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ==================== 检测到 Frida,执行终杀 ====================

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [12] 检查全局标志位 (是否启用杀进程)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(((*global_config)[89] &&nbsp;0x20) ==&nbsp;0&nbsp;&& !(*global_config)[84]) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [13] 直接用系统调用 kill,绕过 libc hook
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; syscall(__NR_kill, target_pid, SIGKILL); &nbsp;// 杀死目标进程
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [14] 反调试 hash 校验
&nbsp; &nbsp; &nbsp; &nbsp; sub_32CF0(32,&nbsp;0xB6A104AF,&nbsp;4095);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// [15] fork 子进程执行清理,然后退出
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;pid_t&nbsp;pid = getpid();
&nbsp; &nbsp; &nbsp; &nbsp; sub_44450(pid); &nbsp;// 可能是进程状态清理
&nbsp; &nbsp; &nbsp; &nbsp; _exit(3); &nbsp; &nbsp; &nbsp; &nbsp;// 直接退出,不经过 atexit handlers

&nbsp; &nbsp; sleep_and_retry:
&nbsp; &nbsp; &nbsp; &nbsp; sleep(1); &nbsp;// 每秒检测一次
&nbsp; &nbsp; }
}

检测流程图

pthread_create(sub_5ED80)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; [无限循环 sleep(1)]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; 打开 /proc/<pid>/status
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; 解析&nbsp;"TracerPid:"&nbsp;字段
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; +----+----+
&nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; == 0 &nbsp; &nbsp; != 0 且 != getpid()
&nbsp; (安全) &nbsp; &nbsp;(被 ptrace!)
&nbsp; &nbsp; | &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; v &nbsp; &nbsp; &nbsp; &nbsp; v
&nbsp; 继续循环 &nbsp; &nbsp;syscall(openat,&nbsp;"/proc/<TracerPid>/cmdline")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;读取 tracer cmdline
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; v
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;手动子串匹配 Frida 特征
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;+----+----+
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; |
&nbsp; &nbsp; &nbsp; &nbsp;不匹配 &nbsp; &nbsp;匹配到 Frida!
&nbsp; &nbsp; &nbsp; &nbsp;(继续) &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; syscall(kill, pid, SIGKILL) &nbsp;← 杀死目标进程
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;_exit(3) &nbsp;← 终止自身

反检测技巧

| 技巧 | 具体实现 | 目的 | | — | — | — | | 栈上构造字符串 | qmemcpy(proc_paths, "/proc/self/statu/proc/%ld/cmdlin...", 48) | 避免静态字符串搜索 | | syscall 直调 | linux_eabi_syscall(__NR_openat, ...) | 绕过 libc open() 的 Frida hook | | syscall kill | linux_eabi_syscall(__NR_kill, pid, 9) | 绕过 libc kill() 的 Frida hook | | 手动字符串匹配 | 逐字节比较而非 strstr() | 绕过 strstr 的 Frida hook | | 解密特征串 | sub_2F5B0(frida_sig) 运行时解密 | 避免静态分析发现 “frida” 字符串 | | 持续轮询 | while(1) { ... sleep(1); } | 应对延迟注入 | | pthread 重试 | 创建失败重试 30 次 | 确保检测线程一定启动 |


0x57278 — sub_57278: /proc/self/maps 内存映射扫描

功能

扫描进程内存映射,查找 Frida 注入的动态库。

线程创建入口 — sub_53FE4 (0x53FE4)

// 在全局标志检查后创建线程
if&nbsp;(!(*global_config)[83]) {
&nbsp; &nbsp; buf =&nbsp;malloc(128);
&nbsp; &nbsp;&nbsp;memset(buf,&nbsp;0,&nbsp;128);
&nbsp; &nbsp;&nbsp;memcpy(buf, param,&nbsp;128);
&nbsp; &nbsp; pthread_create(&tid,&nbsp;NULL, sub_57278, buf); &nbsp;// 启动 maps 扫描线程
}

检测代码

// sub_57278: maps 扫描检测
void&nbsp;sub_57278(void&nbsp;*arg)&nbsp;{
&nbsp; &nbsp;&nbsp;char&nbsp;decoded_buf[128];
&nbsp; &nbsp;&nbsp;memcpy(decoded_buf, arg,&nbsp;128);
&nbsp; &nbsp;&nbsp;free(arg);
&nbsp; &nbsp; sleep(2); &nbsp;// 延迟 2 秒等待进程稳定

&nbsp; &nbsp;&nbsp;// [1] 加载混淆的库名 "buvfvv0kqgq1z" (解密后可能是 Frida agent 库名)
&nbsp; &nbsp;&nbsp;char&nbsp;obfuscated_name[] =&nbsp;"buvfvv0kqgq1z";
&nbsp; &nbsp;&nbsp;uint64_t&nbsp;xor_key = qword_D64F8; &nbsp;// 解密密钥

&nbsp; &nbsp;&nbsp;// [2] 构造文件名 "nf.oy" (混淆: 可能解密为 "re.frida.server" 类似模式)
&nbsp; &nbsp;&nbsp;// w8 = 0x666E = "nf"
&nbsp; &nbsp;&nbsp;// w8 |= (0x2E6F << 16) = ".o"
&nbsp; &nbsp;&nbsp;// w9 = 0x79 = "y"
&nbsp; &nbsp;&nbsp;// 组合: "nf.oy" → 解密后为 Frida 相关文件名

&nbsp; &nbsp;&nbsp;// [3] 清空大缓冲区用于存放 maps 内容
&nbsp; &nbsp;&nbsp;char&nbsp;maps_buf[512];
&nbsp; &nbsp;&nbsp;memset(maps_buf,&nbsp;0,&nbsp;sizeof(maps_buf));

&nbsp; &nbsp;&nbsp;// [4] 读取并扫描 /proc/self/maps
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; 搜索解密后的 Frida 库特征
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; 比如: "frida-agent", "frida-gadget", "linjector" 等

&nbsp; &nbsp;&nbsp;// [5] 设置检测标志,不直接 kill
}

为什么 patch 后不影响 Frida

  • 此函数仅扫描 maps 并设置标志位,不直接执行 kill/_exit
  • 即使检测到 Frida 库在 maps 中,真正的杀进程动作仍由 sub_5ED80 (0x5f388) 的 TracerPid 检测触发
  • Frida attach 时设置的 TracerPid 是最快被发现的信号,maps 扫描相对更慢

0x53d30 — sub_53A8C: ELF 代码段完整性校验

功能

验证 .text 段代码是否被运行时修改 (检测 inline hook)。

检测代码

// sub_53A8C: 代码完整性校验
__int64&nbsp;sub_53A8C(int&nbsp;version, __int64 path_param, __int64 section_param)&nbsp;{
&nbsp; &nbsp;&nbsp;// [1] 解析参数,获取 ELF section 信息
&nbsp; &nbsp;&nbsp;if&nbsp;(!sub_41CDC(path_param, &elf_info))&nbsp;return&nbsp;0;

&nbsp; &nbsp;&nbsp;// [2] 构造 section 路径
&nbsp; &nbsp;&nbsp;char&nbsp;section_path[520];
&nbsp; &nbsp;&nbsp;int&nbsp;section_size = sub_2F220(section_param);
&nbsp; &nbsp;&nbsp;char&nbsp;*section_buf =&nbsp;malloc(section_size +&nbsp;1);
&nbsp; &nbsp;&nbsp;strcpy(section_buf, section_param);

&nbsp; &nbsp;&nbsp;// [3] 用 syscall 打开文件 (绕过 libc hook)
&nbsp; &nbsp;&nbsp;int&nbsp;fd = syscall(__NR_openat, AT_FDCWD, section_path, O_RDONLY);
&nbsp; &nbsp;&nbsp;if&nbsp;(fd <&nbsp;0)&nbsp;return&nbsp;-3;

&nbsp; &nbsp;&nbsp;// [4] 对版本 >= 29 (Android 10+) 进行额外的内存保护检查
&nbsp; &nbsp;&nbsp;if&nbsp;(version >=&nbsp;29) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取 ELF 段的内存地址
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(sub_338A0(path_param, addr, &mem_region,&nbsp;0)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 确保代码段权限为 r-x (不可写)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mprotect(mem_region.start, mem_region.size, PROT_READ | PROT_EXEC);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// [5] mmap 文件到内存
&nbsp; &nbsp;&nbsp;void&nbsp;*file_map = mmap(NULL, file_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd,&nbsp;0);

&nbsp; &nbsp;&nbsp;// [6] 使用 process_vm_readv 读取运行时内存
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; syscall 270 = __NR_process_vm_readv
&nbsp; &nbsp;&nbsp;struct&nbsp;iovec&nbsp;local&nbsp;= {&nbsp;.iov_base = &result_buf, .iov_len =&nbsp;16&nbsp;};
&nbsp; &nbsp;&nbsp;struct&nbsp;iovec&nbsp;remote&nbsp;= {&nbsp;.iov_base = runtime_addr, .iov_len =&nbsp;16&nbsp;};
&nbsp; &nbsp;&nbsp;pid_t&nbsp;pid = getpid();
&nbsp; &nbsp;&nbsp;ssize_t&nbsp;nread = syscall(__NR_process_vm_readv, pid, &local,&nbsp;1, &remote,&nbsp;1,&nbsp;0);

&nbsp; &nbsp;&nbsp;// [7] 比较文件中的代码与运行时内存中的代码
&nbsp; &nbsp;&nbsp;if&nbsp;(nread ==&nbsp;16) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(memcmp(file_map + offset, result_buf,&nbsp;16) !=&nbsp;0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 代码被修改! (检测到 inline hook)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1; &nbsp;// tampered
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; munmap(file_map, file_size);
&nbsp; &nbsp;&nbsp;return&nbsp;0; &nbsp;// clean
}

为什么 patch 后不影响 Frida

  • 此函数检测的是代码段是否被修改 (inline hook)
  • Frida 的 ptrace attach 不会修改 .text 段代码
  • 只有使用 Interceptor.replace() / Interceptor.attach() 修改指令时才会触发
  • Frida 刚 attach 时还没来得及 hook 任何函数,而 0x5f388 的 TracerPid 检测已经先触发了 kill

反检测技巧

| 技巧 | 说明 | | — | — | | syscall(__NR_openat) | 直接系统调用打开文件,绕过 Frida 对 open 的 hook | | syscall(__NR_process_vm_readv) | 直接系统调用读内存,绕过 Frida 对 read/memcpy 的 hook | | mprotect(PROT_READ|PROT_EXEC) | 确保代码段不可写,增加 hook 难度 |


0x5f8c4 — sub_5F8C4: 线程/进程枚举检测

功能

枚举进程的线程和相关进程信息,检查是否存在 Frida 注入的线程。

调用路径

  1. 从 sub_37DA8 (主检测调度器) 直接调用 — 作为独立检测模块
  2. 从 sub_5F694 的栈保护触发 — 作为反篡改响应

sub_5F694 (调用者): /proc/self/maps 重映射保护

// sub_5F694: maps 自修复 + 线程检测触发
__int64&nbsp;sub_5F694()&nbsp;{
&nbsp; &nbsp;&nbsp;unsigned&nbsp;int&nbsp;base_addr = sub_2F0F0(); &nbsp;// 获取 SO 基址

&nbsp; &nbsp;&nbsp;// [1] 获取 SO 文件路径
&nbsp; &nbsp;&nbsp;char&nbsp;so_path[256];
&nbsp; &nbsp; sub_5DEE4(1, so_path);

&nbsp; &nbsp;&nbsp;// [2] 打开 /proc/self/maps
&nbsp; &nbsp; FILE *fp = fopen("/proc/self/maps",&nbsp;"r");
&nbsp; &nbsp;&nbsp;char&nbsp;*line =&nbsp;malloc(2048);

&nbsp; &nbsp;&nbsp;// [3] 解析 maps 查找当前 SO 的内存映射范围
&nbsp; &nbsp;&nbsp;while&nbsp;(fgets(line,&nbsp;2048, fp)) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;start, end;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sscanf(line,&nbsp;"%lx-%lx", &start, &end);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 找到 SO 的映射区域
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(start == so_base_in_maps) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; remap_offset = so_base_in_maps - base_addr;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;free(line);
&nbsp; &nbsp; fclose(fp);

&nbsp; &nbsp;&nbsp;// [4] 如果发现映射偏移,重新映射 SO 的代码段
&nbsp; &nbsp;&nbsp;if&nbsp;(remap_offset) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;fd = open(so_path, O_RDONLY);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(fd >=&nbsp;1) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// mmap 原始 SO 文件覆盖当前内存映射
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;void&nbsp;*new_map = mmap(remap_offset, base_addr,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PROT_READ|PROT_WRITE, MAP_PRIVATE, fd,&nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *new_map =&nbsp;0; &nbsp;// 清空首字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 恢复为只读+可执行
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mprotect(remap_offset, base_addr, PROT_READ|PROT_EXEC);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(fd);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
}

sub_5F8C4 检测代码 (简化)

// sub_5F8C4: 线程枚举检测
__int64&nbsp;sub_5F8C4(__int64 a1)&nbsp;{
&nbsp; &nbsp;&nbsp;int&nbsp;target_pid = *(int*)(a1 +&nbsp;8);
&nbsp; &nbsp;&nbsp;int&nbsp;scan_mode = *(int*)(a1);

&nbsp; &nbsp;&nbsp;// [1] 初始化检测缓冲区
&nbsp; &nbsp;&nbsp;char&nbsp;thread_info[512];
&nbsp; &nbsp;&nbsp;memset(thread_info,&nbsp;0,&nbsp;sizeof(thread_info));

&nbsp; &nbsp;&nbsp;// [2] 打开 /proc/<pid>/task/ 目录 (使用 opendir/readdir)
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; 枚举所有线程 TID

&nbsp; &nbsp;&nbsp;// [3] 对每个 TID:
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; - 读取 /proc/<pid>/task/<tid>/status
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; - 读取 /proc/<pid>/task/<tid>/comm
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; - 检查线程名是否包含 Frida 特征:
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; &nbsp; "gmain", "gdbus", "gum-js-loop", "frida-*" 等

&nbsp; &nbsp;&nbsp;// [4] 读取文件内容
&nbsp; &nbsp;&nbsp;int&nbsp;fd = open(proc_path, O_RDONLY);
&nbsp; &nbsp;&nbsp;char&nbsp;buf[16];
&nbsp; &nbsp;&nbsp;ssize_t&nbsp;n = read(fd, buf,&nbsp;16);
&nbsp; &nbsp;&nbsp;if&nbsp;(n >=&nbsp;1) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int&nbsp;tid = atoi(buf);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(tid >&nbsp;0&nbsp;&& tid != getpid()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 检查该线程的详细信息
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 比对线程名与已知 Frida 线程特征
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// [5] 字符串匹配检测
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; 使用 memcmp 对比线程名
&nbsp; &nbsp;&nbsp;char&nbsp;s1[256]; &nbsp;// 读取到的线程名
&nbsp; &nbsp;&nbsp;// 与解密后的 Frida 线程名特征比较

&nbsp; &nbsp;&nbsp;// [6] 检测到则设置标志
&nbsp; &nbsp;&nbsp;// &nbsp; &nbsp; 可能触发 kill/exit (在后续检测循环中)
}

为什么 patch 后不影响 Frida

  • 线程枚举需要遍历 /proc/<pid>/task/ 下所有 TID,速度较慢
  • 0x5f388 的 TracerPid 检测是 O(1) 复杂度 (直接读一个字段),执行速度远快于线程枚举
  • Frida attach 的瞬间 TracerPid 就会被设置,0x5f388 在下一次 sleep(1) 循环就能捕获
  • 线程枚举此时可能还在初始化阶段,尚未完成扫描

0x60ffc — sub_60B70: APK Signing Block 签名校验

功能

验证 APK 签名块完整性,防止重打包。与 Frida 检测无关。

调用链

sub_605D8 (APK 文件解析)
&nbsp; &nbsp; → sub_60B70 (签名块数据处理)

sub_60894 (文件路径白名单校验)
&nbsp; &nbsp; → sub_60B70 (签名数据验证)

sub_605D8: APK Signing Block 解析

// sub_605D8: 解析 APK 签名块
__int64&nbsp;sub_605D8(unsigned&nbsp;int&nbsp;fd, _OWORD *v2_sig, _OWORD *v3_sig, _OWORD *v4_sig)&nbsp;{
&nbsp; &nbsp;&nbsp;struct&nbsp;stat&nbsp;st;
&nbsp; &nbsp; fstat(fd, &st);
&nbsp; &nbsp;&nbsp;size_t&nbsp;file_size = st.st_size;

&nbsp; &nbsp;&nbsp;// [1] mmap APK 文件
&nbsp; &nbsp;&nbsp;void&nbsp;*file_map = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd,&nbsp;0);

&nbsp; &nbsp;&nbsp;// [2] 定位 End of Central Directory (ZIP 尾部)
&nbsp; &nbsp;&nbsp;// 检查魔数: 0x06054B50 (PK\x05\x06)
&nbsp; &nbsp;&nbsp;if&nbsp;(*(uint32_t*)(file_map + file_size -&nbsp;22) !=&nbsp;0x06054B50)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;fail;

&nbsp; &nbsp;&nbsp;// [3] 定位 APK Signing Block
&nbsp; &nbsp;&nbsp;// 检查魔数: "APK Sig Block 42"
&nbsp; &nbsp;&nbsp;uint64_t&nbsp;sig_block_offset = ...;
&nbsp; &nbsp;&nbsp;if&nbsp;(memcmp(sig_block_ptr -&nbsp;16,&nbsp;"APK Sig Block 42",&nbsp;16) !=&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;goto&nbsp;fail;

&nbsp; &nbsp;&nbsp;// [4] 解析签名块中的条目
&nbsp; &nbsp;&nbsp;while&nbsp;(entry < sig_block_end) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;id = entry->id;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;switch&nbsp;(id) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;0xF05368C0: &nbsp;// (-262969152) V2 签名
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sub_60B70(entry->data, v3_sig); &nbsp;// 处理 V2 签名数据
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;0x1B93AD61: &nbsp;// (462663009) V3 签名
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sub_60B70(entry->data, v4_sig); &nbsp;// 处理 V3 签名数据
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;0x7109871A: &nbsp;// (1896449818) V1 签名
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sub_60B70(entry->data, v2_sig); &nbsp;// 处理 V1 签名数据
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; munmap(file_map, file_size);
}

sub_60894: 文件路径安全校验

// sub_60894: 检查文件路径是否在白名单内
__int64&nbsp;sub_60894(const&nbsp;char&nbsp;*path,&nbsp;int&nbsp;fd)&nbsp;{
&nbsp; &nbsp;&nbsp;// 白名单路径前缀:
&nbsp; &nbsp;&nbsp;char&nbsp;whitelist_1[] =&nbsp;"/data/data/";
&nbsp; &nbsp;&nbsp;char&nbsp;whitelist_2[] =&nbsp;"/data/user/";
&nbsp; &nbsp;&nbsp;char&nbsp;whitelist_3[] =&nbsp;"/storage/";
&nbsp; &nbsp;&nbsp;char&nbsp;whitelist_4[] =&nbsp;"/sdcard";

&nbsp; &nbsp;&nbsp;// 逐一匹配路径前缀
&nbsp; &nbsp;&nbsp;if&nbsp;(starts_with(path, whitelist_1))&nbsp;return&nbsp;0; &nbsp;// 安全
&nbsp; &nbsp;&nbsp;if&nbsp;(starts_with(path, whitelist_2))&nbsp;return&nbsp;0; &nbsp;// 安全
&nbsp; &nbsp;&nbsp;if&nbsp;(starts_with(path, whitelist_3))&nbsp;return&nbsp;0; &nbsp;// 安全
&nbsp; &nbsp;&nbsp;if&nbsp;(starts_with(path, whitelist_4))&nbsp;return&nbsp;0; &nbsp;// 安全

&nbsp; &nbsp;&nbsp;// 非白名单路径,检查文件 UID
&nbsp; &nbsp;&nbsp;struct&nbsp;stat&nbsp;st;
&nbsp; &nbsp; fstat(fd, &st);
&nbsp; &nbsp;&nbsp;int&nbsp;expected_uid = get_app_uid();

&nbsp; &nbsp;&nbsp;// UID 2000 = shell (adb),expected_uid = app UID
&nbsp; &nbsp;&nbsp;if&nbsp;(st.st_uid !=&nbsp;2000&nbsp;&& st.st_uid != expected_uid) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1; &nbsp;// 可疑文件!
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;return&nbsp;0;
}

sub_60B70: NEON 加速签名数据处理

// sub_60B70: 使用 ARM NEON 指令处理签名块数据
// 大量 NEON 向量运算 (uint16x4_t, int8x16_t, int32x4_t)
// 用于高效的数据解析和 hash 计算
// 这是纯数据处理函数,不涉及任何安全检测逻辑

为什么 patch 后不影响 Frida

  • 完全不涉及 Frida 检测,这是 APK 完整性校验
  • 防止的是 APK 被重签名/重打包,不是运行时注入
  • 即使 APK 签名校验失败,也不会触发 kill(pid, SIGKILL)

整体安全架构

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; libDexH*lper.so 安全检测架构
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ================================

&nbsp; &nbsp; ┌─────────────────────────────────────────────────────┐
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;主检测调度器 sub_37DA8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; │ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(0x37DA8, size: 0x9CB8) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; └──────┬──────────┬──────────┬──────────┬──────────┬───┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;v
&nbsp; &nbsp; ┌──────────┐┌──────────┐┌──────────┐┌──────────┐┌──────────┐
&nbsp; &nbsp; │ 0x5f388 &nbsp;││ 0x57278 &nbsp;││ 0x53d30 &nbsp;││ 0x5f8c4 &nbsp;││ 0x60ffc &nbsp;│
&nbsp; &nbsp; │ TracerPid││ &nbsp;Maps &nbsp; &nbsp;││ &nbsp;代码 &nbsp; &nbsp;││ &nbsp;线程 &nbsp; &nbsp;││ &nbsp;APK &nbsp; &nbsp; │
&nbsp; &nbsp; │ + Frida &nbsp;││ &nbsp;扫描 &nbsp; &nbsp;││ &nbsp;完整性 &nbsp;││ &nbsp;枚举 &nbsp; &nbsp;││ &nbsp;签名 &nbsp; &nbsp;│
&nbsp; &nbsp; │ cmdline &nbsp;││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;││ &nbsp;校验 &nbsp; &nbsp;││ &nbsp;检测 &nbsp; &nbsp;││ &nbsp;校验 &nbsp; &nbsp;│
&nbsp; &nbsp; │ 匹配 &nbsp; &nbsp; ││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; ├──────────┤├──────────┤├──────────┤├──────────┤├──────────┤
&nbsp; &nbsp; │ pthread &nbsp;││ pthread &nbsp;││ 直接调用 ││ pthread/ ││ 直接调用 │
&nbsp; &nbsp; │ 无限循环 ││ 一次扫描 ││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;││ callback ││ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; ├──────────┤├──────────┤├──────────┤├──────────┤├──────────┤
&nbsp; &nbsp; │kill+exit&nbsp;││ 设置标志 ││ 返回结果 ││ 设置标志 ││ 返回结果 │
&nbsp; &nbsp; │(直接终杀)││ (间接) &nbsp; ││ (间接) &nbsp; ││ (间接) &nbsp; ││ (间接) &nbsp; │
&nbsp; &nbsp; └──────────┘└──────────┘└──────────┘└──────────┘└──────────┘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;↑
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;│
&nbsp; &nbsp; 唯一直接导致
&nbsp; &nbsp; Frida 进程退出
&nbsp; &nbsp; 的检测点!!!

通用反检测技巧汇总

| 技巧 | 使用位置 | 说明 | | — | — | — | | 字符串栈上构造 | 0x5f388, 0x57278 | 所有敏感字符串 (/procTracerPidfrida) 都在栈上构造,不存在于 .rodata | | 字符串加密/混淆 | 全局 | “buvfvv0kqgq1z” 等混淆字符串,运行时解密 | | 直接系统调用 | 0x5f388, 0x53d30 | linux_eabi_syscall(__NR_openat/kill/...) 绕过 libc hook | | 函数名混淆 | 全局 | p5lS05SSlS5SISl5l5$5I5I5I5lSISIS5SISI5l5IS$... 等混淆导出符号 | | 多线程并行检测 | 0x5f388, 0x57278 | pthread_create 创建独立检测线程 | | 线程创建重试 | 0x5EC84, 0x53FE4 | 创建失败重试 30 次,确保检测线程启动 | | 栈保护 (canary) | 所有函数 | _ReadStatusReg(TPIDR_EL0) + canary check,检测栈溢出攻击 | | NEON 向量运算 | 0x53FE4, 0x60B70 | 使用 SIMD 指令加速数据处理,增加逆向难度 | | 手动字符串匹配 | 0x5f388 | 不使用 strstr/strcmp,手动逐字节比较 | | process_vm_readv | 0x53d30 | 通过内核接口读取自身内存,绕过用户空间 hook |

Frida 绕过建议 (安全研究用途)

  1. 仅 patch 0x5f388 (sub_5ED80) 即可阻止 Frida 被检测杀死
  2. 原因: 其他检测点要么不直接 kill,要么速度慢于 TracerPid 检测
  3. 如需完整绕过所有检测,还需处理:
  • 0x57278: Maps 中的 Frida 库名 (可用 frida-server -l 0.0.0.0:1234 改名)
  • 0x5f8c4: Frida 线程名 (可重命名 frida 线程)
  • 0x53d30: 代码完整性 (避免 patch .text 段或使用 Stalker 代替 Interceptor)

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:小白技术社 吾爱小白 吾爱小白《全AI分析DexH*lper Anti-Frida深度检测机制》

评论:0   参与:  0