1. 从一道签到题说起:为什么新手赛值得认真复盘
很多人觉得CTF新手赛就是"送分题合集",做完就忘,没什么好复盘的。我一开始也这么想,直到带了几届新人之后才发现,新手赛里踩过的坑,往往比高级赛更值得记录——因为那些坑是基础认知层面的,不解决就会一直带到后面。这次要聊的是一场高校CTF新手赛的参赛经历,题目覆盖了Misc、Crypto、Web、Reverse几个常规方向,难度控制得比较友好,但每道题背后都藏着一些"新手一定会卡住"的设计点。
这篇文章适合三类人看:刚接触CTF、想找一套完整入门路径的新人;准备组织校内赛、想知道出题人思路的组织者;以及打了一段时间比赛但基础不牢、想回头补课的老选手。我会按方向拆解每道题的解题链路,重点讲清楚"我当时是怎么想的""为什么走错了""正确的思路应该从哪里切入",而不是只丢一个flag出来。所有题目和平台信息都做了脱敏处理,只保留技术细节。
先说一个整体感受:新手赛最大的价值不在于题目多难,而在于它逼你把工具链跑通一遍。很多人平时看教程觉得"我会了",一到比赛环境里连Python脚本都跑不起来,编码问题、依赖问题、路径问题全冒出来。所以下面每个方向我都会把环境准备和工具使用单独拎出来讲,这些才是真正拉开差距的地方。
2. Misc方向:从文件识别到隐写排查的完整链路
2.1 第一道签到题暴露的工具链问题
签到题通常是一张图片或者一个压缩包,flag直接藏在里面。但就是这种题,我见过太多人卡住。当时拿到的是一张看起来普通的PNG图片,第一反应是用图片查看器打开,肉眼扫一遍没发现异常。这时候正确的做法不是继续盯着看,而是立刻上工具做文件类型确认。
我习惯的第一步是file命令:
file suspicious.png如果输出显示的是PNG image data,说明文件头没问题;但如果显示data或者别的类型,那就有意思了——很可能是改了扩展名的其他格式。这次签到题就是这种情况,file输出的是Zip archive data,说明这其实是个压缩包被改成了png后缀。
改回.zip解压,里面是一个文本文件,flag就在里面。整个过程不到两分钟,但我观察到很多新人在这里卡了十几分钟,原因是他们只用鼠标右键看属性,没有养成命令行确认文件类型的习惯。
提示:Windows下可以用
certutil -dump或者直接看文件头十六进制,Linux/Mac下file命令是最快的。养成"先确认文件真实类型"的习惯,能省掉大量无效尝试。
2.2 图片隐写的几种常见藏法及排查顺序
第二道Misc题是一张更大的图片,file确认是正常PNG。这时候就要进入隐写排查流程了。我的排查顺序是这样的,从简单到复杂:
- 看EXIF信息:用
exiftool或者在线工具读元数据,有时候flag直接写在Comment字段里。 - 看文件尾部:用
binwalk扫一遍,看有没有附加的压缩包或文件。 - 看像素层:用
zsteg(PNG)或steghide(JPG)尝试提取隐藏数据。 - 看通道分离:用
stegsolve逐通道查看,或者用Python的PIL做通道运算。 - 看LSB:最低有效位隐写,通常需要脚本提取。
这次题目卡在第三步。binwalk扫出来尾部附加了一个zip,但解压需要密码。密码线索藏在EXIF的某个字段里,是一个看起来像日期的字符串。这里有个细节:EXIF里的日期格式和压缩包密码要求的格式可能不一致,我试了2020-11-15、20201115、2020/11/15几种写法才通过。
# 提取附加文件 binwalk -e suspicious.png # 查看EXIF exiftool suspicious.png | grep -i comment这道题教给我的经验是:隐写题的线索往往是分层的,第一层给你一个加密文件,第二层告诉你密码在哪,第三层才是flag。不要看到加密就放弃,先找密码线索。
2.3 流量分析题里的协议识别技巧
第三道Misc是流量包分析,给了一个pcap文件。新手最容易犯的错是直接Wireshark打开然后漫无目的地翻。正确做法是先做协议统计:
# 查看协议分布 tshark -r capture.pcap -q -z io,phs # 过滤HTTP请求 tshark -r capture.pcap -Y "http.request" -T fields -e http.request.uri这次流量包里混了HTTP和TCP两种流量,flag藏在HTTP的一个POST请求体里,但被URL编码了。直接看Wireshark的"Follow HTTP Stream"就能看到明文,但需要手动做URL解码。我写了个小脚本处理:
from urllib.parse import unquote encoded = "%66%6c%61%67%7b..." print(unquote(encoded))流量分析的核心能力是快速定位可疑流量。我的经验是:先看协议层级统计,再看有没有异常端口,最后跟踪TCP/HTTP流。如果流量包很大,用tshark做字段提取比Wireshark手动点快得多。
3. Crypto方向:从编码识别到数学推导的思维转换
3.1 编码题和加密题的分界线在哪里
Crypto方向新手最容易混淆的就是"编码"和"加密"。编码是可逆的、无密钥的,比如Base64、Hex、URL编码;加密是需要密钥的,比如AES、RSA。新手赛里经常混着出,先让你解一层编码,再解一层加密。
第一道Crypto题给了一串看起来像Base64的字符串,但直接解码出来是乱码。这时候要怀疑是不是多重编码。我的处理流程是写一个循环尝试的脚本:
import base64 s = "ZmxhZ3t...==" for i in range(5): try: s = base64.b64decode(s).decode('utf-8') print(f"Round {i}: {s}") except: # 尝试其他编码 try: s = bytes.fromhex(s).decode('utf-8') print(f"Hex round {i}: {s}") except: break这次是Base64套Hex再套Base64,三层解完出flag。关键经验是:看到字符串结尾有等号,优先怀疑Base64;看到全是0-9a-f,优先怀疑Hex。但不要死板,写个自动尝试的脚本最省事。
3.2 古典密码的识别特征与工具选择
第二道Crypto是古典密码,给了一段看起来像乱码的英文字母。古典密码的识别主要看字符分布特征:
| 密码类型 | 特征 | 识别方法 |
|---|---|---|
| 凯撒密码 | 字母频率偏移 | 尝试所有25种偏移 |
| 维吉尼亚 | 多表替换 | 需要密钥长度分析 |
| 栅栏密码 | 字符重排 | 尝试不同栏数 |
| 培根密码 | 只有A/B两种字符 | 5位一组映射 |
| 摩斯密码 | 点和划 | 直接查表 |
这次题目给的是纯字母,长度不长,我直接上了凯撒暴力破解:
def caesar_bruteforce(cipher): for shift in range(26): plain = ''.join( chr((ord(c) - ord('a') - shift) % 26 + ord('a')) if c.islower() else c for c in cipher ) print(f"Shift {shift}: {plain}") caesar_bruteforce("xyzabc...")跑出来第13个偏移是可读文本,flag就在里面。古典密码题在新手赛里基本就是考工具使用和耐心,没有太多数学难度。
3.3 简单RSA题的参数计算与脚本编写
第三道Crypto是RSA,给了n、e、c三个参数。新手看到大整数就慌,其实新手赛的RSA通常有固定套路:
e特别小(比如3),直接开方n可以分解(用factordb或者yafu)- 给了多个
n共享素数 e和phi不互素
这次是n比较小,可以直接用Python的sympy分解:
from sympy import factorint import gmpy2 n = 123456789... e = 65537 c = 987654321... factors = factorint(n) p, q = list(factors.keys()) phi = (p-1)*(q-1) d = gmpy2.invert(e, phi) m = pow(c, d, n) print(bytes.fromhex(hex(m)[2:]).decode())这里有个坑:m转字节的时候,如果m的十六进制长度是奇数,需要前面补0。我见过有人在这里卡住,解出来的明文少一个字符。正确做法是:
hex_str = hex(m)[2:] if len(hex_str) % 2: hex_str = '0' + hex_str print(bytes.fromhex(hex_str).decode())RSA题的核心是先判断属于哪种攻击类型,再套对应脚本。新手赛不会考太复杂的攻击,把常见的几种(小e、共模、因数分解)脚本准备好就行。
4. Web方向:从源码泄露到注入绕过的实战记录
4.1 信息收集阶段最容易忽略的几个入口
Web题的第一步永远是信息收集。新手常犯的错是直接对着首页一顿乱点,忽略了最基础的几个检查点:
- robots.txt:看有没有禁止爬取的路径
- 源码注释:F12看HTML注释里有没有提示
- 备份文件:尝试
index.php.bak、www.zip、.git等 - 响应头:看Server、X-Powered-By等字段
- Cookie:看有没有奇怪的字段
这次第一道Web题就是源码泄露。首页看起来是个普通登录页,但访问/.git/目录返回了403而不是404,说明存在Git仓库。用GitHack或者githacker工具直接拉取源码:
python GitHack.py http://target/.git/拉下来一看,登录逻辑里有个硬编码的admin密码,直接登录拿flag。这道题教给我的经验是:403和404的区别很重要,403说明目录存在但禁止访问,往往意味着有东西可挖。
4.2 SQL注入的手工测试与自动化取舍
第二道Web是SQL注入。新手赛的注入题通常不会上WAF,手工就能测出来。我的测试流程是:
- 在输入框输入
',看是否报错 - 输入
' or 1=1--,看是否绕过登录 - 用
order by测字段数 - 用
union select联合查询
这次是登录框注入,' or 1=1--直接绕过了。但flag不在登录后的页面,而在数据库里。需要进一步用联合查询:
' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--拿到表名后查字段,再查数据。整个过程手工就能完成,不需要上sqlmap。但如果是盲注或者时间盲注,手工效率太低,这时候再上sqlmap:
sqlmap -u "http://target/login.php" --data="user=admin&pass=1" --dbs注意:新手赛里sqlmap有时候会被简单过滤拦掉,手工注入反而更稳。建议先手工确认注入点,再用工具提效。
4.3 文件包含与伪协议的组合利用
第三道Web是文件包含。页面有个?page=home参数,尝试改成?page=../../etc/passwd,返回了文件内容,说明存在本地文件包含。但flag不在系统文件里,需要读源码或者用伪协议。
PHP伪协议是文件包含题的标配:
# 读源码 ?page=php://filter/convert.base64-encode/resource=index.php # 执行代码(如果allow_url_include开启) ?page=data://text/plain,<?php system('cat /flag');?>这次allow_url_include是关闭的,但php://filter可用。读源码发现flag在某个被包含的文件里,直接读出来就行。文件包含题的关键是先确认包含类型(本地还是远程),再选择对应的伪协议。
5. Reverse方向:从静态分析到动态调试的入门路径
5.1 用file和strings做第一轮快速筛查
Reverse题给的是一个可执行文件。新手看到二进制就头大,其实第一轮筛查用两个命令就能拿到不少信息:
file challenge strings challenge | grep -i flagfile告诉你这是ELF还是PE,是32位还是64位,有没有加壳。strings直接搜可读字符串,有时候flag就明文写在里面。这次strings没搜到flag,但搜到了一些提示性字符串,比如"correct"、"wrong"、"input your flag"。
5.2 静态分析工具的选择与关键函数定位
接下来上静态分析工具。新手赛的Reverse题通常不会加壳,用IDA Free或者Ghidra就能搞定。我习惯先用Ghidra(免费且跨平台),加载后看函数列表,找main函数。
这次main函数逻辑很清晰:读输入,做一系列字符变换,然后和硬编码的字符串比较。关键代码是:
for (int i = 0; i < len; i++) { input[i] ^= 0x42; input[i] += 3; } if (strcmp(input, target) == 0) { printf("Correct!\n"); }逆向这个逻辑很简单:把target拿过来,反向操作(先减3再异或0x42)就能得到flag。写个Python脚本:
target = [0x12, 0x34, ...] # 从二进制里提取 flag = ''.join(chr((t - 3) ^ 0x42) for t in target) print(flag)5.3 动态调试在什么时候比静态分析更高效
有些题静态分析看起来很绕,比如有大量分支判断或者混淆。这时候动态调试更高效。用gdb(Linux)或者x64dbg(Windows)下断点,直接看内存里的值。
这次有一道题静态分析时发现有个循环做了很多无意义的运算,但最终只影响一个变量。我直接在比较函数处下断点:
gdb ./challenge break *0x400800 run # 输入测试字符串 x/s $rdi # 查看比较的字符串断下来直接看到正确的flag在寄存器里。动态调试的核心是找到关键比较点,通常在strcmp、memcmp或者条件跳转指令附近。
6. 赛后复盘:新手赛真正该带走的三样东西
打完这场比赛,我最大的感受是:新手赛的题目本身不难,难的是在有限时间里把工具链跑通、把思路理清。很多人平时看教程觉得都会,一到比赛就手忙脚乱,根本原因是缺少"从零到一"的完整实操。
第一样该带走的是一套自己的工具清单。我现在的Misc工具链是file+binwalk+exiftool+zsteg+stegsolve,Crypto是Python+sympy+gmpy2,Web是Burp Suite+sqlmap+GitHack,Reverse是Ghidra+gdb。这些工具不需要全会,但每个方向至少要有两三个能熟练用的。
第二样是排查问题的顺序感。比如隐写题先看EXIF再看尾部再看像素,Web题先看robots再看源码再看参数。这个顺序不是死的,但有一个默认路径能让你在卡住的时候知道下一步试什么,而不是原地打转。
第三样是写脚本的习惯。新手赛里很多题手工能做,但写脚本更快更准。比如凯撒暴力破解、Base64多重解码、RSA参数计算,这些脚本写一次就能反复用。我建议每个方向都维护一个自己的"脚本库",比赛时直接改参数就能跑。
最后分享一个小心得:比赛时遇到卡住的题,不要死磕超过20分钟。先跳过去做别的,回头再看往往会有新思路。我这次有一道Crypto题卡了半小时,后来去做Web题放松了一下,回来发现只是漏看了一个参数。心态比技术更重要,尤其是新手赛。