📚 网络安全 Kali 学习系列
模块 0 基础阶段 ✅ 已完成
01-18 Linux → 网络 → 密码学 → 编程 → Docker
模块 1 信息收集 ✅ 已完成 | 模块 2 Web渗透 ✅ 已完成
19-31 扫描 → 漏洞 → sqlmap → XSS → WAF绕过
模块 3 代码审计 ✅ 已完成
32-37 审计入门 → Node.js → PHP → Java → Python → CVE复现
模块 4 漏洞利用与后渗透 ✅ 已完成
38-43 Metasploit → Payload → 提权 → 维持 → 免杀
模块 4-2 逆向工程基础(进行中)
44 汇编基础与文件格式 ✓ | 45 静态分析工具 ✓
46 动态调试:GDB 与反调试(当前篇)
47 恶意软件静态分析 | 48 恶意软件动态分析
动态调试:GDB 与反调试识别
网络安全 Kali 学习系列 · 第 46 篇 · 模块 4-2 逆向工程基础

读完本篇你将能:使用 GDB 在 Linux 下设置断点、单步执行、查看寄存器和内存,掌握 PEDA/GEF 插件增强调试体验,理解 6 种常见反调试技术(ptrace、时间差、IsDebuggerPresent、NtQueryInformationProcess、INT 3 检测、输出调试字符串),并能识别和绕过基础反调试手段。

上一篇学了静态分析——不运行程序,靠工具"看"代码。但静态分析有局限:遇到混淆、加壳、动态解密的代码时,反编译器给出的伪代码可能毫无意义。动态调试解决这个问题——让程序真的跑起来,在运行时观察每一步的状态。GDB 是 Linux 下最强大的调试器,而反调试则是恶意软件对抗分析的常用手段。

目录

第一章 GDB 基础:启动、断点与单步
第二章 寄存器、内存与调用栈
第三章 GDB 增强:PEDA 与 GEF 插件
第四章 反调试技术全景
第五章 反调试绕过方法
第六章 实战:分析带反调试的程序
第七章 分级练习

第一章 GDB 基础:启动、断点与单步

GDB(GNU Debugger)是 Linux 下的标准调试器,支持 C/C++、Go、Rust 等多种编译语言。逆向分析中,GDB 用来在程序运行时控制执行流程、检查内存状态、修改变量值。Kali Linux 默认已安装 GDB。

1.1 启动与加载

Shell · GDB 启动方式 gdb_start.sh
1
2
3
4
5
6
7
8
9
10
11
# 方式 1:直接加载程序
gdb ./challenge
 
# 方式 2:附加到正在运行的进程
gdb -p 12345 # PID = 12345
 
# 方式 3:带参数启动
gdb --args ./challenge arg1 arg2
 
# 进入 GDB 后,输入 run (r) 启动程序
(gdb) run

1.2 断点与执行控制

断点(Breakpoint)是调试的核心——让程序在指定位置停下来,然后你可以检查状态。GDB 支持函数断点、地址断点、条件断点等多种类型。

GDB · 断点与执行命令 breakpoints.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 设置断点
b main # 在 main 函数入口断
b *0x401234 # 在指定地址断
b check if $eax == 0 # 条件断点:eax=0 时才断
info breakpoints # 查看所有断点
d 1 # 删除第 1 个断点
 
# 执行控制
r # run 运行/重启程序
c # continue 继续执行
ni # next instruction 步过(不进入函数)
si # step instruction 步入(进入函数)
finish # 运行到当前函数返回
kill # 终止当前程序
q # 退出 GDB
💡 小贴士
GDB 命令支持缩写:b = break,r = run,c = continue,ni / si = next/step instruction。逆向分析中 90% 的时间在用这几个命令,务必记熟。

第二章 寄存器、内存与调用栈

程序断下来后,你需要知道"现在是什么状态"。寄存器、内存和调用栈是三个核心观察维度——寄存器告诉 CPU 正在做什么,内存存储数据和代码,调用栈记录函数调用关系。

