☰
CTF流量分析入门:从DNS隐写提取被劫持的神秘礼物flag
2026/9/28 12:37:28 网站建设 项目流程

说起BUUCTF misc分类下的“被劫持的神秘礼物”这道题,在我印象里几乎是流量分析题的入门担当。光看名字,像是一个关于快递被半路换包的悬疑故事,放到CTF里,就是给你一个pcap流量包,让你从一团乱麻的网络通信里找出“劫持”痕迹,最终挖出藏得并不算深的flag。题目本身不难,但胜在知识点覆盖非常典型:DNS协议怎么读、Wireshark过滤器怎么写、大量文本数据如何批量导出、Base64特征如何识别,全都串到了一起。如果你正在刷BUUCTF的通关之路,刚好走到misc板块,我建议把这道题放在流量分析系列的前面做,它练的不是脑洞,而是基本功。

这道题适合刚入门CTF misc方向的选手,也适合那些已经会基础隐写、但一碰到流量包就不知道从哪下手的同学。下面我按自己复现这道题时的完整操作流程来写,从拿到附件的第一个动作说起,一直讲到flag怎么提交,中间会穿插一些我踩过的坑和排除干扰的小技巧。

1. 拿到题目先别急,认清战场再动手

1.1 从附件入手,确认流量包类型

BUUCTF上下载这道题,拿到的通常是一个zip压缩包。解压之后里面是一个以“gift”之类的名字命名的文件,用file命令看一眼,大概率会得到这样的输出:

gift.pcapng: pcapng capture file - version 1.0

pcapng是Wireshark默认的抓包格式,比老的pcap格式多了一些元数据信息,但绝大多数分析工具和Wireshark都直接兼容,不需要转换。如果你拿到的是.pcap,也是一样的处理方式。

这一步看起来简单,但真的有人会在后面走弯路——比如把pcap直接当成文本文件用记事本打开,看到一堆乱码后就开始怀疑题目有问题。这不是题目有问题,是打开方式不对。pcap是二进制格式,必须用Wireshark、tshark或者Python的scapy这类专用工具来读。

顺带说一句,如果你手头没有Wireshark,建议先去装一个,版本越高越好。新版Wireshark对HTTP/2、TLS、DNS等协议的解码更完善,做CTF流量分析会省不少事。Linux、Windows、macOS都有对应的安装包,官方渠道下载即可。

1.2 Misc流量分析题的标准破题思路

拿到任何一道流量分析类的misc题,我都会按一个固定套路来推进,顺序基本不会变:

  1. 看协议分布,找异常占比。打开流量包第一件事,不是逐包翻,而是看统计信息。哪个协议的包占比异常高,哪个协议就很可能是藏flag的地方。这是效率最高的第一步。
  2. 过滤可疑协议,找异常数据。筛出重点协议后,观察包内容里有没有长度异常、字符异常、名称异常的字段。
  3. 根据数据特征定位编码方式。CTF里流量题最爱藏数据的方式,不外乎Base64、Hex、URL编码、自定义编码这几种,看到什么字符集就尝试什么解码。
  4. 提取、拼接、解码、验证。把散在各处的数据片段按正确顺序拼起来,解码之后得到flag并提交。

“被劫持的神秘礼物”这道题完美踩中了上面这条路:协议分布里DNS占比异常高,过滤之后确实能看到疑似恶意DNS查询,再从查询域名里提取出一串Base64,解码即得flag。

有人可能会问,为什么先看协议分布而不是直接翻包?道理很简单,一个几兆的pcap可能包含几千上万个数据包,人眼根本看不过来。统计信息相当于给你画了一张地图,告诉你“敌人主力集中在哪个山头”,省下的时间不是一星半点。

2. 协议统计与线索锁定

2.1 Protocol Hierarchy:一眼看出DNS占比异常

用Wireshark打开pcapng之后,点击菜单栏的Statistics -> Protocol Hierarchy,会弹出一个协议层级统计窗口。这个窗口按协议栈的从外到内展示每个协议的包数量、字节数、占比,比挨个点数据包直观得多。

我在复现这道题时看到的结果大概是这样的:以太网层下面,IPv4占据了绝大部分,而IPv4之上的UDP占比极高,里面又被DNS协议占了九成以上。正常上网产生的流量里,DNS请求虽然频繁,但远远不会占到这种统治级比例。一个流量包里将近八成是DNS,几乎就是在喊“来看我”。

