☰
CTF逆向题实战指南:从GDB调试到反调试绕过与网络重放
2026/10/15 17:56:02 网站建设 项目流程

简介:24~31关的CTF逆向赛题合集,面向CTF参赛者、安全入门者和逆向爱好者,覆盖逆向工程、隐写术与抓包分析等方向,很适合用来补全从反汇编到动态调试的实战经验。压缩包共352个文件,整体仅8.8MB,以196个txt说明文档、62个dll动态库、31个udd调试记录、22个exe可执行程序为主,并配chm帮助手册、pcap抓包文件、c源码和lib库,构成一套紧凑的离线逆向样本库。已有2492人学习下载。题目涉及静态分析、动态调试、栈帧查看、字符串混淆、内存取证、常见加密算法与隐写提取,难度呈阶梯式分布,题型覆盖面广;txt与chm文档相当于解题笔记,exe与dll可直接加载进IDA或调试器实操,pcap文件则可用来抓包分析训练。仔细啃完这八关,能系统性补全二进制格式识别、汇编阅读、动态调试及工具链使用等关键技能,干货十足、密度较高。

1. 起点:为什么逆向题总是“一看就会,一跑就废”

CTF 逆向题大概是最容易让人产生“幻觉”的题型:描述里写着算法是“把输入异或三次再倒序”,你也看懂了解题思路,可一旦把程序丢到终端里跑起来,结果和你推的完全对不上。问题出在哪?出在多数新手把逆向当成“读题解谜”,而忘了逆向的本质是“伺候程序”:你得顺着它的脾性,把内存里的每一个字节都当成证据。这次要拆的这套“几个 ctf 逆向题”资源包,就是冲这个痛点去的——里面没有空谈理论,而是几道带完整解决过程记录的逆向题,覆盖了字符串比较、反调试和网络协议三个最常考的关卡。适合刚入 CTF 三个月、想找一个能反复练手的靶场的萌新,也适合那些能看懂 writeup、但自己一动手就翻车的老实人。文章后面所有命令,你都可以在 Ubuntu 虚拟机里完整跑一遍。

2. 先把底座搭稳:Ubuntu 环境、GDB 与两个常被看低的命令

这一章讲准备。不是装个虚拟机就完事,而是要让你愿意反复折腾。常见做法是准备一个 Ubuntu 22.04 虚拟机,建议内存给到 4G 以上,硬盘 40G 就够了。为什么不用 Windows 直接跑?因为绝大多数 CTF 逆向题都是 Linux ELF 文件,你在 Windows 上要么装 WSL,要么用 IDA 的远程调试,多出来的中间层只会让“玄学问题”更多。虚拟机的好处是快照就是后悔药,炸了直接回滚。我一般会把/home单独分一个区,快照回滚后题目文件还在,不至于全部白干。

2.1 为什么是 Ubuntu 而不是 Kali:选型理由与一条血泪经验

Kali 里自带一堆工具没错,但它默认用户是root,有些题对权限敏感时会表现异常,而且系统更新频繁,容易把环境和题目跑挂。Ubuntu 的干净系统反而更接近比赛或真实靶场的默认环境,也更容易装通那些并不常用的调试工具。血泪经验是:我早期拿 Kali 去调某个加了反调试的题,程序一进 GDB 就死,后来换到 Ubuntu 下用同一份二进制文件,居然没有任何异常。后面才意识到,Kali 自带的环境变量LD_PRELOAD里挂了一些监控组件,干扰了 ptrace 的返回值。所以选型不是看“谁的武器多”,而是看“谁的底噪小”。

2.2 把 GDB 用成“后悔药”:断点、内存查看与一次典型调试

GDB 是动态分析的主轴线。先安装基础组件:

sudo apt update sudo apt install -y gdb binutils file

装完之后,拿到一个未知文件先做“体检”:

file ./chall readelf -h ./chall | grep Type

file会告诉你这是 32 位还是 64 位、是否动态链接、是不是被 strip 过。readelf -h里的 Type 如果显示DYN (position-independent executable),说明开了 PIE,打断点时地址会变,这是个重要信号。我一般会先跑一遍strings,再开 GDB:

gdb -q ./chall

进去以后第一步习惯性关掉分页,避免长输出卡屏:

set pagination off info functions

用info functions看导出的函数。如果这题没被 strip,你会直接看到main、check_key这些名字,解题思路就半透明了。如果看到一堆sub_xxx,那就得老实从入口点_start开始啃。接着在某处下断点:

break *0x4012a0 run x/20i $rip

