Skip to content

Android 反调试检测与绕过(VMP 场景) ​

场景 ​

对 VMP 加固应用做动态分析(trace 解释器、dump 内存)时,Frida/调试器一挂上应用就闪退或卡死。VMP 壳把反调试与反 Frida 检测内嵌在解释器/守护进程里,必须先过检测才能继续 trace 路线(04-vmp-dump-trace-recovery)。

输入信号 ​

  • Frida attach/spawn 后进程立即退出,或界面卡死
  • 启动时有第二个进程(守护进程)互相 ptrace
  • logcat 出现 tracerpid / frida / detected 字样
  • 应用在无 hook 时正常,一旦有 /proc/self/maps 出现 frida 相关映射就退出
  • 反编译/反汇编看到大量 open("/proc/self/status")、ptrace、clock_gettime 调用

常见检测与绕过对照表 ​

检测手段原理绕过
TracerPid读 /proc/self/status 的 TracerPid: 字段,非 0 = 被调试hook open/read 伪造文件内容;或 spawn 早注入让检测点发生在注入之后
ptrace 双进程守护守护进程 ptrace 主进程,检测自身/对方是否被附加先杀/停守护进程(见下);patch ptrace 返回值
/proc/self/maps Frida 检测扫描 maps 找 frida-agent/gadget 字符串frida-server 改名/换端口(见下);maps hook 过滤
frida 默认端口扫描连 27042 端口探测 frida-serverfrida-server -l 0.0.0.0:xxxxx 换端口
时间差检测clock_gettime(CLOCK_MONOTONIC) 两次采样差过大hook clock_gettime 固定返回值;硬断不停检测区
syscall 直读绕过 libc 直接 syscall 读状态hook 内核层(需 root + kprobe),或模拟器/定制 ROM 绕开
反 unidbg/模拟器检测 syscall 指纹、时间单调性unidbg 的 setTime/hook 处理;成本较高,优先真机

绕过一:frida-server 换端口 + spawn 早注入 ​

bash
# 1. 设备上启动 frida-server 指定端口
adb shell "su -c '/data/local/tmp/frida-server -l 0.0.0.0:28999 &'"

# 2. 桌面端连接指定端口 + spawn 模式(早注入, 先于壳的检测点)
# frida 命令行:
frida -H 127.0.0.1:28999 -f com.target.app --no-pause -l bypass.js
# Python:
#   device = frida.get_device_manager().add_remote_device("127.0.0.1:28999")
#   session = device.attach("com.target.app", realm="native")

spawn 模式下 Frida 在 Application 创建前注入,能覆盖 attachBaseContext 阶段的检测。

绕过二:TracerPid 伪造 ​

javascript
// bypass.js: hook open/read 伪造 /proc/self/status 的 TracerPid
var openPtr = Module.findExportByName("libc.so", "open")
var readPtr = Module.findExportByName("libc.so", "read")
var fake = "TracerPid:\t0".replace("0", "0")

Interceptor.attach(openPtr, {
    onEnter: function (args) {
        this.path = args[0].readCString()
    },
    onLeave: function (ret) {
        if (this.path && this.path.indexOf("status") !== -1 && this.path.indexOf("self") !== -1) {
            this.hit = true
        }
    }
})
Interceptor.attach(readPtr, {
    onEnter: function (args) {
        this.hit = this.hit || false
    },
    onLeave: function (ret) {
        // 简化: 直接替换 read 缓冲中的 TracerPid 行为 0
        if (this.hit) {
            var buf = args[1]
            var data = buf.readUtf8String(Math.min(ret.toInt32(), 512))
            if (data && data.indexOf("TracerPid:") !== -1) {
                data = data.replace(/TracerPid:\s*\d+/, "TracerPid:\t0")
                buf.writeUtf8String(data)
            }
            this.hit = false
        }
    }
})

更稳的做法是只 hook fopen/fgets(应用通常用 ifstream/fgets 读 status),按实际实现调整。

绕过三:ptrace 双进程守护 ​

流程:
1. 启动应用 → 观察进程树: 主进程 + 守护进程(通常低权限/不同 uid)
2. adb shell ps -A | grep <pkg> 确认守护进程 pid
3. 方案 A: su -c "kill -9 <守护pid>" 后立即 attach 主进程
   (守护死后主进程的 ptrace 保护消失; 部分壳主进程也会自检守护存活, 需同时 patch)
4. 方案 B: Frida 早注入后 hook ptrace 调用, 伪造失败返回:
   Interceptor.attach(Module.findExportByName("libc.so", "ptrace"), {
       onEnter: function (args) { this.req = args[0].toInt32() },
       onLeave: function (ret) {
           if (this.req === 16 /* PTRACE_ATTACH */ || this.req === 0) {
               ret.replace(-1)   // 让壳以为 ptrace 失败
           }
       }
   })
5. 方案 C: 定制 ROM (Youpk 思路) 在系统层对目标进程豁免 ptrace 检测

绕过四:时间检测与时钟固定 ​

javascript
// hook clock_gettime 固定单调时钟
Interceptor.attach(Module.findExportByName("libc.so", "clock_gettime"), {
    onEnter: function (args) {
        this.clk = args[0].toInt32()
    },
    onLeave: function (ret) {
        if (this.clk === 1 /* CLOCK_MONOTONIC */) {
            // 写固定时间戳, 消除单步/断点延迟特征
        }
    }
})

攻击链 ​

spawn 早注入 + frida-server 换端口 → 加载 bypass 脚本
→ TracerPid/时间/maps 检测逐个确认并绕过 (logcat + 是否闪退判断)
→ 双进程守护 → 杀守护或 hook ptrace
→ 稳定挂载后 → 转 04-vmp-dump-trace-recovery 的 trace 路线
→ 全程记录: 哪个检测点被哪个 bypass 覆盖, 剩余未覆盖检测清单

MCP 工具映射 ​

AI Agent 可调用以下 MCP 工具自动完成或加速上述攻击链步骤:

攻击链步骤MCP 工具说明
部署并启动 frida-serverandroid_frida_ensure_server在 MuMu/root 设备部署 frida-server(可指定 arch/version)
运行 bypass 脚本android_frida_run_script对目标进程跑一次性 Frida JS,收集 send 消息(TracerPid/时间 bypass 脚本)
解密/脱壳 recipe 附带反调试观察android_crypto_unpack_recipe含反调试相关模板,观察 dlopen/RegisterNatives/解密证据
强制停止重试android_force_stop绕过失败后清理进程状态重新开始

证据与验证闭环 ​

  • 记录包名、进程树(主进程+守护进程 pid/uid)、frida-server 版本与端口、Android 版本。
  • 每个检测点绕过绑定:检测代码位置(so 偏移/API 名)、bypass 脚本、绕过前后行为差异(闪退 vs 稳定运行)。
  • 未被绕过的检测点必须列为"剩余风险",不得默认全过。
  • 保存 logcat、Frida 输出、脚本版本到 exports/android/,可重放复现。

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用