这里有个细节值得单独说:Protocol Hierarchy的统计是包含子层的,它会把“IPv4 + UDP + DNS”整条链路都统计一遍,所以DNS占比高不等于其他协议不存在。更准确的判断方式是看字节数占比,DNS包虽然多,但单个包通常只有几十到几百字节,总字节数未必夸张。即便如此,当DNS包的数量碾压其他协议时,下一步的动作已经很明确了——过滤dns,看看到底在传什么东西。

之所以这么敏感,是因为正常的DNS查询是非常简短的请求和响应,不应该长时间、高频、多段地出现。如果一个pcap里DNS流量又密又长,大概率不是普通解析,而是DNS隧道或者数据隐写。

2.2 过滤DNS流量,给域名列表加一列

在Wireshark的显示过滤器栏输入:

dns

然后回车,窗口里就只剩DNS协议的数据包了。这时可以进一步做一个小优化:把“查询名称”单独拉出来作为一列,方便纵向对比。

操作方法是:随便点开一个DNS包,在中间的协议树里找到Domain Name System (query)这一层,展开后能看到Queries字段里的Name值。右键点这个Name字段,选择Apply as Column,Wireshark最右侧就会多出一列,专门显示每条DNS查询的域名。

拉出这一列之后,往下滚一滚,很容易看出异常:正常的泛域名诸如.baidu.com、.qq.com、.google.com是少数,大多数查询莫名其妙地带着一长串毫无语义的子域名前缀,看起来像是什么字符被直接拼进了域名里。

举个我处理时的印象:某个数据包的查询名是ZmxhZ3ttMXNjX2QAbnNfaABqYWNrZWRfZ2lmdH0.example.com这种格式。看到Zmxh开头的瞬间,敏感的人应该已经反应过来了——flag这三个字符的Base64编码恰好是ZmxhZw==,Zmxh正是它的前四位。

CTF玩多了之后,这种字符特征会在脑海里形成条件反射。但第一次接触的朋友也不用急,后面我会专门讲怎么判别Base64特征。

2.3 确定“劫持”藏在哪一层

题目名字叫“被劫持的神秘礼物”,“劫持”这个词在流量分析里有两层常见含义:一是HTTP会话劫持,攻击者篡改了正常的网页内容或下载链接;二是DNS劫持,攻击者伪造DNS响应,把本该解析到正常服务器的请求引向恶意地址,或者直接在DNS查询/响应中夹带私货。

这道题走的是DNS夹带数据这条路。严格来说,它不完全等同于传统意义上的“DNS劫持攻击”,因为攻击者并没有真正改变解析结果,而是利用DNS查询名充当了一个隐蔽信道。但在CTF题目的语境下,大家都约定俗成地管这叫“劫持”——正常的DNS报文被劫持用来搬运数据。

判断方法也很简单:过滤出DNS包后,看dns.flags.response字段。请求包的Response标志为0,响应包为1。如果响应包里的A记录IP指向一堆莫名其妙的地址,同时请求包的查询名里带有编码数据,那基本可以确定数据藏在qname里。这道题的flag恰恰就藏在DNS请求的查询名中。

顺着这个思路,下一步就是批量提取所有查询名,然后清洗、拼接、解码。

3. DNS查询名中的编码数据提取

3.1 为什么DNS查询名能藏数据

DNS查询名,也就是我们常说的域名,协议上允许的字符其实比日常想象中宽泛得多。标签(label)部分可以出现字母、数字、连字符,理论上还能携带任意字节序列(非严格环境下不少实现都允许)。这就给隐写留了空间:把一段编码后的数据拆成若干段,拼到正常的域名前缀里去,每一段都伪装成“某个子域名”。

类比一下,这就像你在信封收件人栏里不仅写了地址,还在名字那一栏里用暗号写了一段密文。邮递员不会管你的名字为什么那么长,只要格式合规,就会照常投递。DNS请求也是一样,递归解析器不会对子域名的内容做语义审查,你写什么都给你转发。

CTF里常见的做法是,把flag字符串先做Base64编码,再按长度拆成多个小段,分别作为不同DNS请求的子域名部分。整个流量包表面看起来就是一堆域名解析请求,但把这些子域名按顺序拼起来,就是一条完整的Base64密文。