x/20i $rip是反汇编当前指令地址往后 20 条,相当于给你一张局部地图。很多新手不会用x命令,其实它比任何插件都直给:直接看内存里的裸字节,不被伪代码带跑偏。x/s 0x402010可以看字符串,x/wx $rsp可以看栈顶双字,配合si(单步进入)和ni(单步跳过),你完全能徒手跟踪一条数据是怎么被加工然后被比对的。

2.3 strings 和 objdump:静态检查里最容易被浪费的两个命令

strings不是拿来看 flag 的,而是拿来看“程序自己暴露了什么意图”。比如:

strings ./chall | grep -E "key|flag|correct|wrong"

能匹配到correct和wrong就能定位到校验分支所在的函数。但注意,strings只能提取连续的可见字符,如果 flag 被逐字节拆开存放,你什么都看不到,这时候要配合objdump看数据段:

objdump -s -j .rodata ./chall

-j .rodata指定只看只读数据段。我经常在这里找到一串十六进制,看起来像乱码,实际是 XOR 之后保密的密文。很多题就是这么设计的:明文 flag 不直接存在.rodata里,而是把密文放进去,程序运行时执行异或还原。你用strings找不到,但用objdump -s能看到那串“精致的乱码”,解题的抓手就来了。

3. 拆一道签到题:从回显逆推“是骡子是马”

准备动作做完,直接啃一道真实的签到题。别嫌简单,这类题是整套资源包的第一课,也是所有复杂技巧的骨架。签到题的特点是逻辑直白:用户输入字符串,程序拿去和内部某个值比较,相同输出成功。但“直白”不代表“好做”,特别是当程序被 strip 掉符号之后。这一章我按三步展开:先跑起来看回显,再用objdump找校验函数,最后用 GDB 在比较点抓取藏在内存里的正确输入。

3.1 先跑起来再说:从执行输出找线索

拿到./baby_re以后,先直接运行,任何分析前都要这么做。因为它可能会打印出使用提示、密文或者干脆崩给你一个寄存器地址。运行结果可能是这样的:

Enter the password: admin Wrong!

这个Wrong!就是第一个线索。反向推理:程序一定有某个比较点,比较结果被用来决定打印Wrong!还是Correct!。下一步就是在二进制里找这两条字符串的引用位置。用命令:

strings ./baby_re | grep -E "Correct|Wrong" objdump -d ./baby_re | grep -n "402010"

第二行里的地址402010是strings输出中Correct!所在的地址(实际以你机器上的输出为准)。objdump结合grep能把引用该地址的指令行号揪出来,通常是一句lea rdi, [rip+0x...]或mov esi, 0x402010附近的代码。看到这里就知道打印分支在哪了。

3.2 从汇编看透字符串比较:strcmp 的调用与参数布局

顺着引用地址往上翻几十条指令,你会看到类似这样的典型汇编片段:

4005a0: e8 65 fe ff ff callq 400490 <[email protected]> 4005a5: 85 c0 test %eax, %eax 4005a7: 75 1e jne 4005c7 <main+0x37>

callq 400490调用的是strcmp@plt,传入参数是两个地址:一个指向用户输入缓冲区,另一个指向内部存储的期望值。参数说明:在 x86-64 的 System V 调用约定下,第一个参数用RDI传递,第二个用RSI。test %eax, %eax是在检查strcmp的返回值,如果相等则eax为 0,jne(不等则跳)不触发,就会走进成功分支;如果返回值非 0,就跳到打印Wrong!的分支。这整套逻辑不需要读伪代码,只需看懂那三行指令,你就能定位到“关卡”的真正位置。

3.3 flag 怎么来的?从内存到输出的完整链路

定位到比较点以后,突破口在于“第二个参数指向的内存里藏着什么”。在 GDB 里做一次有预谋的断点:

break *0x4005a0 run x/s $rsi

输入随便什么都行,关键是停在callq之前,这时RSI就是程序内部期望的字符串地址。x/s会直接把内存当字符串打印出来。操作简不简单?简单,但它解决的是逆向里最常见的卡点:你以为要逆向算法,实际只是要摘果子。从入口到打印分支再到内部参数,整条链路就三跳:读输入、比较、按结果分流。这类题的“坑”不在算法,而在于你把时间耗在反推一个根本不需要反推的字符串比较逻辑上。记住经验:程序是跑着的,答案往往就在运行时的寄存器和内存里,别总想着静态把代码背下来。

4. 被反调试拦路?用 LD_PRELOAD 绕过“最没技术含量”的卡点