2.1 查看寄存器与内存

GDB · 检查状态 inspect.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 寄存器
info registers (i r) # 查看所有通用寄存器
i r eax ebx eip # 查看指定寄存器
set $eax = 0x41414141 # 修改寄存器值
 
# 内存(x 命令: examine)
x/10x $esp # 以 16 进制显示栈顶 10 个字
x/s 0x402000 # 以字符串格式查看地址内容
x/20i $eip # 反汇编 eip 后 20 条指令
x/1gx $rbp # 以 8 字节(giant word)查看
 
# 调用栈
bt (backtrace) # 查看调用栈
bt full # 详细栈帧(含局部变量)
frame 2 (f 2) # 切换到第 2 层栈帧
info locals # 查看当前栈帧局部变量
info args # 查看函数参数

2.2 观察点(Watchpoint)

观察点是一种特殊断点——不是在某条指令上断,而是在某个内存地址的值被修改时断。当你想知道"谁改了这个变量"时,观察点是最直接的工具。

GDB · 观察点 watchpoints.txt
1
2
3
4
5
6
7
# 写入观察:变量值被修改时中断
watch variable_name # 监视变量
watch *0x7fffffffda80 # 监视指定地址
 
# 读观察:变量被读取时中断
rwatch *0x404000 # 读观察点
awatch *0x404000 # 读写都触发

第三章 GDB 增强:PEDA 与 GEF 插件

原生 GDB 的界面比较朴素——每次都要手动输入 i r、x/20x $esp 查看状态。PEDA 和 GEF 是两个流行的 GDB 增强插件,可以在每次断点时自动显示寄存器、栈、反汇编,大幅提升调试效率。

3.1 PEDA 安装与使用

Shell · PEDA 安装 install_peda.sh
1
2
3
4
5
6
7
8
9
10
# 克隆 PEDA 仓库
git clone https://github.com/longld/peda.git ~/peda
 
# 配置 GDB 自动加载 PEDA
echo "source ~/peda/peda.py" >> ~/.gdbinit
 
# 启动 GDB,会自动加载 PEDA
gdb ./challenge
 
# PEDA 会在每次断点时自动显示:寄存器 + 反汇编 + 栈

3.2 PEDA 常用增强命令

命令 功能 用途
aslr 查看/切换 ASLR 状态 漏洞利用时关闭 ASLR
checksec 检查二进制安全保护 查看 NX/Canary/PIE/RELRO
searchmem 在内存中搜索字符串/字节 找 gadget、找字符串
ropgadget 查找 ROP gadget ROP 链构造
pattern_create 生成唯一字符串模式 计算溢出偏移
vmmap 显示内存映射 查看各段地址和权限
💡 小贴士
GEF(GDB Enhanced Features)是另一个流行的 GDB 插件,功能比 PEDA 更丰富,支持更多架构(ARM、MIPS 等)和高级功能(heap 分析、fmtstr 辅助等)。PEDA 适合 x86/x64 入门,GEF 适合进阶。两者不能同时加载,需要在 ~/.gdbinit 中只 source 一个。

第四章 反调试技术全景

反调试(Anti-Debugging)是程序检测自己是否被调试器附加的技术。恶意软件广泛使用反调试来拖延分析时间——如果检测到调试器存在,程序会退出、崩溃或执行错误逻辑。理解反调试的原理,才能有效绕过。

4.1 ptrace 检测(Linux)

Linux 下最经典的反调试手段。一个进程只能被一个 ptracer 跟踪。如果程序自己调用 ptrace(PTRACE_TRACEME),如果返回 -1 说明已经被调试了(调试器已经在跟踪它)。

C · ptrace 反调试 ptrace_check.c
1
2
3
4
5
6
7
8
9
10
11
12
13
// Linux ptrace 反调试检测
#include <stdio.h>
#include <sys/ptrace.h>
 