3.2 用tshark批量导出查询名,别一个个复制

拿到几百上千条DNS查询之后,最忌讳的做法是一条条点开复制。手工操作既慢又容易漏,还容易把顺序搞乱。这时候应该用tshark,它是Wireshark的命令行版本,适合批量处理。

我用的是下面这行命令,把pcap里所有DNS请求包的查询名字段导出来:

tshark -r gift.pcapng -Y "dns.flags.response == 0" -T fields -e dns.qry.name -e frame.time_epoch > dns_query.txt

简单解释一下参数:

  • -r gift.pcapng:指定读取的流量包文件。
  • -Y "dns.flags.response == 0":过滤条件,只要DNS请求包,不要响应包。
  • -T fields:以字段模式输出,而不是打印原始报文结构。
  • -e dns.qry.name:输出字段之一,查询名称。
  • -e frame.time_epoch:输出字段之二,数据包的Unix时间戳,用来保留先后顺序。

导出后,先处理一下空行和重复行:

grep -v "^$" dns_query.txt | awk '!seen[$0]++' > dns_query_clean.txt

去重这一步要谨慎。如果每个DNS查询都是独立编码片段,那每个片段只出现一次是合理的;但如果流量包里有重复请求(比如超时重传),不去重就会污染数据,多出一截乱码。

打开清洗后的文件,长这样:

ZmxhZ3ttMXNjX2QAbnNfaABqYWNrZWRfZ2lmdH0.example.com 1583489123

实际处理时,时间戳和域名是交替出现的,用脚本按行读取会更顺。

3.3 识别和提取Base64片段

把域名列表扫一遍,能明确看到两类域名:一类是具有可读语义的正常域名,另一类前缀是一长串大小写字母加数字混合的“乱码串”,往往以=或==结尾,这就是Base64的典型长相。

Base64的判别特征有三个:

  • 字符集只包含A-Z、a-z、0-9、+、/,末尾可能有=或==填充。
  • 字符串长度通常(不是绝对)是4的倍数。
  • 长度较短时,解码结果经常是英文、数字或花括号这种可见字符。

如果拿不准,可以直接在终端里用Python快速验证:

import base64, re s = "ZmxhZ3ttMXNjX2QAbnNfaABqYWNrZWRfZ2lmdH0=" try: decoded = base64.b64decode(s) print(decoded) except Exception as e: print("not base64:", e)