题目做到第二三个,打击感最强的不是算法复杂,而是“我一开 GDB 程序就自杀”。现象很统一:终端里直接跑,程序老老实实等待输入;一进 GDB 下好断点,run下去就跑完了或者秒退,跟没断点一样。这种题就是加了反调试,通常是用ptrace(PTRACE_TRACEME, 0, 0, 0)来检测自己是否被调试器附加。如果返回 -1,说明已经有人(调试器)在跟踪它了,程序直接exit(1)。解法有很多,但最省事的是用环境变量LD_PRELOAD注入一个“冒名顶替”的 C 函数。

4.1 现象:一上 GDB 就退场,退出码还不一样

先描述清楚现象,方便你对症下药。当你执行gdb ./anti_dbg,然后run,程序可能立即退出,也可能是打印了一行提示后退出。用echo $?查看退出码,有时是 0,有时是 133,这取决于作者的“心情”和实现方式。如果退出码不稳定,大概率是 ptrace 检测逻辑里套了随机延迟或者把退出动作放在了错误处理分支里。还有一个观察点:如果你用strace去跟踪它,可能会看到ptrace(PTRACE_TRACEME)返回EPERM (Operation not permitted),这就是铁证。

4.2 解法:用 PTtrace 的返回值做文章

思路很简单:程序以为ptrace返回 -1 就等于被调试,那我们让ptrace永远返回 0,它的“被调试报警器”就哑了。写一个假的ptrace:

#include <sys/types.h> long ptrace(int request, pid_t pid, void *addr, void *data) { return 0; }

然后编译成动态库:

gcc -shared -fPIC fake_ptrace.c -o fake_ptrace.so LD_PRELOAD=./fake_ptrace.so gdb ./anti_dbg

逻辑说明:LD_PRELOAD会先于程序的正常依赖库加载,链接器在解析ptrace符号时,会优先找到我们定义的版本。这个函数无视参数、一律返回 0,等于告诉程序“一切正常,没人跟踪你”。参数说明:ptrace的第一个参数request通常是PTRACE_TRACEME,枚举值在 Linux 上是 0,所以我们直接返回 0 恰好也让调用方误以为跟踪成功。那一句gcc -shared -fPIC里的-fPIC是生成位置无关代码,共享库必须用它,否则加载时会报重定位错误。

4.3 边界:为什么 LD_PRELOAD 不是万能的

看到这里你可能觉得反调试太菜了。别急着下结论,LD_PRELOAD只对“动态链接且对 ptrace 做单一检测”的程序有效。如果程序作者把ptrace封装成函数指针,在运行时通过dlsym手动获取真正库中的符号地址,LD_PRELOAD就会被绕过,因为dlsym(RTLD_NEXT)会跳过预加载库。还有一种更难缠的做法是直接内联汇编调用syscall指令,绕开 libc 的包装函数,这种情况下你即使是ptrace,拦截普通函数也没用,得用seccomp或者 ptrace 的上级机制gdb本身都会有冲突。我的习惯是先用LD_PRELOAD试一次,不行就换objdump定位ptrace调用点,直接 patch 掉那几行汇编或者 NOP 掉判断跳转。绕过反调试是一个“分级排查”的过程,永远先试最便宜的方案,别一上来就掀桌子反虚拟机检测。

5. 抓包并重放:接收端不管数据来源,那我们就自己造

逆向不只是分析本地文件,有一类题把服务器藏在网络里:程序会向某个远程端口发送一段加密后的“证据”,服务器校验后返回 flag。这类题最让人挠头的是它是个黑匣子——你看得见本地程序,看不见服务端逻辑。解决思路就一条:把数据从程序嘴里骗出来,再想办法让服务端以为你是原程序。这一章聊抓包与重放,核心工具是tcpdump和nc,它们是逆向资源包里被严重低估的两把刀。

5.1 什么时候要抓包:网络协议题的“黑匣子”属性

当你运行一个逆向题,发现它卡在“Waiting for server response”这类提示时,别急着逆向算法,先用netstat看看它连了什么端口:

netstat -tunap | grep challenge

输出会告诉你它连到了某个 IP 的12345端口。这时候你的目标已经从“逆向程序”变成了“理解协议”:它发送了什么,服务端返回了什么。抓包是最直接的观察方式,因为协议数据通常在传输层是明文或可分辨的结构,根本不需要逆向。前提是你得抓到它。

5.2 用 tcpdump 找出明文与端口:三个关键参数

抓包命令越长越容易乱,我固定用这一条:

sudo tcpdump -i lo -A -s 0 -X port 12345