int is_debugged() {
  // 尝试自我 ptrace
  if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
    return 1; // 已被调试
  }
  return 0;
}
 
// 绕过:在 GDB 中 hook ptrace 返回 0

4.2 时间差检测

调试时单步执行会让程序运行变慢。通过测量两段代码之间的执行时间,如果时间差超过阈值,就认为有调试器介入。常用函数:clock_gettime、rdtsc 指令(读取 CPU 时间戳计数器)。

ASM · RDTSC 时间检测 rdtsc_check.asm
1
2
3
4
5
6
7
8
9
10
11
12
; x86 RDTSC 时间戳检测
rdtsc ; 第一次读时间戳,EDX:EAX = 64位周期数
mov ebx, eax ; 保存低 32 位
 
; 执行一段被测代码
call some_function
 
rdtsc ; 第二次读时间戳
sub eax, ebx ; 计算差值
cmp eax, 0x1000 ; 超过阈值?
ja debugged ; 是 → 被调试了
; 绕过:在两次 rdtsc 之间设断点并修改 eax

4.3 Windows 常见反调试 API

Windows 平台有一套专门的调试 API,恶意软件常直接调用这些 API 检测调试器。最常见的有三个:IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess。

反调试手段 原理 绕过方法
IsDebuggerPresent 读取 PEB 的 BeingDebugged 标志位 修改 PEB+2 字节为 0
CheckRemoteDebuggerPresent 检查指定进程是否被调试 Hook 该函数返回 FALSE
NtQueryInformationProcess 查询 ProcessDebugPort 等信息 Hook ntdll 中的该函数
OutputDebugString 无调试器时调用会设置错误码 忽略返回值 / Hook
INT 3 扫描 扫描代码段中是否有 0xCC(断点指令) 硬件断点代替软件断点
父进程检测 检查父进程是否为 explorer.exe 从 explorer 启动调试目标

第五章 反调试绕过方法

反调试的绕过思路分为三层:(1) 静态 Patch——直接修改二进制中的反调试检测逻辑;(2) 动态 Hook——在运行时 Hook 反调试函数,让它返回"未被调试";(3) 调试器功能——利用调试器本身的特性(如硬件断点、脚本)绕过检测。

5.1 GDB 脚本绕过 ptrace

GDB · 绕过 ptrace 检测 bypass_ptrace.gdb
1
2
3
4
5
6
7
8
9
10
11
# 方法 1:Hook ptrace 函数,让它总是返回 0
break ptrace
commands
  set $rax = 0 # 返回值设为 0(成功)
  return # 直接返回,不执行真实 ptrace
  continue
end
 
# 方法 2:修改检测跳转(把 jz 改成 jmp 或 nop)
# 找到检测后的条件跳转指令,修改为无条件跳转
set {char}0x401234 = 0x90 # nop 掉 jne 的判断

5.2 硬件断点绕过 INT 3 扫描

软件断点(GDB 默认的 break)是通过把代码改成 0xCC(INT 3 指令)实现的。如果恶意软件扫描代码段中的 0xCC 字节,就能发现断点。硬件断点(Hardware Breakpoint)使用 CPU 的 DRx 调试寄存器,不修改代码,因此不会被扫描发现。

GDB · 硬件断点 hbreak.gdb
1
2
3
4
5
6
7
# 硬件执行断点(不修改代码)
hbreak *0x401234 # 在指定地址设硬件断点
hbreak main # 在函数入口设硬件断点
 
# 注意:x86 CPU 只有 4 个调试寄存器
# 同时最多 4 个硬件断点
# 硬件断点也能被检测(检查 DRx 寄存器值)

第六章 实战:分析带反调试的程序

本节写一个带 ptrace 反调试的小程序,编译后用 GDB 分析并绕过检测,最后找到正确密码。这是一个完整的"检测→定位→绕过→分析"流程。

6.1 反调试程序源码

C · 带反调试的验证程序 antidebug.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <sys/ptrace.h>
 