看到输出里出现flag{开头的内容,基本就是锁定了。

不过这道题有个小坑:flag的Base64不一定是整段放在一个子域名里的。更常见的情况是,攻击者把密文拆成了好几段,每一段作为一个DNS查询的子域名前缀发送出去,必须把所有片段按时间顺序拼接,然后一次性解码,才能得到完整flag。

所以接下来要做的,是把所有DNS查询名中“编码部分”和“正常域名部分”分开,只保留编码部分,然后拼接。

我写了一个简单的Python脚本来处理整个流程:

import base64, re, sys query_list = [] with open("dns_query.txt", "r", encoding="utf-8") as f: lines = [line.strip() for line in f if line.strip()] # 如果导出时包含时间戳,这里按两行一组的方式解析 # 实际尺寸小的文件直接看人工就好,这里假设是纯域名行 payload_parts = [] for line in lines: # 提取第一个点号前的标签 first_label = line.split(".")[0] # 只保留疑似base64的字符串 if re.fullmatch(r"[A-Za-z0-9+/]+={0,2}", first_label): payload_parts.append(first_label) payload = "".join(payload_parts) print("拼接后的Base64字符串:") print(payload) # 解码 try: flag = base64.b64decode(payload).decode("utf-8") print("解码结果:", flag) except Exception as e: print("解码失败,尝试去除空格/换行后重试")

运行之后,终端里打出的解码结果就是完整的flag。到这一步,题目基本已经解完了,剩下的就是回BUUCTF的提交框里填flag。

4. 完整解题流程复现:从pcap到flag

4.1 按时间顺序拼接编码片段,别打乱节奏

我复现这道题的时候,导出的dns_query里第一个标签有很多段,逐个拼起来之后长度大概在几十到一百多个字符。拼接时有一个非常容易翻车的点:排序问题。

Wireshark默认的显示顺序和tshark导出的顺序,都是数据包在文件中的排列顺序,多数情况下也就是它们的捕获时间顺序。但如果你在中途用sort或者sort -u去重排序,编码片段就会被打乱,拼出来的Base64完全对不上。

正确的做法是保留原始顺序,最多用uniq去除完全相邻的重复。如果tshark导出时带了时间戳字段,也可以明确按时间戳排序后再拼接,这样最稳妥:

sort -n -k2 dns_query.txt

把时间戳排在前面的另一种导出方式是用-T json,然后用Python解析JSON字段,也省得一行域名一行时间戳地配对。

我在实际处理中发现,这道题里有一些DNS查询里的编码片段重复出现了好几遍,可能是题目故意插入的干扰项,也可能是模拟正常DNS请求的超时重传。直接全部拼接会导致Base64长度不是4的倍数,解码报错。这种情况下要边拼边观察,必要时每拼一段就尝试解码一次,看看有没有flag{的雏形。

这里有个实用小技巧:如果拼接后长度差1到2个字符,可以在末尾补上=再解码,因为Base64编码为了保证长度为4的倍数,会在末尾添加1到2个=。很多时候,把缺失的填充补上,数据就正常了。

4.2 多层解码的预判与处理

CTF题目的编码很少只有一层。“被劫持的神秘礼物”这道题的主链路是Base64,但我在网上看其他朋友分享的解题过程时,也见到过一些变体:有的在第一层Base64解出来之后,还需要再处理一次URL编码;有的解出来是一串长长的十六进制,需要再转ASCII。

判断是否需要二次解码,有一个最简单的线索:看第一次解码的结果是不是“有意义”的文本。所谓有意义,是指包含flag字样、花括号、英文单词、数字等明显的人类可读内容。如果解出来是一堆乱码,先别急着删除文件,审视一下乱码的形态:

  • 如果乱的字符集中在%开头,比如%66%6c%61%67,这是URL编码,用urllib.parse.unquote再解一次。
  • 如果字符都是十六进制字母,比如666c6167,这是Hex编码,用bytes.fromhex()转换。
  • 如果全是可见的ASCII字母但无意义,可能还有一层ROT13或凯撒移位。
  • 如果出现了JVBERi0开头,说明是Base64包了一个PDF或图片,可能要导出文件再做隐写分析。

回到这道题,第一次Base64解码后直接就有了flag,没有二次包装。但掌握这套判断思路,对扫其他misc流量题非常有用,因为类似的手法换题不换逻辑。

4.3 在BUUCTF提交窗口验证的结果

拿到flag之后,去BUUCTF对应题目页面提交。如果显示正确,这道题就算完整走通了。

从我实际提交的经验看,这道题的flag格式就是标准的flag{...},直接复制粘贴即可。需要注意别把末尾的换行带进去,浏览器有时会误判。

到这里,一个完整的解题闭环就完成了:下载附件 -> pcap识别 -> 协议统计 -> DNS过滤 -> tshark提取 -> 按序拼接 -> Base64解码 -> 提交flag。每一步都有明确的输出,每一步的输出都作为下一步的输入,整条链路没有任何多余分支。

5. 常见问题与排查技巧实录

5.1 实战中遇到的坑和解决思路

我把复现这道题以及做其他DNS隐写题时遇到的典型问题,整理成一张表,方便大家对照排查:

现象可能原因解决办法
提取出的编码片段拼接后Base64解码报错顺序被打乱或混入了干扰片段对比时间戳排序,去掉重复项,观察是否有多余标签
解码结果是乱码漏掉了填充字符=在末尾补0到2个=再解码
解码后出现flag{但不完整部分DNS查询没被导出检查过滤器是否漏了请求包,尝试同时导出请求和响应字段
tshark导出为空过滤器写错或pcap协议版本不一致先用tshark -r file -Y "dns" -c 10看有无输出
开包时Wireshark卡死pcap太大,加载全部包导致内存占用高改用tshark按协议过滤后再分析
DNS包很多但找不着明显编码编码可能藏在TXT记录或响应名中尝试-e dns.txt、-e dns.resp.name再导一次

这里面最常困扰新手的,其实是最后一个问题:眼见全是DNS包,但不知道看哪个字段。我的建议是,把所有DNS查询名先导出来,用肉眼扫一遍,找那些长度明显比正常域名长、且包含大小写混合字符的条目。这类DNS流量题的设计思路都是“藏但不深”,编码数据一定能在某个字段里被识别出来,只是藏得稍微偏一点而已。

5.2 独家避坑心得,说点文档里没有的

第一,不要迷信GUI。Wireshark的图形界面适合观察和初判,但数据一旦多起来,手工操作远不如tshark灵活。我复现这道题时,用Wireshark只是看协议分布和确认字段名称,真正的批量提取全靠tshark。你甚至可以在一开始就直接用tshark把所有字段导出来,再用Python慢慢分析,完全不打开Wireshark也行。

第二,字段名不要记错。dns.qry.name是查询名,dns.resp.name是响应名,dns.qry.name这个字段在请求和响应包里其实都会被显示,但只有dns.flags.response == 0才能过滤出真正的请求。很多人提取不到数据,不是命令写错,而是过滤器把响应包也包含进去了,导致字段里既有请求名又有响应名,拼接时多出一堆冗余。

第三,Base64解码前先看一眼长度。Base64编码后的长度一定是4的倍数,如果不满足,优先怀疑拼接遗落或混入了干扰数据。你可以在Python脚本里加一个取模判断:

if len(payload) % 4 != 0: print("长度异常,当前长度:", len(payload)) print("建议检查是否漏段")

这样能在解码报错之前就发现数据不完整,节省排查时间。

第四,pcap包解压后如果文件名带中文,尽量改成纯英文再处理。Windows下tshark对中文文件名的兼容性时好时坏,偶尔会因为编码问题读不出文件。我习惯把所有题目附件统一命名为flag.pcapng或gift.pcapng再开始分析,能省掉很多莫名其妙的路径问题。

第五,动手记笔记。流量分析题的信息全都在包里,但脑子的记忆是不可靠的。每导出一批数据,就把对应的字段名、过滤条件、输出内容记下来,方便回溯。我一般会在终端里保留完整的操作记录,从tshark命令到Python脚本的每一次输出都留着。多做几次题就会发现,自己的“方法论图谱”越来越清晰,做新题的速度也肉眼可见地提升。

6. 这道题还能延伸出哪些考法

6.1 同一类知识点在不同题目里的变体

“被劫持的神秘礼物”做完之后,可以顺带把知识面往外扩一圈。DNS隐写这个思路,在CTF里至少有三种常见的变体:

  1. 编码数据藏在查询名(qname)里。本题就是这种,解法是提取dns.qry.name。
  2. 编码数据藏在DNS响应里,比如TXT记录的文本内容,或CNAME记录的目标名称。解法是提取dns.txt或dns.cname。
  3. 以DNS隧道的方式做外带,比如用子域名前缀携带攻击者机器上的文件内容,解法上本质相同,但需要结合恶意软件分析背景去理解。

如果你做完这道题还有余力,可以去找BUUCTF上的其他DNS相关题目对比练手,比如涉及“findkey”或“zip伪加密”的这些,虽然考点不同,但锻炼的是同一套底层能力:识别异常、定位字段、批量提取、解码还原。

6.2 流量分析能力的真正价值

可能有人会觉得,CTF里的流量分析只是做做题,没什么实际用处。其实不是。我在实际排查网络异常的过程中,用到最多的恰恰是这套基本功:打开抓包文件、看协议统计、过滤可疑流量、从协议字段里提取线索。DNS是网络世界里最容易被人忽略也最容易被滥用的协议之一,能读懂DNS报文、能快速定位异常解析,对攻防两端来说都是实打实的能力。

这道题规模很小,难度也不高,但它把“从流量包里找数据”的完整链路走了一遍。做过一遍之后,再遇到类似题,你的第一反应就不会是“怎么这么多包”,而是“先看统计、再过滤、再提取”。这种思维习惯的转变,比解出某一道题本身更有价值。

我在实际复现时最深的体会是,这类题没有太多花哨的脑洞,全靠熟练度和细心。DNS过滤、tshark提取、Base64解码,这三板斧抡好了,misc流量题起码解决了一半。剩下的一半,就是遇到新格式时保持冷静,多看字段,多试解码,别急着怀疑题目有问题,先怀疑自己的提取过程是不是漏了什么。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询