参数说明:-i lo抓回环接口,因为程序连的服务器往往就在本机或同一个虚拟机里,流量走 lo 而不过物理网卡;-A把数据包内容以 ASCII 打印,方便肉眼扫 flag 或协议关键字;-s 0表示不截断,抓整个包;-X同时打印十六进制和 ASCII。如果程序运行完没有抓到任何包,先怀疑程序根本没发数据,而是卡在 DNS 解析或 Unix socket 上,这时候用strace -f -e trace=network ./program看系统调用,确认它真正用的传输方式。抓到的包可能长这样:

GET /challenge HTTP/1.1 Host: localhost:12345 token=0xdeadbeef

这就是程序提交到服务端的“密码”。你不需要理解 token 怎么来的,只需要把它从抓包里抠出来。

5.3 重放攻击:从服务端视角看“信任边界”

抠出 token 以后,下一步是把它重放给服务器。最常见的是直接启动一个终端模拟请求,看返回里有没有 flag 或者新的 token:

printf "token=0xdeadbeef\n" | nc -w 3 localhost 12345

如果服务器校验严格,返回可能是一段新挑战:要求你带上一个动态生成的时间戳。这时候再回到 tcpdump 的抓包里,把时间戳的生成逻辑找到,在程序里打个断点,观察内存中时间戳从哪里来。这已经跳出了“逆向题”的范畴,直接变成“协议逆向”,但它确实是 CTF 里的一道常见关卡。本质原因是服务端信任了传输内容本身,而没有验证来源的合法性。从安全的视角看,这叫缺少来源身份确认;从解题视角看,这就是送分题:它不验证 client,只验证“包长这样就行”。遇到动态 token 的题,我一般会写一个五分钟的 Python 小脚本,用socket库把抓包里的请求原样发出去再收回来,循环三次看哪次被接受。

6. 进阶技巧:从“看懂”到“改着玩”,用 Ghidra 的伪代码验证手推结论

到了最后一步,你已经会跑程序、下断点、抓内存,还知道怎么骗过反调试和劫持网络数据。但还得解决一个效率问题:当函数调用链很长,或者算法藏在 500 行汇编里时,GDB 一步一步走会让人崩溃。我的习惯是先用静态分析工具把伪代码拉出来,再用动态调试验证伪代码里最关键的那一行。Ghidra 在这次整套资源包里算得上加速器,它能省掉你一半的汇编阅读时间,但它的输出只能作为“参考译文”,不能直接信。

6.1 用伪代码当计算器:把鼠标点成“提效”习惯

在 Ghidra 里打开题目的二进制,找到main函数,按 Tab 切到反编译视图。你会看到类似这样的 C 风格伪代码:

local_8 = 0; while (local_8 < 10) { iVar1 = strcmp((char *)(input + local_8), (char *)(key_data + local_8 * 4)); if (iVar1 != 0) break; local_8 = local_8 + 1; } if (local_8 == 10) { puts("Correct!"); }

对照之前 GDB 单步看到的汇编,逻辑一致:这是个 10 字节的逐字符比较循环,key_data是内部存储的密钥表。伪代码的价值在于让你快速抓住“比较了几次”“每次偏移多少”,省得数lea rax, [rbp-0x30]这种地址加减。但注意,Ghidra 对结构体识别的并不准确,它打印出的local_8 * 4可能是4 字节步长的意思,也可能是误导,实际得用 GDB 在循环里x/4cx $rsi去看当前比较的真实的字节。

6.2 四个清单:每个逆向题都要强制走一遍的检查顺序

这是我在这次题目里提炼出的一个固定套路。拿到任何二进制,按这个顺序过,基本不会漏:

顺序动作常见突破口
1file+strings拿到文件类型、密钥提示、输出信息
2objdump -d定位比较分支找strcmp、memcmp调用点
3GDB 动态断点 + 查看寄存器直接读到被比较的内存内容
4tcpdump/strace看交互摸清网络协议 OS 层行为

如果前两步没结果,别急着怀疑工具,多数时候是题目开了 PIE 或加了混淆,你该用set disable-randomization on关掉 ASLR,再重新跑一遍断点流程。还有一个我自己踩过的大坑:某些题需要先修改某处全局变量才能进入正确分支,但你直接改内存是不行的,得写一段python用ctypes手动加载二进制并 patch 后再模拟执行,这时候 Ghidra 的Export功能能把修改后的文件倒出来,省得每次运行都重新 patch。

目前这几年做逆向题的习惯是,所有新题目都强制自己走一遍“静态看结构,动态验内存,最后再看伪代码”的顺序,即使一眼能看出是签到题也绝不跳过。因为省略步骤的人,迟早会在某个隐蔽的分支上翻车,而逆向这行当的乐趣,从来不是“看懂了”,是“跑通了”。希望这整套笔记和题目的拆解思路能帮到你,少走几段弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询