逆向工程实战手册 第 7 课

第七课:会咬人的钟——反调试与对抗

对应原书第 8 章《反调试与反分析对抗》


第七天,陈师傅从柜台深处捧出一只上了锁的木盒。打开,里面是一只黑乎乎的老钟——钟面被刮花过,齿轮上缠着铜丝,最诡异的是,它旁边贴着一张纸条:"请勿拆开"。

"这只钟,会咬人。"陈师傅说。

小林后退半步:"……怎么咬?"

"你伸手进去,它就'咔哒'一声夹住你。"陈师傅指了指齿轮缝,"它里面装了机关:只要感觉到'有人拆它',就锁死。而且它会假装'没坏'——你不动它,它走得好好的;你一碰,它立刻停摆装死。"

"这不就是我们昨天学的……那个会闪退的 App 吗?"

"比那个狠。那个 App 是'感觉到被动手脚就自杀'。这只钟是'感觉到被拆就锁死',而且它还会骗你——表面一切正常,让你以为它真坏了。"陈师傅顿了顿,"我们管这种机关,叫反调试。意思是:'防着你检查它'。"

"那……怎么办?"

"先搞清楚它怎么'感觉到被拆'的。"陈师傅拿起放大镜,"它一定有'传感器'。找到传感器,绕过去,它就成了哑巴。"


课程正文

反调试(Anti-Debugging) = 程序检测"是否有人在分析自己",一旦发现就改变行为(闪退/死循环/装正常)。

程序的"传感器"(常见检测手段)

1. 检查"有没有调试器"(API 检测)
   程序问系统: "有人在跟踪我吗?"
   Windows: IsDebuggerPresent / PEB 标志
   Linux: ptrace(自己跟踪自己,让别人无法再跟踪)

2. 检查"时间"(时序检测)
   被调试时程序跑得慢
   程序测量两次时间差 → 太慢 = 被调试

3. 检查"环境"(沙箱/虚拟机检测)
   程序问: "我是跑在真电脑上还是沙箱里?"
   查 CPU 特征/内存/设备

4. 检查"自己是否被改"(完整性校验)
   程序给自己做哈希 → 比对原值 → 被 patch 就发现

识别"传感器"(静态看)

# 反调试程序的特征:
strings 程序 | grep -iE "debug|ptrace|gdb|sandbox|vmware"
rabin2 -i 程序 | grep -iE "ptrace|IsDebugger|GetTickCount"

# 看到这些函数 → 程序在防调试

绕过"传感器"(动态绕)

方法 1: 让检测"失灵"(LD_PRELOAD,Linux 神器)
  写一个假的 ptrace,永远返回 0(成功)
  → 程序以为自己成功"跟踪了自己",以为没人调试
  → 用 LD_PRELOAD 加载假的库

方法 2: 用模拟器"骗"它(Qiling)
  在模拟环境里跑程序,直接 hook 掉检测函数
  → 程序以为环境一切正常

方法 3: 直接改掉检测逻辑(patch)
  找到检测函数 → 改成永远返回"正常"
  → 程序永远不会发现有人动过

实例:ptrace 检测(Linux 最常见的传感器)

// 程序里的检测逻辑(伪代码)
if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
    // 返回 -1 = 已经被别人跟踪 → 说明被调试
    exit(1);   // 自杀
}
绕过思路: 让 ptrace 永远返回 0
  → 程序以为"成功跟踪了自己"(没人调试)
  → 继续正常跑

实现: LD_PRELOAD 加载假 ptrace(返回 0)
  或: Qiling 模拟时 hook ptrace 系统调用

陈师傅的比喻

"反调试的钟,装了一圈'感应器'。你要么找到感应器剪掉它的线(patch),要么用一块'假地板'让它感应不到(模拟/替换)。记住:它咬人,是因为它有感应器——感应器就是它的命门。"


本课口诀

它会咬人,是因为有感应器。找到感应器,绕过去——它就是哑巴。

动手做

  1. 写一个带 ptrace 检测的 C 程序(或让 AI 生成),用三种方式绕过(LD_PRELOAD/Qiling/patch)。
  2. strings + rabin2 -i 识别一个"防调试"程序的检测函数。
  3. 想一想:如果程序同时装了 5 个感应器(多层检测),怎么逐个击破?(提示:分层绕过,记录每层)

想学更深的

见原书 第 8 章《反调试与反分析对抗》——94 种反分析技术大全、控制流平坦化、MBA 混淆、OLLVM 去混淆。