4. pcap 文件格式解析:从字节读懂每一帧
上一章我们讲了怎么抓包。但抓下来的 .pcap 文件,里面到底是什么?
我个人习惯,拿到一个文件,先用 hexdump 扒开看看。这就像医生看 X 光片,底层数据不会骗人。pcap 格式其实不复杂,搞懂了它,你就知道协议分析仪是怎么把电信号还原成我们能看懂的请求和响应的。
4.1 pcap 的三层结构
一个标准的 pcap 文件,说白了就是三层结构叠在一起:
- 全局头(Global Header):文件的总开关。告诉工具「这个文件是谁写的、时间精度是多少、里面存的是什么类型的数据包」。
- 数据包包头(Packet Header):每一帧的说明书。记录了这帧包的真实抓取时间、实际长度、在文件里占多大空间。
- 数据部分(Packet Data):赤裸裸的二三层协议帧。以太网帧、IP 报文、TCP/UDP 段,都塞在这里。
记住这个顺序:「1 个全局头 → N 个(包头 + 数据)」。你用 Wireshark 打开一个 pcap,看到的每一个包,对应文件里都是一组连续的「包头+数据」。
你看看上面这张图,这就是 pcap 在磁盘上的真实样子。每个包都是「说明书 + 货」的组合,简单粗暴。
4.2 全局头:文件的身份证
全局头固定 24 字节。我刚开始学的时候,觉得前 4 字节「魔数」最有用。你想想看,如果工具不认识这个文件,怎么打开它?
| 偏移量 | 大小 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 4 字节 | Magic Number | 固定 0xa1b2c3d4 或 0xd4c3b2a1(字节序翻转) |
| 4 | 2 字节 | Version Major | 主版本号,通常为 2 |
| 6 | 2 字节 | Version Minor | 次版本号,通常为 4 |
| 8 | 4 字节 | Timezone Offset | 时区偏移量,多数工具写入 0 |
| 12 | 4 字节 | Sigfigs | 时间戳精度,一般填 0 表示全精度 |
| 16 | 4 字节 | SnapLen | 最大抓取长度,超长报文会被截断 |
| 20 | 4 字节 | Link Layer Type | 数据链路类型,1=Ethernet,105=IEEE 802.11 |
踩坑提醒:我曾经排查过一个 pcap 文件,Wireshark 死活打不开。用 hexdump 一看,魔数居然是 0x4d3c2b1a。这是字节序的问题。pcap 格式用 Little-Endian 写入,但有些工具用 Big-Endian 存。后来我写解析器时,会先读前 4 字节判断字节序,再决定怎么解析后续字段。
4.3 数据包包头:每一帧的时间戳和长度
每个包之前,都有 16 字节的包头。你猜对了,我第一个看的字段是时间戳。协议分析仪回放时,时间戳决定了「什么时候发这个包」。如果时间戳乱了,回放就是车祸现场。
| 偏移量 | 大小 | 字段名 | 说明 |
|---|---|---|---|
| 0 | 4 字节 | Timestamp (seconds) | 自 1970-01-01 00:00:00 UTC 起的秒数 |
| 4 | 4 字节 | Timestamp (microseconds) | 微秒部分,范围 0-999999 |
| 8 | 4 字节 | Incl Len | 文件内保存的数据长度(含尾部填充) |
| 12 | 4 字节 | Orig Len | 报文的原始长度(未截断时的真实长度) |
这里有个细节:Incl Len 可能比 Orig Len 小。 如果你的 SnapLen 设置得小,工具会把超长报文砍掉。我在做回放性能测试时,遇到过一个诡异现象:回放出来的报文总是少几个字节。一查,原来是抓包时 SnapLen 设成 128,而真实报文有 1500 字节。数据包被截了,回放工具却以为收到了完整的包。
4.4 用 hexdump 真刀真枪看一把
光说不练假把式。我们找个真实的 pcap 文件,用 xxd 或 hexdump 烤一下数据。下面是我从一个握手包里截出来的前 48 字节:
00000000 d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 04 00 01 00 00 00 |........ |
00000018 5a 6f 4d 00 00 00 00 00 4e 00 00 00 4e 00 00 00 |ZoM.....N...N...|
00000028 aa bb cc dd ee ff 00 11 22 33 44 55 08 00 45 00 |........"3DU..E.|
00000038 00 40 00 00 40 00 40 06 00 00 c0 a8 01 01 c0 a8 |.@..@.@.........|
# 解析方法:
# 全局头(0x00 - 0x17):
# d4 c3 b2 a1 → 魔数 Little-Endian
# 02 00 → 主版本号 2
# 04 00 → 次版本号 4
# 00 00 00 00 → 时区偏移 0
# 00 00 00 00 → 时间戳精度 0
# 00 00 04 00 → SnapLen = 1024 字节
# 01 00 00 00 → Link Type = 1(以太网)
# 第一个数据包包头(0x18 - 0x27):
# 5a 6f 4d 00 → 秒 = 0x004d6f5a = 5079514
# 00 00 00 00 → 微秒 = 0
# 4e 00 00 00 → Incl Len = 78 字节
# 4e 00 00 00 → Orig Len = 78 字节
# 数据部分从 0x28 开始:以太网帧 + IP + TCP
你看,实际操作就是这么简单。用 hexdump 一条一条数过去,全局头的每个字段都能对上。我曾经用这个方法排查过两个 pcap 文件:一个回放正常,另一个失败。对比字节发现,失败的那个 Link Type 填的是 0(无效类型)。Wireshark 容错能力强,帮你自动修复了;但我的回放工具没做这个容错,直接崩溃。
我的排查经验:遇到 pcap 文件解析异常,别急着写代码。先用 xxd -l 48 input.pcap 看前 48 字节。如果全局头里 SnapLen 填成 0,或者 Link Type 超出已知范围(0-255),那多半是文件损坏或者工具写入 bug。
4.5 常见数据链路类型一览
Link Type 字段太重要了。它决定了 pcap 文件里装的是有线网帧还是无线网帧。回放工具如果认错了类型,发出去的包格式就不对。下面是我经常遇到的几种:
| Link Type 值 | 描述 | 常见场景 |
|---|---|---|
| 0 | 无效 / 未指定 | 文件损坏,通常不可用 |
| 1 | Ethernet (10/100/1000 Mb/s) | 最常用,笔记本电脑抓包首选 |
| 105 | IEEE 802.11 | Wi-Fi 抓包,带 Radio Tap 头 |
| 113 | Linux SLL (Cooked) | 通过 lo 接口抓包时常见,无 MAC 地址 |
| 101 | Raw IP | 直接从 IP 层抓取,跳过了链路层 |
说实话,我第一次拿到 113 类型时还奇怪:为什么 MAC 地址全是 0?后来才知道,本地回环接口就没链路层,Linux 用 SLL (Linux Cooked) 模拟了一个。所以回放时链路层处理跟 Ethernet 完全不同。
4.6 数据部分——协议栈的裸奔
数据部分就是原始的二层帧。拿 Ethernet 举例,从 pcap 的数据部分偏移 0 开始,就是标准的以太网帧:
- 0-5:目的 MAC(6 字节)
- 6-11:源 MAC(6 字节)
- 12-13:EtherType(2 字节),0x0800 = IPv4,0x86DD = IPv6
- 14 往后:IP 载荷
为什么我强调这个?因为协议分析仪回放时,如果你直接裸发二层帧,它连目标 MAC 都不检查冲突。我遇到过一回,回放工具把帧发到交换机上,交换机一看源 MAC 跟端口绑定的 MAC 不一样,直接扔进黑洞。嗯,后来我学乖了,回放前先校验 MAC 一致性。
核心记忆点:pcap 文件 = 全局头 + 包记录 × N。每个包记录 = 16 字节包头(含时间戳和长度)+ 原始帧数据。用 hexdump 验证时,全局头看 Link Type,包头看 Incl Len 和 Orig Len,数据部分看 MAC 头。
最后说一句。如果你自己写 pcap 解析器,一定要处理好字节序。我曾经在 ARM 和 x86 之间传递 pcap 文件,ARM 默认 Big-Endian 但 pcap 是 Little-Endian,结果时间戳全读反了,回放出来的报文时间顺序乱七八糟。写一个字节序探测函数,这事我建议每位做协议分析的工程师都备着。