
简介2431关的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 Typefile会告诉你这是 32 位还是 64 位、是否动态链接、是不是被 strip 过。readelf -h里的 Type 如果显示DYN (position-independent executable)说明开了 PIE打断点时地址会变这是个重要信号。我一般会先跑一遍strings再开 GDBgdb -q ./chall进去以后第一步习惯性关掉分页避免长输出卡屏set pagination off info functions用info functions看导出的函数。如果这题没被 strip你会直接看到main、check_key这些名字解题思路就半透明了。如果看到一堆sub_xxx那就得老实从入口点_start开始啃。接着在某处下断点break *0x4012a0 run x/20i $ripx/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, [rip0x...]或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 main0x37callq 400490调用的是strcmpplt传入参数是两个地址一个指向用户输入缓冲区另一个指向内部存储的期望值。参数说明在 x86-64 的 System V 调用约定下第一个参数用RDI传递第二个用RSI。test %eax, %eax是在检查strcmp的返回值如果相等则eax为 0jne不等则跳不触发就会走进成功分支如果返回值非 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 tracenetwork ./program看系统调用确认它真正用的传输方式。抓到的包可能长这样GET /challenge HTTP/1.1 Host: localhost:12345 token0xdeadbeef这就是程序提交到服务端的“密码”。你不需要理解 token 怎么来的只需要把它从抓包里抠出来。5.3 重放攻击从服务端视角看“信任边界”抠出 token 以后下一步是把它重放给服务器。最常见的是直接启动一个终端模拟请求看返回里有没有 flag 或者新的 tokenprintf token0xdeadbeef\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 四个清单每个逆向题都要强制走一遍的检查顺序这是我在这次题目里提炼出的一个固定套路。拿到任何二进制按这个顺序过基本不会漏顺序动作常见突破口1filestrings拿到文件类型、密钥提示、输出信息2objdump -d定位比较分支找strcmp、memcmp调用点3GDB 动态断点 查看寄存器直接读到被比较的内存内容4tcpdump/strace看交互摸清网络协议 OS 层行为如果前两步没结果别急着怀疑工具多数时候是题目开了 PIE 或加了混淆你该用set disable-randomization on关掉 ASLR再重新跑一遍断点流程。还有一个我自己踩过的大坑某些题需要先修改某处全局变量才能进入正确分支但你直接改内存是不行的得写一段python用ctypes手动加载二进制并 patch 后再模拟执行这时候 Ghidra 的Export功能能把修改后的文件倒出来省得每次运行都重新 patch。目前这几年做逆向题的习惯是所有新题目都强制自己走一遍“静态看结构动态验内存最后再看伪代码”的顺序即使一眼能看出是签到题也绝不跳过。因为省略步骤的人迟早会在某个隐蔽的分支上翻车而逆向这行当的乐趣从来不是“看懂了”是“跑通了”。希望这整套笔记和题目的拆解思路能帮到你少走几段弯路。本文还有配套的精品资源点击获取