int check_password(char *input) {
  char key[] = "r3v3rs3_1s_fun";
  return strcmp(input, key) == 0;
}
 
int main() {
  // 反调试检测
  if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
    printf("Debugger detected!\n");
    return 1;
  }
  char buf[64];
  scanf("%63s", buf);
  printf(check_password(buf) ? "WIN\n" : "FAIL\n");
  return 0;
}

6.2 GDB 绕过并分析

GDB · 完整绕过流程 bypass_flow.gdb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 步骤 1:编译并加载
gcc -o antidebug antidebug.c -s
gdb ./antidebug
 
# 步骤 2:Hook ptrace,让它返回 0
break ptrace
commands
  set $rax = 0
  return
  continue
end
 
# 步骤 3:运行程序,断在 check_password,看 key 字符串
break strcmp
run # 输入任意密码后会断在 strcmp
x/s $rsi # 第二个参数就是 key = "r3v3rs3_1s_fun"
💡 小贴士
真实恶意软件的反调试远比这个例子复杂——可能有几十种检测手段嵌套使用,还会用代码混淆、控制流平坦化来隐藏检测逻辑。逆向分析的思路是通用的:(1) 先定位反调试点(strings 找 "debugger"、import 表找 ptrace/IsDebuggerPresent);(2) 逐个绕过(Hook、Patch、硬件断点);(3) 逐步深入分析核心逻辑。

第七章 分级练习

🟢 基础验证

任务:在 Kali 上用 GDB 调试 /bin/ls。(1) 在 main 函数设断点,运行程序,查看断下时的寄存器值;(2) 用 bt 查看调用栈,用 x/20i $rip 反汇编当前位置;(3) 单步执行 5 条指令(ni),观察寄存器变化;(4) 用 x/s 查看 argv[0] 的字符串内容。

🟡 组合应用

任务:编写第六章的 antidebug.c 程序,编译为 stripped 版本。(1) 不使用反调试绕过,直接用 GDB 运行,观察程序输出什么;(2) 使用 GDB 的 ptrace hook 方法绕过反调试,确认程序正常运行;(3) 在反调试被绕过的前提下,用 GDB 找出正确密码(提示:在 strcmp 处设断点,查看第二个参数);(4) 验证密码正确性。记录每一步的 GDB 命令和输出截图。

🔴 开放挑战

任务:找一个 Crackme(带反调试的,推荐 crackmes.one 上搜索 "anti-debug" 标签),完成完整分析:(1) 用 strings 查找反调试相关字符串和 API;(2) 用 GDB 定位反调试点;(3) 选择合适的绕过方法(Hook / Patch / 硬件断点);(4) 绕过反调试后,分析验证逻辑,找出正确密码;(5) 编写一份分析报告,包含反调试点定位过程、绕过方法选择理由、密码推导过程。

进阶方向:尝试使用 x64dbg(Windows 调试器)分析 Windows 平台的反调试程序。x64dbg 的界面更直观,有图形化的反调试插件(ScyllaHide),可以对比 Linux 和 Windows 反调试技术的异同。

知识回顾

GDB 调试器 断点 / 单步 寄存器与内存 调用栈 backtrace 观察点 watchpoint PEDA / GEF ptrace 检测 RDTSC 时间差 IsDebuggerPresent INT 3 扫描 硬件断点 反调试绕过

下一篇预告

本篇学了动态调试的基础和反调试对抗。下一篇进入恶意软件静态分析——把前面学到的静态分析工具(Ghidra、radare2)和反调试知识结合起来,专门分析恶意软件样本。恶意软件通常经过加壳、混淆、反调试处理,分析流程和普通程序完全不同。我们将学习恶意软件的常见特征、静态分析方法论、以及如何安全地处理恶意样本。

关注本公众号,持续获取网络安全 Kali 学习系列更新。如果觉得有帮助,欢迎点赞、在看、分享给同样在学习网络安全的朋友。