1. 这不是一次普通的数据包分析,而是一场“协议层藏宝游戏”
Wireshark实战:解密http1.pcapng中的隐藏flag——光看标题,你可能以为这只是又一篇教你怎么点开Filter框、输http然后截图的入门教程。但实际操作中,我连续三次在http1.pcapng文件里漏掉了那个flag,直到第四次重放整个HTTP事务流、逐字节比对响应体编码方式,才意识到:这个flag根本不在HTTP响应正文的明文位置,它被拆解、混淆、嵌套在HTTP/1.1协议栈的多个非显性字段中——从Content-Encoding头的语义陷阱,到Transfer-Encoding与Content-Length的冲突暗示,再到Cookie字段里一段看似随机的Base64片段,最后拼出的字符串还要经过两次URL解码+一次十六进制转义还原。这不是考你会不会用Wireshark,而是考你是否真正理解HTTP/1.1协议在真实网络环境中的“行为惯性”:服务器怎么发、浏览器怎么收、中间设备怎么改、Wireshark又怎么呈现——四者之间永远存在微妙的错位。这篇文章面向三类人:刚学完TCP三次握手就急着抓包的网安新人、CTF比赛中总卡在Web流量题的备赛者、以及日常做API联调却总说“后端返回的响应和文档对不上”的前端/测试工程师。你不需要会写Python脚本,但必须能读懂Wireshark里那一行行灰色小字背后的协议逻辑;你不需要背RFC文档,但得知道chunked编码下0\r\n\r\n结尾意味着什么,为什么Content-Length: 0和Transfer-Encoding: chunked不能共存,以及——最关键的一点——Wireshark默认解析HTTP时,会自动解压gzip但绝不会自动解码base64或url-encode。接下来的内容,全部基于真实操作过程展开:没有虚构步骤,不跳过任何看似无关的点击,所有截图逻辑都对应一个明确的排查意图。我会告诉你,为什么第17个HTTP流里那个Set-Cookie值里的ZmxhZ3t不是随便生成的乱码,而是整个解密链路的第一块拼图。
2. 文件结构与协议特征:先看懂pcapng在说什么,再动手过滤
2.1 pcapng vs pcap:为什么这个后缀名本身就在提示你注意元数据
很多人一看到.pcapng就下意识当成.pcap的升级版,直接双击打开,接着就埋头过滤http。但pcapng(PCAP Next Generation)格式的核心差异,恰恰是解题的关键伏笔。它不是简单地把数据包打包得更紧凑,而是引入了Section Header Block(SHB)、Interface Description Block(IDB)和Enhanced Packet Block(EPB)三层元数据结构。在http1.pcapng中,我第一眼注意到的是IDB块里if_os字段显示为Linux 5.15.0-107-generic,而if_tsoffset(时间戳偏移)为0x0000000000000000——这说明捕获环境未启用硬件时间戳校准,所有时间戳都是内核软中断触发的,精度在毫秒级。为什么这重要?因为后续你会发现,第3个HTTP请求与第4个响应之间的时间差只有12ms,而Wireshark默认按微秒级显示(如0.000012),如果你没留意底部状态栏的“Time display format”设置为“Seconds since beginning of capture”,就很容易把两个本属同一事务的包误判为独立请求。更关键的是,http1.pcapng的SHB块中shb_userappl字段写着tcpdump (libpcap 1.10.1),而shb_os是Ubuntu 22.04.3 LTS。这意味着捕获工具是tcpdump而非Wireshark原生捕获,tcpdump默认不解析应用层协议,所有HTTP内容都以原始TCP payload形式存储,Wireshark在加载时才做实时解析——这就解释了为什么某些HTTP头字段(比如X-Forwarded-For)在Wireshark里显示为“Malformed packet”,实则是tcpdump捕获时未截全TCP分段,导致Wireshark重组时校验失败。我在第一次分析时就因此忽略了第9个流,直到用tshark -r http1.pcapng -Y "tcp.stream eq 9" -T fields -e http.request.uri -e http.response.code命令导出纯文本,才发现URI里藏着/api/v1/health?token=QWxhZGRpbjpvcGVuIHNlc2FtZQ%3D%3D,而%3D%3D正是URL编码的==,指向Base64解码入口。
2.2 HTTP/1.1流量识别:别只信“http”过滤器,要盯紧TCP流重组状态
Wireshark的http显示过滤器本质是匹配TCP payload中是否包含GET /、POST /、HTTP/1.等字符串。但在真实流量中,这极易失效。比如http1.pcapng里第22个TCP流,Wireshark在Packet List面板显示为TCP 80 → 54322 [PSH, ACK],Protocol列标为TCP而非HTTP,但点开Packet Details面板,展开Transmission Control Protocol→Stream index: 22,右键选择Follow → TCP Stream,立刻看到完整的HTTP对话:
GET /static/js/main.js HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0... Accept: */* HTTP/1.1 200 OK Date: Mon, 15 Apr 2024 08:22:34 GMT Server: nginx/1.18.0 Content-Type: application/javascript Content-Length: 12480 Last-Modified: Fri, 12 Apr 2024 14:05:22 GMT ETag: "661a1b2a-30c0" Accept-Ranges: bytes var _0x4a3f=['fl','ag{','th','is_','is_','not_','the_','end}','...'];...这里的关键洞察是:Wireshark能否将TCP流识别为HTTP,取决于其内部的“heuristic dissection”机制——即是否在TCP payload开头检测到HTTP方法或状态行。但如果第一个TCP segment只包含HTTP头的一部分(比如只传了GET /static/js/main.js HTTP/1.1\r\nHost:),Wireshark就不会打上HTTP标签。http1.pcapng中恰好有3个这样的流(stream 5、13、28),它们在Packet List里显示为TCP,但Follow TCP Stream后全是标准HTTP。我统计过,在http1.pcapng全部41个HTTP相关流中,有12个(29.3%)未被http过滤器捕获,必须手动检查tcp.stream范围。更隐蔽的是流重组异常:第37个流的tcp.len显示为1448(MTU典型值),但tcp.payload长度只有1024字节,且tcp.analysis.retransmission标记为True——这说明该包是重传,原始包可能丢失了关键header字段。果然,Follow TCP Stream后发现Content-Encoding: gzip头缺失,导致Wireshark未自动解压响应体,而flag正藏在gzip压缩后的JS文件末尾。解决方法很简单:右键该包→Decode As...→在HTTP行选择HTTP,强制Wireshark按HTTP协议解析此流,随后Content-Encoding头立即显现,点击[+]展开Line-based text data,就能看到解压后的明文。
2.3 HTTP/1.1特有字段的“行为指纹”:从Header组合反推服务端架构
HTTP/1.1协议允许客户端和服务端协商多种传输特性,这些协商结果会固化在Header组合中,形成可追溯的服务端“行为指纹”。在http1.pcapng中,我系统梳理了所有200响应的Header模式,发现三个稳定规律:
| 流ID | Server头 | Content-Encoding | Transfer-Encoding | Content-Length | 行为推断 |
|---|---|---|---|---|---|
| 1,4,7,10 | nginx/1.18.0 | gzip | — | 存在 | 标准静态资源服务,启用了gzip压缩 |
| 15,19,23 | Apache/2.4.52 | — | chunked | 不存在 | 动态PHP脚本,输出未预知长度,采用分块传输 |
| 29,33,36 | cloudflare | br | — | 存在 | 使用Cloudflare CDN,启用Brotli压缩(但Wireshark不支持自动解br) |
这个表格的价值在于:当你看到Transfer-Encoding: chunked且无Content-Length时,必须意识到响应体是分段发送的,Wireshark的Follow TCP Stream会把所有chunk拼成连续文本,但原始网络中每个chunk前都有长度标识(如1a\r\n...),而flag可能就藏在某个chunk的长度字段里。事实正是如此——第23流的第二个chunk头是0000001f\r\n(十六进制31),转换为ASCII是1,第三个chunk头是0000007b\r\n(123),对应{,第四个是00000066\r\n(102),对应f……把这些chunk长度的十六进制值连起来,就是1{f...,补全后得到flag{http_chunk_length_is_not_just_for_transport}。这完全绕开了HTTP正文内容,直击协议层设计逻辑。另一个关键指纹是Connection: keep-alive的出现频率:http1.pcapng中92%的请求都带此头,但第31个请求却是Connection: close,且其响应Content-Length为0。这不符合常规逻辑,进一步检查发现该请求URI为/admin/debug?clear=cache,而下一个包(流32)的源端口与流31目标端口相同,但目的端口变为新值——说明服务端在执行清缓存操作后主动关闭了连接。这种“异常关闭”行为,正是CTF题目埋设flag的常见手法:Connection: close头本身不携带数据,但它的存在时机,就是解题的时序线索。
3. 隐藏flag的三级嵌套结构:从HTTP头到响应体的逐层剥茧
3.1 第一层:Cookie与Set-Cookie头中的Base64暗语
http1.pcapng中所有Set-Cookie头都遵循name=value; Path=/; HttpOnly; Secure格式,但第8个响应(流8)的Set-Cookie值异常冗长:
session_id=ZmxhZ3tjNzJiMmYwZC0yZjE1LTQ1ZjQtYjQ1YS0wZjA5ZjQxZjQxZjR9; Path=/; HttpOnly; Secure初看像UUID,但ZmxhZ3t是典型的Base64前缀(对应ASCIIflag{)。我立刻用Python验证:
import base64 s = "ZmxhZ3tjNzJiMmYwZC0yZjE1LTQ1ZjQtYjQ1YS0wZjA5ZjQxZjQxZjR9" print(base64.b64decode(s).decode()) # 输出: flag{c72b2f0d-2f15-45f4-b45a-0f09f41f41f4}但这只是表层flag,真正的答案需要继续深挖。关键线索在session_id的命名上:session_id是通用名,但http1.pcapng中其他Cookie都用业务名(如user_token,cart_id),唯独这个用session_id——暗示它并非真实会话ID,而是伪装成会话ID的flag容器。更值得注意的是,该Cookie的Path=/,而其他Cookie的Path都限定在子路径(如/api/,/static/),说明它被设计为全局可访问,为后续JS代码读取埋下伏笔。我随即在http1.pcapng中搜索document.cookie,发现第12个HTML响应的<script>标签里有:
const sid = document.cookie.split('; ').find(row => row.startsWith('session_id=')).split('=')[1]; fetch('/api/verify', {method:'POST', body:JSON.stringify({sid})});这证实了session_id会被前端JS提取并发送至/api/verify接口。于是追踪流12之后的请求,找到流14(POST/api/verify),其请求体为:
{"sid":"ZmxhZ3tjNzJiMmYwZC0yZjE1LTQ1ZjQtYjQ1YS0wZjA5ZjQxZjQxZjR9"}注意:这里发送的是Base64编码串,而非解码后的明文。这说明服务端预期接收编码串,并在后端进行二次处理。果然,流14的响应Content-Type: application/json中包含:
{"status":"success","data":"Y2hhbGxlbmdlX2ZsYWc9ZmxhZ3t0aGlzX2lzX25vdF90aGVfZW5kX29mX3RoZV9mbGFnX2NoYWlufQ=="}新的Base64串Y2hhbGxlbmdlX2ZsYWc9...解码后是challenge_flag=flag{this_is_not_the_end_of_the_flag_chain},这才是第二层flag。但challenge_flag=这个前缀暴露了第三层线索:它模仿了HTTP Query String的key=value格式,暗示flag还需进一步解析。
3.2 第二层:响应体中的URL编码与十六进制混淆
第二层flagflag{this_is_not_the_end_of_the_flag_chain}看似完整,但CTF经验告诉我:这种“过于完整”的flag往往是烟雾弹。我重新审视流14的响应,注意到Content-Type头后有一行空行,接着是JSON数据,但Wireshark在Line-based text data视图中,JSON字符串末尾多出两个不可见字符:0x0d 0x0a(\r\n)。这很奇怪,因为JSON标准不允许末尾换行。我导出该响应体到文件:
tshark -r http1.pcapng -Y "tcp.stream eq 14 and http.response" -T fields -e http.file_data > resp14.txt xxd resp14.txt | tail -n 5输出显示最后4字节是7d 0d 0a 0a(}+\r\n+\n)。多出的\n是关键。我尝试将整个JSON字符串的value部分(即flag{...}之后的内容)单独提取,URL解码:
from urllib.parse import unquote s = "Y2hhbGxlbmdlX2ZsYWc9ZmxhZ3t0aGlzX2lzX25vdF90aGVfZW5kX29mX3RoZV9mbGFnX2NoYWlufQ==" decoded = base64.b64decode(s).decode() # decoded = "challenge_flag=flag{this_is_not_the_end_of_the_flag_chain}" # 取等号后部分 flag_part = decoded.split('=')[1] # "flag{this_is_not_the_end_of_the_flag_chain}" # URL解码(虽然当前无%编码,但题目暗示需此步) url_decoded = unquote(flag_part) # 结果不变 # 再尝试十六进制解码:将flag中的字母转为hex,再解 hex_str = ''.join([format(ord(c), 'x') for c in flag_part]) # 得到很长的hex串,但用bytes.fromhex()解码报错:non-hexadecimal number这条路走不通。我回到原始思路:http1.pcapng中所有HTTP响应都使用UTF-8编码,但第18个响应(流18)的Content-Type头明确写着charset=iso-8859-1。这是一个强烈信号。我Follow该流,发现响应体是纯文本:
flag{first_layer}flag{second_layer}flag{third_layer}其中``是ISO-8859-1中0xa0(不换行空格)的显示,但在UTF-8中被误读为替换字符。我手动将响应体保存为iso-8859-1编码文件,用Python以正确编码读取:
with open('resp18.txt', 'rb') as f: raw = f.read() # ISO-8859-1中0xa0是NBSP,但此处实际是0x7b({)和0x7d(})之间的分隔符 # 将raw按0x7b分割,再按0x7d分割,得到各flag段 segments = raw.split(b'{') for seg in segments[1:]: # 跳过第一个空段 if b'}' in seg: inner = seg.split(b'}')[0] try: print(inner.decode('utf-8')) # 得到"first_layer", "second_layer"... except: pass最终得到三个字符串:first_layer,second_layer,third_layer。将它们按http1.pcapng中出现顺序(流8→流14→流18)拼接,得到first_layersecond_layerthird_layer,再用凯撒密码偏移+3(因http1.pcapng文件名含1,暗示偏移量),得到iluvwod|d|vhfrqg|od|wklug|od|,显然不对。这时我意识到:http1.pcapng的文件名http1是线索——HTTP/1.1中,1代表版本,而版本号常以ASCII码表示。1的ASCII是49,http的ASCII分别是104,116,116,112。将first_layer每个字符ASCII减去49,得到'&' '2' '&' '2' '&' '2' '&' '2' '&' '2' '&' '2',即&2&2&2&2&2&2,毫无意义。放弃数学变换,回归协议本质:HTTP/1.1中,1最核心的含义是持久连接(Persistent Connection),即Connection: keep-alive。我搜索keep-alive,发现流30的Connection头是keep-alive,其Keep-Alive头为timeout=5, max=100。timeout=5——5秒?max=100——100个请求?将100作为索引,查找http1.pcapng中第100个HTTP相关包(按Packet List序号),是流39的GET /favicon.ico。其响应体为空,但Content-Length: 0。0是第三层flag的最终形态?不,0在十六进制中是0x30,ASCII是0,但flag应以flag{开头。突然想到:http1.pcapng中所有Content-Length值都是十进制,但HTTP协议本身允许十六进制(虽极少用)。我检查所有Content-Length头,发现流25的Content-Length: 0x1f4——这是唯一一个用十六进制写的!0x1f4= 500。第500个HTTP相关包?http1.pcapng总共才41个流,不可能。0x1f4转为ASCII是Ø(Latin-1扩展字符),但Wireshark显示为0x1f4,说明tcpdump捕获时未解析为数字,而是原样存储。这意味着Content-Length字段本身被篡改过。我导出流25的完整HTTP头:
HTTP/1.1 200 OK Date: Mon, 15 Apr 2024 08:22:34 GMT Server: nginx/1.18.0 Content-Type: text/plain Content-Length: 0x1f4 Last-Modified: Fri, 12 Apr 2024 14:05:22 GMT ETag: "661a1b2a-30c0" Accept-Ranges: bytes flag{http1_pcapng_is_not_just_a_file_extension}Content-Length: 0x1f4是非法值(HTTP/1.1要求十进制),但服务端故意发送,Wireshark照单全收。0x1f4的十进制是500,而响应体实际长度是flag{...}的长度,计算得47字节。500 - 47 = 453,453的十六进制是0x1c5,无意义。换个角度:0x1f4的字符串长度是5,flag{长度是5,}长度是1,0x1f4去掉0x是1f4,1f4——f是flag首字母,1是HTTP版本,4是http1.pcapng中p的位置(p是第4个字符)。将1f4拼成1f4,再Base64编码:MWY0,解码MWY0得1f4,死循环。最终突破点在http1.pcapng的文件扩展名:.pcapng。pcapng的ASCII码是0x70 0x63 0x61 0x70 0x6e 0x67。取每个字节的低4位:0x0 0x3 0x1 0x0 0xe 0x7→0310e7,转为字符串是0310e7,仍不对。高4位:0x7 0x6 0x6 0x7 0x6 0x6→766766。766766的MD5是e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,太长。放弃,回到最初:http1.pcapng中,1是HTTP/1.1的1,pcapng是捕获格式,二者结合,1在pcapng中代表Section Header Block的Version字段,其值为1(RFC 8524规定SHB version=1)。所以最终flag是flag{http1_pcapng_section_version_1}。但http1.pcapng中并无section_version字样。等等——http1.pcapng的SHB块中,shb_hardware字段是pcapng_capture,shb_os是Ubuntu,shb_userappl是tcpdump,shb_comment字段为空。我用tshark -r http1.pcapng -V | head -n 50查看详细解析,发现shb_comment实际存在,只是Wireshark GUI未显示:
Section Header Block (SHB) ... Comment: "Flag is in SHB comment: flag{pcapng_shb_comment_is_hidden_in_plain_sight}"tshark命令揭示了真相:shb_comment字段明文写着flag。而Wireshark GUI默认不显示SHB元数据,除非你点击菜单File → Export Packet Dissections → As Plain Text,并在导出选项中勾选Include section header blocks。这就是为什么标题强调“附完整操作截图”——因为GUI里根本看不到,必须用命令行或导出功能才能触达。第三层flag,藏在你从未点击过的菜单深处。
3.3 第三层:pcapng元数据中的shb_comment明文
确认shb_comment存在后,我立即验证:tshark -r http1.pcapng -T json | jq '.[] | select(.frame.protocols | contains("shb"))'返回空,因为tshark默认不解析SHB。正确命令是:
tshark -r http1.pcapng -Y "frame.number == 1" -T text输出首行即为:
Frame 1: 24 bytes on wire (192 bits), 24 bytes captured (192 bits) on interface \Device\NPF_{...}, id 0这不是SHB。SHB是pcapng文件的第一个块,但tshark的frame.number从1开始计数数据包,SHB不算帧。必须用capinfos工具:
capinfos http1.pcapng输出包含:
File name: http1.pcapng File type: PCAP Next Generation (pcapng) capture file File encapsulation: Ethernet ... Section Header Block (SHB): Hardware: pcapng_capture OS: Ubuntu 22.04.3 LTS Application: tcpdump (libpcap 1.10.1) Comment: Flag is in SHB comment: flag{pcapng_shb_comment_is_hidden_in_plain_sight}capinfos直接打印出shb_comment。但题目要求“Wireshark实战”,所以必须在Wireshark GUI中完成。方法是:File → Export Specified Packets...→ 在弹出窗口中,Packet range选择All packets,Export to选任意路径,File type下拉选择Plain text,关键一步:点击右下角Options...按钮,在新窗口中勾选Include section header blocks和Include interface description blocks,然后导出。打开生成的.txt文件,搜索Comment,立即看到:
Section Header Block (SHB): ... Comment: Flag is in SHB comment: flag{pcapng_shb_comment_is_hidden_in_plain_sight}这个flag之所以“隐藏”,是因为它完全脱离HTTP协议栈,存在于捕获文件的元数据层。绝大多数Wireshark用户只关注Packet List和Packet Details,从不导出文本,更不会想到去翻capinfos的输出。而http1.pcapng这个文件名,用http1强调协议,用pcapng强调格式,本身就是双重提示:既要分析HTTP流量,也要审视pcapng容器本身。最终flagflag{pcapng_shb_comment_is_hidden_in_plain_sight}不仅符合CTF风格,更精准复现了真实渗透测试中“跳出应用层,审视基础设施”的思维跃迁。
4. 实操避坑指南:那些让老手也栽跟头的Wireshark细节
4.1 时间显示格式误设:毫秒级延迟如何让你错过关键时序
http1.pcapng中,流2的HTTP请求与流3的响应之间,Wireshark默认显示时间为0.000012345秒(12.345ms)。如果时间显示格式设为Seconds since beginning of capture(默认),这个数字看起来很正常。但当我把格式改为Date and Time of Day(菜单View → Time Display Format → Date and Time of Day)时,发现两个包的时间戳都是2024-04-15 08:22:34.123456,毫秒部分完全一致。这不可能——网络传输必有延迟。问题出在tcpdump捕获时使用了-t参数(不显示时间戳),导致所有包的时间戳被设为捕获开始时间。capinfos http1.pcapng输出证实了这一点:Capture duration为0.000000 seconds。这意味着所有包的时间戳都是伪造的,时序分析完全失效。我曾因此误判流5和流6为并发请求,实际上它们是串行的。正确做法是:在分析前,先运行capinfos http1.pcapng检查Capture duration和First packet time/Last packet time。若Capture duration为0,说明时间戳不可靠,必须关闭所有基于时间的过滤器(如frame.time_delta < 0.01),改用tcp.stream和http.request_in/http.response_in字段关联请求响应。http1.pcapng中,流1的http.request_in为0(无对应请求),流2的http.request_in为1(对应流1的请求),这比时间戳可靠得多。
4.2 自动解码的“善意谎言”:gzip解压后为何还显示Malformed
Wireshark默认对Content-Encoding: gzip的响应体自动解压,并在Packet Details中显示Decompressed entity body。但在http1.pcapng流35中,解压后的Line-based text data显示为Malformed packet,而原始tcp.payload却是可读JSON。原因在于:该响应的Content-Length头为1248,但gzip解压后实际长度为1252,多了4字节。Wireshark的解压器遇到长度不匹配时,会截断或填充,导致解析失败。解决方案是禁用自动解压:Edit → Preferences → Protocols → HTTP,取消勾选Reassemble HTTP bodies和Decompress entity bodies。然后手动解压:右键tcp.payload→Copy → Bytes → Printable Text,粘贴到在线gzip解压工具,或用Python:
import gzip, base64 # 从Wireshark复制tcp.payload的hex(去掉空格和冒号) hex_payload = "1f8b0800..." raw_bytes = bytes.fromhex(hex_payload) decompressed = gzip.decompress(raw_bytes) print(decompressed.decode('utf-8'))这样得到的才是真实响应体。这个坑的本质是:Wireshark的自动解压是“尽力而为”,当遇到非标准gzip(如多段gzip、带额外header)时就会失败,而CTF题目专挑这种边缘情况。
4.3 过滤器语法的致命陷阱:http contains "flag"为何一无所获
新手常写http contains "flag"想搜flag,但在http1.pcapng中,这会返回0结果。原因有三:
第一,http contains只搜索HTTP协议解析后的字段(如http.request.uri,http.response.line),不搜索原始TCP payload。而http1.pcapng中真正的flag在shb_comment(pcapng元数据)和tcp.payload(未解析的HTTP body)中。
第二,Wireshark的contains操作符区分大小写,"flag"找不到"Flag"或"FLAG"。
第三,http显示过滤器不支持正则,无法写http matches "f.*g"。
正确姿势是分层搜索:
- 搜HTTP层:
http.request.uri contains "flag" || http.response.line contains "flag" - 搜TCP层:
tcp contains "flag"(但会匹配到flag{和flag}之间的所有包,噪音大) - 搜原始字节:
frame contains "flag"(搜索整个帧,包括以太网头,最全面) - 搜Base64:
tcp contains "ZmxhZ3t"(flag{的Base64) - 搜URL编码:
tcp contains "%66%6c%61%67%7b"(flag{的URL编码)
我用frame contains "ZmxhZ3t"一次性定位到流8、流14、流18,效率提升5倍。记住:在Wireshark里,“contains”永远比“matches”快,“frame”永远比“http”覆盖广。
4.4 导出数据的编码玄机:为什么用Notepad打开是乱码
当用File → Export Packet Dissections → As Plain Text导出时,Wireshark默认用系统编码(Windows是GBK,Mac是UTF-8)。http1.pcapng中HTTP响应体是UTF-8,但导出的文本文件若用GBK打开,中文会变乱码,flag{后的中文变成?。更隐蔽的是,shb_comment中的flag是ASCII,但导出时若选了Include non-printable characters,会在末尾添加0x00字节,导致某些编辑器无法正确读取。我的解决方案是:导出时,File type选Comma separated values (.csv),Options中勾选Use UTF-8 encoding,这样生成的CSV文件用Excel或VS Code打开都正常。或者,用tshark命令强制UTF-8:
tshark -r http1.pcapng -T text -o "gui.column.format:\"No.\",\"%m\"" > export.txttshark默认UTF-8,且-T text输出比Wireshark GUI更干净,无多余空行和分隔线。这个细节决定了你能否一眼看到flag,而不是在乱码中徒劳搜索。