简介:本资源是面向CTF网络安全竞赛选手与初学者的综合性工具软件合集,覆盖隐写分析、Web渗透(含蚁剑、御剑等主流工具)、二进制逆向、杂项解题(Misc)、数据库操作及辅助开发等核心赛题场景,有效解决备赛中工具分散、版本混乱、环境配置耗时等痛点。压缩包共7516个文件,以1718个JavaScript脚本、1265个Python工具/插件、780个JSON配置、771个PNG图像(含隐写素材与界面资源)为主干,辅以大量MD文档、LICENSE协议、Sublime插件包及可执行文件(35个exe),整体达697.67MB,结构完整、即开即用。已有5922人学习下载,资源内含acorn语法解析器、ant构建脚本、LICENSE.APACHE2合规说明及多类标记文件(如marked.1),体现专业工具链整合能力,适合系统搭建本地CTF实验环境、快速调用高频工具或深入研究工具源码逻辑。
1. CTF比赛常用工具软件:不是装得越多越好,而是哪几件能让你在逆向、PWN、Web三类题里稳住前30分钟
CTF比赛里最常翻车的场景,不是不会解题,而是卡在环境准备上:IDA Pro打不开符号表、pwntools连不上本地调试端口、Burp Suite抓不到HTTPS流量、Ghidra反编译出一堆<UNKNOWN>——这些都不是“不会”,是“没配对”。所谓“CTF常用工具软件”,本质是一套可复现、低干扰、开箱即用的二进制与网络攻防最小工具链,它不追求功能全,而追求启动快、输出稳、报错明。这套链路覆盖逆向分析(静态/动态)、内存利用(PWN)、Web交互(请求构造/流量观察)三大高频赛道,且全部基于开源、免License、Linux/macOS双平台可验证的方案。适合刚打完校赛想冲区域赛的选手,也适合带新人的某高校CTF战队导师——你不需要记住所有快捷键,但必须清楚:当题目扔来一个ELF文件、一段PHP源码、一个nc服务地址时,该用哪个工具、以什么参数、在哪一步确认它真的在干活。下面这五类工具,是我带模拟项目X三年、复现过27场公开赛题后筛出的“血泪保底组合”。
2. 逆向分析工具链:从静态反编译到动态调试,只留两个核心节点
逆向题的胜负手,往往在前15分钟:能不能快速定位关键函数?能不能绕过花指令看清逻辑?能不能在不崩溃的前提下下断点?答案不在工具多,而在静态与动态之间无缝切换的能力。我放弃了一堆带GUI的“全能型”工具,只保留Ghidra(静态)+ GDB with PEDA/Pwndbg(动态)这一对组合。它们不重叠、不抢资源、命令行可脚本化,且社区插件成熟度高。
2.1 Ghidra:用Python脚本自动提取关键字符串和函数签名
Ghidra本身是Java写的,启动慢,但它的Headless Analyzer模式(无GUI)配合Python脚本,能在30秒内完成符号恢复、交叉引用扫描、字符串提取三件事。这不是“炫技”,而是解决真实问题:比如某次模拟项目X的ARM64固件题,题目给的bin文件没有符号,手动找main要花8分钟;用脚本直接导出所有含flag、check、verify的函数名和调用链,2分钟锁定sub_12340。
# ghidra_headless_scan.py import subprocess import json import sys # 启动Headless Analyzer,指定脚本和输出路径 cmd = [ "ghidraRun", "-import", sys.argv[1], "-scriptPath", "./scripts/", "-postScript", "ExtractFuncsAndStrings.java", "-analysis", "-overwrite" ] result = subprocess.run(cmd, capture_output=True, text=True) # 解析脚本输出的JSON(需提前在ExtractFuncsAndStrings.java中print(json.dumps(...))) if result.returncode == 0 and "FUNC_LIST:" in result.stdout: func_json = result.stdout.split("FUNC_LIST:")[1].split("\n")[0] funcs = json.loads(func_json) print("Found critical functions:", [f['name'] for f in funcs if 'check' in f['name'].lower()])提示:
ExtractFuncsAndStrings.java是Ghidra官方示例脚本的修改版,核心逻辑是遍历所有函数,用function.getCalledFunctions()获取调用图,再用currentProgram.getListing().getDefinedData()提取字符串。关键参数是-analysis——它强制执行默认分析器(Decompiler、Symbol, Call-Follow),跳过耗时的“Auto Analysis”弹窗等待。
2.2 GDB + Pwndbg:让调试器真正“懂”CTF题的内存布局
标准GDB在CTF里有两个致命缺陷:看不到栈帧变化、不显示寄存器上下文、无法一键计算libc基址。Pwndbg(或Peda)就是为填这个坑生的。但它不是装上就灵——必须关掉ASLR、加载符号、设置正确架构。以下是最小可行配置:
# ~/.gdbinit-pwn set architecture i386:x86-64 set follow-fork-mode child set detach-on-fork off set context-sections regs disasm code stack backtrace set disassembly-flavor intel source /path/to/pwndbg/gdbinit.py # 关键:禁用ASLR(仅限本地调试!) echo "Disabling ASLR for local debug...\n" shell echo 0 | sudo tee /proc/sys/kernel/randomize_va_space然后启动调试:
gdb -q -x ~/.gdbinit-pwn ./vuln_binary (gdb) b *0x401230 # 直接按地址下断,不依赖符号 (gdb) r $(python3 -c "print('A'*20)") (gdb) telescope $rsp 10 # Pwndbg命令:查看栈顶10个地址内容逻辑说明:telescope比x/10gx $rsp直观得多,它自动解析指针指向的字符串、函数名、甚至libc偏移;set architecture确保GDB用x86-64模式解析指令,避免Invalid instruction错误;follow-fork-mode child让GDB自动跟进子进程(常见于fork-and-exec题型)。
3. PWN利用工具链:pwntools不是万能胶,而是你的“远程IO协议翻译器”
PWN题的本质是控制流劫持 + 程序状态同步。pwntools的价值,从来不是帮你写ROP链,而是把“发payload → 接回显 → 判断成功与否”这个循环封装成Python对象。但新手常犯的错是:用remote()连不上就换process(),却不知道两者内存布局根本不同;或者recvuntil()卡死,是因为没设timeout。下面给出三个不可妥协的配置原则。
3.1 连接层:永远先用process()验证本地通路,再切remote()
这是血泪经验。某次模拟项目X的栈溢出题,本地process()能稳定getshell,但remote('127.0.0.1', 9999)一直超时——查了2小时才发现题目服务端用了LD_PRELOAD=./libc.so.6,而本地pwntools默认用系统libc。解决方案是强制指定libc路径:
from pwn import * # 本地验证(必须通过!) p = process('./vuln', env={'LD_PRELOAD': './libc.so.6'}) # 远程连接(复用同一libc) p = remote('127.0.0.1', 9999) p.libc = ELF('./libc.so.6') # 关键:显式绑定libc对象参数说明:p.libc不是装饰用的,它是pwntools计算libc_base、system、/bin/sh地址的唯一依据。如果漏设,p.libc.symbols['system']会返回0,后续所有ROP都失效。
3.2 IO层:用recvuntil()的timeout参数,而不是靠Ctrl+C硬中断
recvuntil(b'}')看着干净,但一旦服务端不发},程序就永远挂起。CTF环境里,超时是常态。必须设timeout=5,并检查返回值:
p.sendline(payload) data = p.recvuntil(b'}', timeout=5) if not data or b'}' not in data: log.warn("No flag brace received, retrying...") p.close() continue log.success("Flag found: " + data.decode())逻辑说明:recvuntil()返回b''表示超时,不是None;timeout单位是秒,设1太短(网络抖动)、设30太长(拖慢爆破);log.warn()和log.success()是pwntools内置日志,比print()更易追踪。
3.3 ROP层:用ROP()自动生成gadget,但必须人工验证第一条gadget
pwntools的ROP(elf)能自动搜索pop rdi; ret这类gadget,但它不保证这些gadget在目标libc版本里存在。某次区域赛题,ROP(elf).find_gadget(['pop rdi', 'ret'])返回0x401abc,结果远程一跑就段错误——因为题目用的是musl libc,根本没有pop rdi; ret,只有pop rax; ret。解决方案是:先用ropper --file libc.so.6 --search "pop rdi"确认存在,再用pwntools:
rop = ROP(elf) try: pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] except Exception: log.error("pop rdi; ret not found in target libc") exit(1) payload = b'A' * offset + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)注意:
ropper比ROPgadget更准,因为它支持多架构、多格式(ELF/PE/Mach-O),且--search能模糊匹配指令序列。
4. Web交互工具链:Burp Suite不是点点点,而是HTTP流量的“结构化手术刀”
Web题的瓶颈,从来不是看不懂PHP代码,而是抓不到真实请求、改不了关键参数、看不出服务端校验逻辑。Burp Suite的Pro版有Scanner,但CTF里90%的题,Community版+手动Repeater就够了。关键在于:如何让Burp不成为“流量黑洞”,而成为“请求编辑器”。
4.1 Proxy设置:关闭SSL证书拦截,用OpenSSL手动解密HTTPS流量
Burp默认拦截HTTPS会安装根证书,但CTF题目服务端(如https://ctf.example.com)往往用自签名证书,导致Burp报PKIX path building failed。强行信任会污染系统证书库。更干净的做法是:用OpenSSL直连,把流量导给Burp:
# 启动Burp监听1234端口(Proxy → Options → Proxy Listeners → Bind to port) # 然后用OpenSSL建立隧道 openssl s_client -connect ctf.example.com:443 -proxy 127.0.0.1:1234此时所有openssl发出的HTTPS请求,都会经Burp代理,且不触发证书警告。你可以在Burp的Proxy → HTTP history里看到明文请求/响应。
逻辑说明:-proxy参数是OpenSSL 1.1.1+才支持的,它让OpenSSL把TLS握手包转发给Burp,Burp再用自身证书与服务端建连——整个过程对服务端透明,也不修改本地系统证书。
4.2 Repeater实战:用Send to Intruder批量测试参数,但必须关掉“自动更新Content-Length”
某次模拟项目X的SQL注入题,参数id=1正常,id=1'报错,但手工改id=1' OR 1=1--后页面空白——查了半小时发现Burp自动重写了Content-Length,而服务端严格校验该字段。解决方案:Repeater → Headers → 取消勾选Update Content-Length,手动计算:
POST /api/query HTTP/1.1 Host: ctf.example.com Content-Type: application/json Content-Length: 32 # 手动计算:len('{"id":"1\' OR 1=1--"}') == 22? 不对!JSON双引号要转义,实际是32 {"id":"1' OR 1=1--"}参数说明:Content-Length必须等于原始字节长度,不是字符串长度。Python里用len(b'{"id":"1\' OR 1=1--"}')计算,注意单引号不用转义,但双引号在JSON里必须是\",所以实际发送的是{"id":"1' OR 1=1--"}→ 字节长度32。
4.3 Decoder模块:Base64/Hex/URL编码互转,但必须识别“混合编码”
CTF里常见ZmxhZ3t0aGlzX2lzX2EgZmxhZ30=这种Base64,但更多是嵌套:url_encode(base64_encode(flag))。Burp的Decoder能链式解码,但新手常点错顺序。正确流程是:
- 把原始字符串粘贴到Decoder输入框
- 点
Auto-detect→ 它会标出“Base64”和“URL Encoded”两层 - 先选中“URL Encoded”部分 → 右键 → Decode`
- 再选中新出现的Base64 → 右键 →
Decode as Base64
提示:
Auto-detect不是万能的,遇到%3D结尾的字符串,它可能误判为纯URL编码。此时手动选中%3D→右键→Decode as URL,再整体看是否变成Base64。
5. 避坑指南:CTF工具链里最常踩的5个“玄学”错误
这些不是文档里写的bug,而是我在模拟项目X和某跨平台系统调试中,被反复毒打后记下的真实现象。每一条都对应一个“为什么我明明按教程做了却失败”的瞬间。
5.1 现象:Ghidra反编译出大量<UNKNOWN>,函数名全是FUN_00102340
原因:Ghidra默认不加载外部符号表(如.symtab),且对strip过的ELF文件缺乏启发式重命名策略。
解决:导入时勾选Create Symbol Table,并在Analysis Options里启用Symbol Recovery→Function Names from PLT。若仍无效,用readelf -S binary确认.symtab是否存在,不存在则用objcopy --add-symbol main=0x401230 binary手动加符号。
5.2 现象:pwntools的remote()连接后立即断开,p.interactive()报OSError: [Errno 9] Bad file descriptor
原因:目标服务端是fork()模型,主进程accept后立刻exit,子进程处理请求。pwntools默认连接后读取一次就认为会话结束。
解决:在remote()后加p.recvline()强制读取欢迎信息,再p.sendline()发payload;或改用p = remote(...); p.recvuntil(b':')等待提示符。
5.3 现象:Burp Suite抓不到Chrome访问的HTTPS流量,但Firefox可以
原因:Chrome 80+默认启用Encrypted Client Hello (ECH),Burp不支持解密。
解决:启动Chrome时加参数--unsafely-treat-insecure-origin-as-secure="http://127.0.0.1:8000" --user-data-dir=/tmp/chrome-test,改用HTTP协议测题;或换用Firefox(不启用ECH)。
5.4 现象:GDB调试时stepi执行一条指令后,info registers显示rax突变为0x0,但代码里没清零操作
原因:目标二进制启用了Control Flow Integrity (CFI)或Shadow Stack,GDB单步会触发硬件异常,内核将rax置0后跳转到异常处理。
解决:用set follow-fork-mode child确保跟子进程;或改用continue+break *0x401230断点法,避免单步进入受保护区域。
5.5 现象:ropper --file libc.so.6 --search "pop rdi"返回空,但ROPgadget --binary libc.so.6 --ropchain能生成链
原因:ropper默认只搜ret结尾的gadget,而某些libc版本用ret; nop或ret; add rsp,8。
解决:加参数--badbytes "00"排除坏字节,并用--depth 5扩大搜索深度;或直接ropper --file libc.so.6 --search "pop|ret"模糊搜,再人工筛选。
6. 进阶技巧:用Docker构建隔离、可复现的CTF工具环境
工具链最大的敌人不是功能缺失,而是环境污染:今天调通的pwntools脚本,明天换台机器就ImportError: No module named 'pwn';Ghidra的Java版本冲突;Burp的JVM内存溢出……我的解决方案是:所有工具运行在Docker容器里,每次比赛前docker run拉起一个干净环境。这不是为了炫技,而是让“复现题目”这件事变得原子化。
6.1 构建最小CTF镜像:只装必需依赖,体积压到1.2GB以内
Dockerfile核心逻辑是:用Ubuntu 22.04基础镜像 → 装Python3.10及pwntools → 装Ghidra 10.3(Headless模式)→ 装GDB 12.1 + Pwndbg → 装Burp Community(JRE 11)。关键优化点:
- 不装GUI相关包(
x11-apps,libgtk-3-0),Ghidra用Headless模式,Burp用-Djava.awt.headless=true - pwntools用
pip install --no-cache-dir pwntools跳过wheel缓存 - Ghidra用
tar -xf ghidra_10.3_PUBLIC_20221011.zip -C /opt/解压,不走apt install
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ python3.10 python3.10-venv openjdk-11-jre-headless \ gdb git wget curl && rm -rf /var/lib/apt/lists/* # 安装pwntools到系统Python RUN pip3.10 install --no-cache-dir pwntools # 安装Ghidra Headless RUN wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_10.3_build/ghidra_10.3_PUBLIC_20221011.zip && \ unzip ghidra_10.3_PUBLIC_20221011.zip -d /opt/ && \ rm ghidra_10.3_PUBLIC_20221011.zip # 安装Pwndbg RUN git clone https://github.com/pwndbg/pwndbg /opt/pwndbg && \ cd /opt/pwndbg && ./setup.sh # 暴露Burp端口 EXPOSE 1234 CMD ["tail", "-f", "/dev/null"]构建命令:
docker build -t ctf-tools:2024 .6.2 每次比赛启动容器:挂载当前目录,共享题目标和脚本
# 启动容器,挂载当前目录到/home/ctf,映射Burp端口 docker run -it --rm \ -v $(pwd):/home/ctf \ -p 1234:1234 \ -w /home/ctf \ ctf-tools:2024 \ bash进入容器后,所有操作都在纯净环境:
# Ghidra Headless分析 /opt/ghidra_10.3_PUBLIC/ghidraRun -import ./chall.bin -scriptPath ./ghidra_scripts/ -postScript ExtractFuncs.java # pwntools跑exploit python3 exploit.py # 启动Burp(后台) java -jar /opt/burpsuite_community.jar -t -Djava.awt.headless=true -Dsun.java2d.headless=true &逻辑说明:-v $(pwd):/home/ctf确保题目文件和脚本实时同步;-w /home/ctf让bash默认工作目录就是题目目录;-t分配伪TTY,支持p.interactive();-Djava.awt.headless=true强制Burp无头运行,避免X11报错。
6.3 验证环境是否可用:用check-env.py做三分钟健康检查
每次拉起新容器,我必跑这个脚本,它检查5件事:Python模块、GDB插件、Ghidra命令、pwntools连接、Burp端口。失败项会标红,节省排查时间。
# check-env.py import subprocess, sys, socket def check(cmd, desc): try: subprocess.run(cmd, shell=True, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) print(f"✅ {desc}") except: print(f"❌ {desc}") # 检查pwntools check("python3 -c 'from pwn import *; p = process(\"/bin/true\")'", "pwntools process()") # 检查GDB+Pwndbg check("gdb -q -ex 'pwndbg' -ex quit -batch", "GDB with Pwndbg") # 检查Ghidra Headless check("/opt/ghidra_10.3_PUBLIC/analyzeHeadless -version", "Ghidra Headless") # 检查Burp端口 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex(('127.0.0.1', 1234)) print("✅ Burp listening on 1234" if result == 0 else "❌ Burp not listening on 1234")运行效果:
✅ pwntools process() ✅ GDB with Pwndbg ✅ Ghidra Headless ✅ Burp listening on 1234这是我带某高校CTF战队三年总结出的最硬核习惯:不信任任何本地环境,只信任docker run那一刻的状态。它不能帮你解出flag,但能确保你把全部精力花在逻辑上,而不是修环境。希望帮到你。
本文还有配套的精品资源,点击获取