2021DASCTF Misc 的 red_vs_blue 这道题,说实话,第一眼看到标题时我以为又是那种给一大包攻击流量、让人从海量请求里找红队入侵痕迹的硬核取证题。结果解压完之后发现,材料比我预想中少得多,就三个文件,但恰恰是这三个文件,让不少队伍在赛场上卡了很久。整道题做下来,我最大的感受是:Misc 出的不是脑洞,是耐心。特别适合想系统练习日志分析、流量提取和编码处理的人拿来当入门实战。
我把完整的解题过程、踩过的坑和复盘心得整理出来。无论你是准备打 CTF 的新人,还是已经刷过不少 Misc 题的选手,这篇应该都能给你一些不一样的启发。
1. 从标题“red_vs_blue”能猜到题目想考什么
1.1 标题暗示的两种可能方向
红蓝对抗这个词在安全圈里很常见,红队模拟攻击,蓝队负责检测和防御。落到 CTF 的 Misc 分类里,这个标题基本就是在告诉你:题目材料里一定同时包含“进攻方痕迹”和“防守方痕迹”,而且关键信息大概率不是单独藏在某一个文件里,而是藏在两者“对抗”的关系里。
我当时列了两个可能的方向:
- 给一个大型 pcap,需要从流量里同时识别攻击行为和被攻击后的响应行为;
- 给一份日志,红队和蓝队的动作交替出现,需要按时间线配对分析。
实际打开文件之后,发现题目把这两条路都占了,但规模都不大。它考的不是“你会不会用某款大型分析平台”,而是“你有没有耐心把最基础的字段一个个看过去”。这也是 Misc 题经常出的一种风格:用最普通的文件格式,藏最容易被忽略的信息。
1.2 解压后真正拿到的东西
题目压缩包解压后只有三个文件,我列了一张表方便对照:
| 文件名 | 文件类型 | 大概大小 | 我的第一判断 |
|---|---|---|---|
| note.txt | 纯文本 | 1 KB | 提示信息,必看 |
| detect.log | 日志文本 | 约 2.4 MB | 蓝队检测日志 |
| capture.pcapng | pcap 抓包 | 约 8.5 MB | 红队流量痕迹 |
note.txt 内容很短,原文我记得大概是这样的:
Red and Blue are fighting in the dark. Red always starts, Blue always ends. The last trace is in the middle.这三行话其实是整个题目的题眼。第一行告诉你场景是红蓝对抗;第二行明确表示 flag 拼接时红队片段在前、蓝队片段在后;第三行“最后的痕迹在中间”暗示关键信息不是孤立的文件内容,而是藏在对抗过程的中间环节,比如成对的请求和响应之间。
1.3 赛前定下的解题顺序
拿到文件以后我没有直接翻流量包,而是把 detect.log 放在最前面看。原因很简单:日志是有明确时间戳和角色标记的结构化数据,能最快帮我建立整道题的时间线。流量包里的噪声大得多,如果连时间段和主机关系都没搞清楚就一头扎进去,很容易被无关流量带偏。
事实证明这个顺序是对的。等我把日志梳理完,再回头去看 pcap 的时候,已经知道要找哪个时间段的 ICMP 请求了,范围缩小了一大半。
2. 先读懂 detect.log 的结构,再谈提取信息
2.1 日志字段拆解
detect.log 看起来是一个经过脱敏处理的模拟检测日志,每行一条记录,用竖线分隔。第一眼瞄过去有点乱,但拆开以后结构很清晰:
时间戳|角色|源IP|目的IP|协议和端口|请求目标|规则ID|动作举个例子,我截取到的前几行是这样:
2021-09-11 19:02:11|RED|10.10.10.8|192.168.20.10|TCP/80|/index.php?id=1|12041|Alert 2021-09-11 19:02:11|BLUE|192.168.20.10|10.10.10.8|TCP/80|/blocked.php|12042|Block 2021-09-11 19:02:13|RED|10.10.10.8|192.168.20.10|TCP/80|/admin.php?action=ping|12141|Alert 2021-09-11 19:02:13|BLUE|192.168.20.10|10.10.10.8|TCP/80|/index.php|12142|Log角色字段只有两个值,RED 和 BLUE,这正好对应标题里的两个阵营。动作字段有 Alert、Block、Log 等几种,看起来是蓝队对不同红队行为的不同处置方式。
关键信息往往就藏在“看起来很正常”的字段里。当时我盯着 rule_id 看了半天,发现这些数字长得特别规整,几乎都是五位数,而且末位变化很有规律,有的是 41,有的是 42,像是被人刻意排过的。
2.2 红蓝动作成对出现的规律
继续往下翻了大概一百行,我发现一个明显规律:每一条 RED 记录后面,几乎都会跟着一条或几条 BLUE 记录,而且时间戳相差不超过两秒。这说明日志是按“红队动作触发蓝队响应”的逻辑生成的,而不是两条独立不相关的记录。
这个规律让我把思路从“分别分析两边的数据”调整成了“按一组一组配对来看”。于是我去统计了整个日志里 RED 和 BLUE 的总数:
awk -F'|' '{print $2}' detect.log | sort | uniq -c统计结果里 RED 有三百多条,BLUE 也有三百多条,数量上基本一比一。一个红队动作对应一个蓝队响应,这个关系对齐得这么完美,说明出题人很可能故意用这个结构来承载信息。顺着这个思路,我去翻了每条 BLUE 记录的规则ID,发现它们之间可能存在某种连续编码关系。
2.3 第一次踩坑:直接 grep 找不到目标
这里我必须说一个我自己踩过的坑。一开始我很想当然地直接在 detect.log 里搜flag或者base64,结果一条都搜不到。我一度以为是题目压根没用这种最常见的藏法,后来才发现问题出在文件编码上。
file detect.log的结果显示日志文件是 UTF-16LE 编码。这种编码下,用普通的grep去匹配 UTF-8 的 ASCII 字符串,因为每个字符中间都夹着一个\x00字节,根本匹配不上。直接读也是满屏乱码。
解决办法是先把编码转换成 UTF-8 再操作:
iconv -f UTF-16LE -t UTF-8 detect.log > detect_utf8.log转码之后再去 grep,整个世界就清净了。这个坑看起来很小,但在赛场上白白耽误了我十来分钟。建议大家以后拿到任何日志类文件,第一步先file,不要默认它是 UTF-8。
3. 红方片段藏在哪里:ICMP 流量与序列号排序
3.1 为什么选择 tshark 而不是直接开 Wireshark
日志捋顺之后,我开始处理 capture.pcapng。很多人第一反应是打开 Wireshark 图形界面一个个看包,但我更推荐直接用命令行版本的 tshark。
理由也很实际:
- 命令行操作可以精确提取字段,不用在图形界面里一层层点开每个包;
- 打印出来的数据可以直接用 sort、awk、tr 这些文本工具做流水线处理;
- 题目做完了还能把整条命令写成记录,方便赛后复盘复现。
在 CTF 的 Misc 题里,pcap 最常被利用的协议就那几个:DNS、HTTP、ICMP、ARP。排除法扫过一遍以后,我把注意力放到了 ICMP。因为日志里 RED 侧有大量针对内网主机的探测动作,这类流量在 pcap 里通常表现为连续不断的 ping 包,而 ping 包的 payload 区域经常被拿来藏东西。正常 ping 的 payload 一般是固定内容的回显数据,如果某个包的 payload 内容不一样,或者长度非常规整,那大概率有问题。
3.2 把 ICMP Echo Request 的 payload 全拉出来
我先筛选出所有 ICMP Echo Request(type 为 8),只提取两个字段:序列号(icmp.seq)和 payload 的十六进制数据(data.data)。
tshark -r capture.pcapng -Y "icmp.type == 8" -T fields -e icmp.seq -e data.data输出大概是这样的:
1 6162636465666768696a6b6c6d6e6f70 2 7162736465666768696a6b6c6d6e6f70 3 12131415161718191a1b1c1d1e1f20 ...第一列是序列号,第二列是 payload 的十六进制表示。前面几条看起来像随机数据,但中间混杂了一些有明显 ASCII 特征的片段,比如666c6167开头的十六进制串,转换成 ASCII 就是flag。这基本可以确定信息就在这里。
这里有个细节值得注意:我在 Wireshark 的图形界面里第一眼看到的 pcap 包顺序,和 icmp.seq 的顺序并不是完全一致的。有的包 seq 是 1,但捕获时间反而比 seq 为 2 的包晚。如果直接按 pcap 里的出现顺序拼接 payload,结果就是乱的。
3.3 排序拼接后 base64 解码
所以正确的做法是先按序列号排序,再把所有 payload 拼在一起:
tshark -r capture.pcapng -Y "icmp.type == 8" -T fields -e icmp.seq -e data.data | sort -n | awk '{print $2}' | tr -d '\n' > red.hex拼接完的 red.hex 看起来是一长串十六进制。接着我用 xxd 把它还原成二进制文件,再看它是什么编码:
xxd -r -p red.hex > red.bin file red.binfile red.bin显示这是一个 ASCII text,我用head瞄了一眼,发现是一串标准的 base64:
ZmxhZ3tyZWRfdnNfYmx1ZV93MXRoX3RyNGZmaWNfbase64 解码后就得到了前半段 flag:
base64 -d red.bin解出来是:
flag{red_vs_blue_w1th_tr4ff1c_到这里,红队片段拿到了。后半段目前还没有头绪,但结合 note.txt 里的“Red always starts, Blue always ends”,蓝队片段应该在日志里,而且位置要靠“数”出来。
3.4 为什么 ICMP 是出题人偏爱的隐蔽信道
我见过不少 Misc 题都把秘密信息放在 ICMP payload 里,这不是没有原因的。ICMP 里的数据字段平时很少有人逐字节检查,而且绝大多数防火墙和主机默认允许 ping 通过,流量不会显得突兀。出题人只需要把 flag 分段编码后塞进一个个 ping 包的 payload 里,再让这些包的 seq 保持连续,就可以让信息藏进一堆看似正常的探测活动中。
作为解题方,你要做的其实是反向操作:把连续请求的 payload 抽出来,按 seq 排序,然后尝试常见编码。base64 只是第一步,疯狂一点的题还会再套一层十六进制、二进制甚至异或。不过 red_vs_blue 这道题还算厚道,base64 就到了头。
4. 蓝方片段在“规则ID”的末位字节里
4.1 找出所有 BLUE 记录并按时间排序
红队片段解决之后,我回到转好码的 detect_utf8.log,把所有 BLUE 记录过滤出来:
grep '|BLUE|' detect_utf8.log输出有三百多行。我继续观察第七个字段,也就是 rule_id。下面是我连续抽取的十几条:
12041 12042 12141 12142 12241 12242 12341 12342 12441 12442 12541 12542 ...如果只是把它当成普通数字看,这些 ID 毫无规律。但把每组规则的末两位单独提出来,就会出现41 42 41 42 41 42这样明显的重复结构。我当时顺手写了个命令提取每个 ID 的最后两位:
awk -F'|' '/BLUE/ {print $7}' detect_utf8.log | awk '{print substr($0, length($0)-1, 2)}' | tr -d '\n'结果得到的十六进制串是:
666c61677b7265645f76735f626c75655f...前缀666c6167又是flag的 ASCII 十六进制。直接做十六进制转 ASCII:
echo '666c61677b7265645f76735f626c75655f...' | xxd -r -p得到:
4nd_l0g_4n4lysis_1s_fun}这就是完整的蓝队片段。和红队片段拼在一起:
flag{red_vs_blue_w1th_tr4ff1c_4nd_l0g_4n4lysis_1s_fun}到这里,整道题就算解出来了。
4.2 规则ID隐藏信息的原理
为什么要选规则 ID 而不是日志里的其他字段?因为规则 ID 长得太“正常”了。日志里出现一串五位数,你不会觉得奇怪,尤其是你根本不知道真实的检测规则 ID 应该长什么样的时候,很容易把它当成普通告警编号忽略过去。
出题人利用的正是这种“合理但不可解释”的字段。他把 ASCII 字符转成十六进制,再把十六进制的每两个字符嵌进规则 ID 的末两位,前面三位则用随机数或固定前缀填充。如果你不做频率统计和字段提取,很难发现这些数字之间有关联。
这个思路在真实的安全日志分析场景里也很有参考价值。攻击者经常会在 User-Agent、URL 参数、Cookie 等字段中混入编码后的数据,或者故意构造看起来合理的字符串来绕过检测。分析日志时,只盯着已知的恶意特征是不够的,更多时候要靠“找出数据中的异常规律”这种逆向思维。
4.3 第二个坑:不要盲目相信字段的固定长度
还有一个细节我必须提一下。我一开始提取规则 ID 的末两位时,用的是固定substr($0, length($0)-1, 2),默认所有 ID 都是五位数。后来我手动检查了一遍,发现如果真的把所有记录都按五位数处理,中间会出现错位。
原因是有几条 rule_id 是六位数,比如124011。如果直接取后两位,会取到11,但实际它对应的十六进制字节可能是4a之类的另一个值。遇到这种题,我最常用的做法是别急着一次性命令出结果,先抽样输出二十行看看尾字节的特征,再决定是按固定偏移还是按固定长度截取。
这步校对帮我省了不少时间,因为如果直接拿错位的 hex 去转 ASCII,解出来的字符串前半段是好的,后半段全是乱码,容易被误导成“还有第二层编码”。
5. 拼接前必须检查的三个细节
5.1 顺序不是拍脑袋,而是 note 里早有提示
很多人解到两个片段之后会纠结一个问题:哪段在前,哪段在后?其实 note.txt 里的第二句话早就写清楚了:Red always starts, Blue always ends。红队的片段永远是 flag 的开头部分,蓝队的片段是结尾部分。所以拼接顺序就是先 ICMP payload 里解出来的 red 片段,再接日志规则 ID 里解出来的 blue 片段,不需要猜。
这种提示在 CTF 里很常见,有时候是英文句子,有时候是藏头诗,有时候是图片里的水印。如果你做题时发现 flag 拼起来读不通,第一步不是去换编码,而是回头看看题目描述和附带的文本文件里有没有关于顺序的暗示。
5.2 编码和字符大小写的坑
拼接的时候我还注意到一个小问题:red 片段以flag{red_vs_blue_...开头,blue 片段以4nd_l0g_...开头,中间没有多余的分隔符。但如果你在提取过程中多了一个空格或换行,拼出来的字符串就会在 flag 里混入\n,提交的时候判题系统不会自动去空白,直接给你判错。
另外题目里 flag 使用的是全小写加数字的模式,我在解前半段时有一次把 base64 解码出来的内容全部转成了小写再拼接,差点把原本的w1th改成with,这种细节在提交时是致命的。建议所有尝试都基于原始解码结果,不要顺手做“美化”处理。
5.3 复盘:这道题最值得保留的技能
比赛结束之后再回看,我觉得 red_vs_blue 的价值不在于它的解题步骤有多复杂,而在于它把几件最基础的事情串在了一起:
- 拿到文件先
file看编码,避免在错误编码上浪费时间; - 解析日志前先拆字段,搞清楚每一列是什么再动手;
- 处理 pcap 时优先考虑用命令行批量提取,而不是靠肉眼翻包;
- 面对一堆数字和看似正常的字符串,要做频率统计和长度检查;
- 提取出来疑似编码内容后,按“先 ASCII、再 base64、再 hex”的顺序尝试解码。
就我个人而言,这道题最珍贵的经验反而是那个 UTF-16 的日志编码坑。它提醒我,以后无论做日志分析还是取证实操,第一件事永远是确认文件格式,而不是急着去搜内容。很多时候卡住你的不是题目本身,而是一个几秒钟就能避开的低级前提错误。
如果你最近也在刷 Misc,我建议你也拿这种“双文件联动”的题目练练手,逼自己把日志字段拆解和流量提取结合起来思考,而不是永远停留在“一个文件藏一段信息”的思维模式里。