关键词:PCAP、PCAPNG、NetFlow、会话重组、TLS 元数据、HTTP 对象导出、JA3、snaplen、内容缺口
难度:进阶
前置知识:内存取证总览、TCP/IP 协议栈、五层模型、文件哈希
技术边界声明:本文只讨论对已获授权检材中网络流量证据的离线分析。全文不涉及中间人劫持、证书透明性投毒、TLS 会话密钥提取、无线握手破解等主动干预手段。需要解密明文时,唯一的合规路径是依法取得会话密钥或由用户提供解密材料,不得以技术手段绕过加密。
一、概述
网络流量是唯一能证明数据真的离开过主机的证据类型。
主机侧工件——浏览器历史、下载目录、Shell 历史、数据库操作日志——都只能证明"本机发生过某个动作",或者干脆只能证明"某个文件被访问过"。
它们回答不了那个最关键的问题:这份数据到底有没有走网卡?走到哪里去了?
流量证据的价值在于它是传输过程的直接记录。
但它的局限同样独特,而且是所有证据类型里最容易被夸大的:
- 它是采样的、不是完整的。抓包点的位置、镜像口的配置、设备的
snaplen 设置、运营商的流量留存策略,任何一环都会造成内容缺口。
- 它是密文的、不是明文的。现代互联网流量绝大部分跑在 TLS 上,你能看到的只有连接元数据,不是内容。
- 它是归属模糊的。抓到一个公网 IP,这个 IP 可能是攻击者、可能是 CDN 节点、可能是 NAT 出口、可能是运营商透明代理。
所以本篇不打算教你怎么用 Wireshark 点开一个包。
要解决的是另一个问题:一份抓包能证明什么,不能证明什么。
一份 PCAP 能可靠支撑的结论层级大致是这样的:
| 结论强度 |
需要的证据 |
表述示例 |
| 弱 |
会话存在 |
「该主机在 03-14 09:12:07 向 203.0.113.44:443 发起 TCP 连接」 |
| 中 |
传输了某类数据 |
「该连接上传了 4.2 MB 数据,方向为客户端→服务端」 |
| 强 |
内容可识别 |
「上传内容为 ZIP,SHA256 为 …,与检材 DigiForensics-leak.zip 一致」 |
| 极强 |
内容可归属 |
「上传内容与本案批次的 DigiForensics-batch-07.xlsx 逐字节一致」 |
很多报告卡在第二层就开始写第四层的措辞,这是本领域最高频的表述事故。
第四章会专门处理这件事。
还有一件事需要先分清:流量证据不止 PCAP 一种形态。
交换机镜像口导出的全包、路由器上的 NetFlow/sFlow 记录、主机本机抓的 loopback 抓包、云厂商的流量镜像日志,是四种性质完全不同的东西。
证据能力差别极大。混为一谈比技术错误更严重。
二、核心原理
2.1 流量证据的三种形态
形态一:全包抓包(PCAP / PCAPNG)
保存了每个报文的头部,以及按 snaplen 决定是否保存载荷部分。分析能力最强,是本文的主体。典型来源是交换机 SPAN 口、网桥 TAP、主机 tcpdump、IDS 旁路。
形态二:流记录(NetFlow v5/v9、sFlow、IPFIX)
不保存报文,只保存「一段时间内某对端点之间的汇总统计」:字节数、包数、起止时间、接口、协议、TCP 标志位、下一跳。体积比全包小两到三个数量级,能保留数天的历史,但没有载荷。
形态三:应用层日志(代理日志、CDN 日志、WAF 日志、VPN 网关日志)
由某个具体服务生成,内容是该服务自己看到的请求。最贴近业务语义,但只覆盖经过该服务的那部分流量,且字段由服务方定义、可被篡改。
三者的证据地位完全不同:
证据能力
↑
│ ██ 全包抓包 —— 可证明内容、可做重组、可算哈希
│ ████████ 应用层日志 —— 业务语义清晰,但受服务方视角限制
│ ██████████████ 流记录 —— 可证连接与流量规模,无内容
└──────────────────────────────────────→
覆盖面窄 ←──────────────────────→ 覆盖面宽
一个常见误解是"流记录信息少所以没用"。
实际上在长期行为分析上,流记录比全包更有价值——它能告诉你三个月前的连接情况,而全包根本不会被保留三个月。
选错形态会导致证明力的方向性错误:拿流记录去做内容结论,或者拿一段 20 分钟的全包去否认长期行为。
2.2 采集点决定证据边界
同一个终端的流量,在不同采集点看到的画面完全不同。
这一点必须在分析前想清楚,否则结论会越界。
| 采集点位置 |
能看到 |
看不到 |
| 终端本机(网卡抓包) |
该终端全部连接,含未加密部分 |
被硬件卸载/内核旁路绕过的流量 |
| 接入交换机 SPAN |
该端口进出全部流量 |
交换机自身管理流量、跨 VLAN 路由后的重写结果 |
| 汇聚/出口路由器 |
该网段跨出互联网的流量 |
内部东西向流量、镜像口丢包的部分 |
| 运营商侧 |
跨运营商边界的流量,带五元组 |
明文内容(有合规限制,多已加密) |
| 云 VPC 流日志 |
VPC 内东西向流量的元数据 |
载荷内容、VPC 外流量 |
两条推论:
第一,SPAN 口是「镜像」不是「复制」。 端口带宽超过镜像口带宽时会静默丢包,且交换机通常不报警。
更隐蔽的情况是单镜像口被配置成多个目标端口时,超过源端口速率的流量会随机丢弃。判断方法见 3.3 的丢包检查。
第二,NAT 会重写地址。 出口路由器上看到的源地址是公网出口地址,不是内网主机的地址。
所以「抓到某主机访问 1.2.3.4」这句话在出口侧无法成立——你只能证明「某内网地址经该 NAT 设备访问了 1.2.3.4」。
要从地址池反推具体主机,需要配合 DHCP 日志、ARP 表、连接日志、NetFlow 的接口信息或防火墙会话表。
2.3 PCAP 容器结构
理解容器结构不是为了手工解析,而是为了读懂工具报出的警告。
经典 PCAP 文件由一段 24 字节的文件头加若干条记录组成:
文件头(24 字节,全局唯一)
├── magic 4B 0xa1b2c3d4 = 微秒;0xa1b2c3d4 大端变体 = 纳秒
├── version_major 2B
├── version_minor 2B
├── thiszone 4B 本地时区偏移(几乎所有现代抓包工具写 0)
├── sigfigs 4B 时间戳精度
├── snaplen 4B 每个报文截取的最大长度
└── linktype 4B 链路层类型(1 = Ethernet)
报文记录(每条 16 字节头 + 变长数据)
├── ts_sec 4B 秒
├── ts_usec 4B 微秒(或纳秒)
├── incl_len 4B 实际保存的字节数
└── orig_len 4B 线上原始长度
三个字段的含义差别是陷阱的根源:
incl_len(实际保存)可能小于 orig_len(线上原始)。差值就是被截断的部分。Wireshark 会在包列表里显示 [TCP segment of a reassembled TCP message] 或 Packet size limited during capture,在专家信息里标为 Truncated。看到 orig_len > incl_len 就意味着载荷不全,任何内容层面的结论都不成立。
snaplen 是全局的,写在文件头。所有报文按同一上限截。常见配置是 1500(只够以太网头 + IP 头 + 少部分 TCP 载荷)或 262144(足够绝大多数场景)。
- 这个值无法事后修复。 载荷没抓就是没抓,只能靠别处的证据补,不能靠工具「还原」。
2.4 PCAPNG:多接口与元数据
PCAPNG 是 PCAP 的后继格式(RFC 增强版),最重要的差别是一个文件里可以容纳多个接口。
PCAPNG 由若干种**块(Block)**构成,每块有类型、长度、内容和填充:
| 块类型 |
名称 |
作用 |
0x0A0D0D0A |
SHB |
Section Header,一个文件可有多个 section |
0x00000001 |
IDB |
Interface Description Block,定义一个接口及其时间基准 |
0x00000006 |
EPB |
Enhanced Packet Block,含报文与可插注释 |
0x00000004 |
SPB |
Simple Packet Block,无时间戳 |
0x00000005 |
ISB |
Interface Statistics Block,含丢包/错误计数 |
0x00000008 |
NRB |
Name Resolution Block,存 IP→名字的解析结果 |
两个实务要点:
第一,if_name 与 if_index 决定归属。
合并多个来源的抓包时,务必保留接口标识。否则分析时无法区分「内网 A 到 B 的流量」和「同地址的互联网流量」。
命令行导出接口名用 -e frame.interface_name,用 -Y 'frame.interface_name == "eth0"' 过滤。
第二,epb_dropcount 是最有价值却最少被看的字段。
ISB 与 EPB 里都有丢包计数。抓包软件崩溃、被 OOM kill、或者用了轮转抓包而没轮转完,都会留下这个数字。
它一旦非零,整份文件的完整性就有了一个可量化的折扣。
读出接口清单与丢包统计
tshark -r file.pcapng -T fields -e frame.interface_name -e frame.interface_id
-e frame.interface_drops_all 2>/dev/null | sort -u
2.5 三种分析粒度:包、流、重组体
初学者最大的困惑是「Wireshark 到底给我看的是什么」。
实际上它提供了三个粒度,混用会导致结论越界。
包(packet / frame) —— 单个 IP 报文的头部视图。回答「这个包从哪来、到哪去、带什么标志位」。
流(flow / conversation) —— 按五元组(源 IP、源端口、目的 IP、目的端口、协议)聚合的报文集合。回答「这两端之间总共传了多少、何时开始结束、谁先发起」。Conversations 视图和 conv,tcp 报表都是这个粒度。
重组体(reassembled stream / object) —— 把跨包的数据按应用层协议重新拼成的连续内容。HTTP 响应体、FTP 文件、SMB 事务、邮件正文的提取都在这一层。
关键区不要在于重组体的完整性不是自动保证的。
Follow TCP Stream 对 HTTP 有效,对 TLS 无效,对自定义二进制协议可能错位——因为 TCP 是字节流,工具靠启发式规则猜边界在哪。
猜错就会得到「前半段正常、后半段乱码」的结果。
因此正确的工作方式是先看流,再看重组,最后才下内容结论:
会话表(谁和谁、传了多少)
↓ 收敛到可疑的少数流
协议分布(这条流里是什么协议)
↓
重组尝试 + 完整性检查
↓
哈希比对
↓
内容结论
跳过前两步直接 Follow Stream 是在几百条流里盲猜,通常只能命中最显眼的那一条。
2.6 应用层协议的可读性分层
不是所有协议都能读出内容。分析前先判断目标协议属于哪一层。
| 层级 |
协议示例 |
能拿到什么 |
重组可行性 |
| 明文文本 |
HTTP、SMTP、POP3、FTP 控制通道、Telnet、IRC |
完整内容与语义 |
高 |
| 结构化明文 |
DNS、SIP、DHCP、NetBIOS、SMB1 |
结构化字段 |
中 |
| TLS 包裹明文 |
HTTPS、IMAPS、POP3S、SMTPS、FTPS |
握手元数据 + 载荷密文 |
载荷不可重组 |
| 应用层自加密 |
某些 IM 的私有协议、SSL VPN 隧道 |
几乎只有连接特征 |
低 |
| 传输层加密 |
SSH、IPsec、WireGuard、GRE 封装 |
隧道端点信息 |
载荷不可重组 |
| 静态加密文件传输 |
SFTP over HTTP、加密压缩包走 HTTP |
密文块,可作字节证据 |
字节可重组,内容不可读 |
最后一行的实践意义常被低估:即使内容是密文,重组出的字节依然是有价值的证据。
它有确定的长度、确定的时间、确定的 SHA256。
如果委托方提供了对应的解密密钥或解密后文件,密文字节的哈希比对可以精确证明「这段传输的内容就是那份文件」。
这是「无法解密」场景下最实用的路径。
2.7 TLS 加密面与可见元数据
TLS 的目的是保护内容,但握手过程是明文的,这就是分析工作的入口。理解哪些字段可见、哪些已被加密,才能判断一个结论能不能写。
TCP 三次握手
↓
ClientHello ← 明文
├─ legacy_version / supported_versions 协议版本
├─ cipher_suites 密码套件列表(JA3 输入)
├─ extensions (JA3 输入)
└─ sni (server_name) 明文主机名(客户端发)
├─ application_layer_protocol_negotiation (ALPN) h2 / http/1.1
├─ supported_groups / signature_algorithms
└─ key_share 密钥交换组
↓
ServerHello ← 明文
├─ selected_version / cipher_suite
├─ extensions
└─ certificate 明文证书(服务器发)
├─ subject / SAN 域名列表
├─ serialNumber / 有效期
└─ 公钥算法与长度
↓
[握手其余部分开始加密]
Application Data ← 密文,此后不可读
SNI 是最有价值也最容易被过度解读的字段。
三个必须知道的限制:
- SNI 只在 ClientHello 里明文,服务器名不加密。 但当客户端使用 ECH(Encrypted Client Hello) 或 ESNI 时,SNI 被加密,只留下公开的加密扩展,此时抓包只能看到「加密的客户端问候」。
- QUIC / HTTP3 的 SNI 通常在加密握手包(Initial 包)内,且 Initial 包本身用公开可解的初始密钥保护,所以主流工具仍能解析——但版本演进中这个机制有过变化,工具支持不一致。
- SNI 是「客户端声称要访问的域名」,不是「实际回应的服务器」。 域名可以指向 CDN 节点、可以被反向代理改写,也可能因为 Hosts 文件或 DNS 劫持而指向任意地址。SNI 与目的 IP 的不一致本身是有价值的发现,但不能简单等同于「造假」。
除了 SNI,还有两组值得提取的元数据:
证书信息 —— 服务器证书的 SAN 字段常常暴露内部域名或泛域名(*.corp.example.com),是内网测绘的线索。
证书序列号与有效期也能用来关联不同时间抓到的同一台服务器。
TLS 指纹 —— JA3 是对 ClientHello 的 CipherSuites、Extensions、EllipticCurves、ECPointFormats 四项按序拼接后取 MD5 得到的 32 位十六进制串;JA4 改进了排序与截断问题,格式更稳定。
用途是在无法解密内容时,用指纹聚类判断两组连接是否来自同一客户端软件。
同一指纹 + 相同 JA3 + 相同 TLS 库默认配置 → 极可能同一客户端软件
不同指纹但同源 IP + 同时段 → 需进一步区分是多用户还是多进程
要注意 JA3 相同不等于同一个人。
大量程序使用相同的库(Python requests、Go 标准库、Java 默认栈)会产生完全相同的 JA3。
它的作用是排除(不同指纹必然不同软件)而不是认定(相同指纹不能证明同一人)。
2.8 HTTP 对象导出的完整性问题
Wireshark 的 File > Export Objects > HTTP 是最常用的操作,但它的输出默认不完整。
这一点在实务中造成过大量误判。
不完整的原因是 HTTP 对象可能来自多个来源:
- 单个完整响应(有
Content-Length,内容在一个响应内)→ 导出成功。
- 分块传输(
Transfer-Encoding: chunked) → 现代 Wireshark 能正确解码,但老版本会失败。
- 被截断的响应(
infolen > caplen)→ 导出的是前一段,后面被 snaplen 吃掉。
- 分段传输(Range 请求、大文件断点续传)→ 每次只到一段,导出得到的是碎片。
- 多个对象共享一个流(HTTP/1.1 流水线)→ 解析错位,取到的可能是两个对象拼接。
因此导出后必须做完整性检查,导出成功不等于内容完整。 检查方法:
1. 看响应体是否以 Wireshark 标记的截断提示结尾(文本类)
file /work/http-objects/200-1.bin
tail -c 200 /work/http-objects/200-1.bin | strings | tail -5
2. 与头部声明的长度对比
tshark -r DigiForensics-net.pcap -Y 'http.response'
-T fields -e frame.number -e http.content_length -e http.content_length_header
3. 对可识别类型直接验证魔数
file /work/http-objects/*.bin | head -20
file 命令的输出是最有效的第一道筛子。
如果导出物被描述为 data 而你期望它是 ZIP 或 PE,那基本可以断定重组失败或只拿到了片段。
还有一个隐蔽的坑:导出的对象没有原始文件名。
工具用的是 序号-状态码.bin 这样的命名。文件在传输时的名称只在 HTTP 请求头或响应头里,导出时需要单独提取并建立映射表:
tshark -r DigiForensics-net.pcap -Y 'http.request or http.response' \
-T fields -e frame.number -e http.host -e http.request.uri \
-e http.content_disposition -e http.request.method \
-e http.response_for.uri 2>/dev/null | head -30
http.content_disposition 里的 filename= 才是服务端声称的文件名,这个字段经常被忽略,而它往往是唯一的文件名来源。
2.9 NetFlow 与全包抓包的分工
流记录的价值在长期行为分析。做一次选型时按这三步判断:
问题是「这 8 小时里发生了什么」?
→ 全包抓包,能看内容
问题是「这三个月里这台主机联系过哪些 IP」?
→ 流记录,PCAP 根本没有三个月
问题是「这台主机一共外传了多少数据」?
→ 流记录(有 src_bytes/dst_bytes),可累加
NetFlow 常见版本的关键差异:
| 版本 |
关键字段 |
实务意义 |
| v5 |
固定 24 字节记录,7 元组 + 字节/包计数 |
覆盖广但无聚合维度 |
| v9 |
可变长,支持多字段与聚合模板 |
能看到源/目的 AS、接口、掩码 |
| IPFIX |
v9 的标准化后继,支持企业自定义字段 |
常见运营商设备默认 |
分析时要特别注意方向:src_bytes / dst_bytes 是以采集设备视角计的。
经过 NAT 的会话里,这两列与终端的实际上传/下载方向可能是反的。
判断方向要看源地址是不是内网段,而不是看 src_bytes 大小就认定是上传。
导出流记录后做汇总:
tshark -r flow.dat
-T fields -e src -e srcport -e dst -e dstport -e prot
-e src_bytes -e dst_bytes -e first -e last 2>/dev/null
| sort -u > /work/flow-summary.tsv
按会话终点聚合,找出外联最多的对端
awk -F'\ ' '{print $4}' /work/flow-summary.tsv | sort | uniq -c | sort -rn | head -20
2.10 时间基准:抓包里最容易出错的一项
抓包的时间戳是一串 64 位整数,解释它需要三个信息,缺一个就会错。
第一,时区。 抓包设备不记录时区,只记录 UTC 时刻。thiszone 字段几乎所有现代工具都写 0。
这意味着同一个时间戳,在不同分析师手里会被解释成不同时区的不同时刻,差值可达一天。
这在跨时区协作的案件里是真实事故来源。
第二,时钟偏差。 抓包主机的系统时间可能不准。
抓包开始前、结束后各与可信时间源对比一次,把偏差记进报告。
这一步的成本是 1 分钟,不做的代价是全部时间线结论都不可靠。
第三,NTP 跳变。 采集中途的时间同步会造成时间戳突然前跳或后跳。
表现为时间排序错乱或出现负的时间差。
固定时区解释,并把抓包时间归一到 UTC
tshark -r DigiForensics-net.pcap
-t ad -T fields -e frame.time_epoch -e frame.time -e ip.src -e ip.dst 2>/dev/null
| head -5
抓包主机与可信时间源比对
chronyc tracking 2>/dev/null || ntpq -p 2>/dev/null
timedatectl status 2>/dev/null | head -8
报告里的时间表述必须带时区后缀或明确注明,裸时间戳是跨机构复核时最容易产生分歧的地方。
相关换算方法见时间戳换算。
三、操作步骤
以下流程以一份 Linux 抓包主机为例,检材命名为 DigiForensics-net.pcap。所有路径、IP、域名均为虚构示例。
3.1 第 1 步:登记来源与固定哈希
在碰任何工具之前先固定检材。流量文件的来源信息往往比内容更稀缺,一旦脱离原始来源就再也无法补回。
mkdir -p /work/net/{objects,reassembled,logs}
1. 登记来源(手写或脚本生成,两种都要)
cat > /work/net/case-sources.md <<'EOF'
检材来源登记
| 序 |
文件名 |
采集点 |
采集设备 |
起止(本地时区) |
时区 |
提供方 |
备注 |
| 1 |
DigiForensics-net.pcap |
出口交换机 Gi0/24 SPAN |
备查见来源单 |
待补 |
待补 |
委托方网络组 |
时钟偏差待测 |
| EOF |
|
|
|
|
|
|
|
2. 固定哈希(用相对路径保存,便于复现)
cd /work/net
sha256sum DigiForensics-net.pcap | tee logs/hash-original.txt
md5sum DigiForensics-net.pcap >> logs/hash-original.txt
ls -l --time-style=full-iso DigiForensics-net.pcap
file DigiForensics-net.pcap
3. 立即只读化工作副本(原件不参与任何分析)
chmod 444 DigiForensics-net.pcap
cp --reflink=auto DigiForensics-net.pcap /work/net/DigiForensics-net-work.pcap
来源登记里的时区一栏不要留空留到后面补。现场问清楚,比事后猜测便宜得多。哈希与保管链的整体要求见证据保管链与委托证明。
3.2 第 2 步:格式识别与规范化
识别容器格式与基本信息
file DigiForensics-net-work.pcap
capinfos -a -e -c -u -y -I DigiForensics-net-work.pcap 2>/dev/null
|| tshark -r DigiForensics-net-work.pcap -q -z io,phs 2>/dev/null | head -20
统一为 pcapng(保留多接口与注释,比降级成 pcap 更安全)
editcap -F pcapng DigiForensics-net-work.pcap /work/net/DigiForensics-net-normalized.pcapng
capinfos -c -d -u /work/net/DigiForensics-net-normalized.pcapng
修正明确的格式性问题:删除两端多余 pad,修正时间戳乱序
editcap -B /work/net/DigiForensics-net-normalized.pcapng
/work/net/DigiForensics-net-fixed.pcapng
降级到 pcap 会丢失接口标识。
如果源文件是 pcapng 且有多个采集接口,直接用 pcapng 工作,不要图省事转换。
3.3 第 3 步:规模摸底与丢包检查
这一步的输出决定后面所有结论的可信度。
必须先做完。
1. 总体规模
capinfos -c -a -e -d -M -s
/work/net/DigiForensics-net-fixed.pcapng
2. 接口清单与丢包计数(pcapng 才有意义)
tshark -r /work/net/DigiForensics-net-fixed.pcapng
-T fields -e frame.interface_name -e frame.interface_id -e frame.interface_drops_all 2>/dev/null
| sort -u | tee logs/interfaces.txt
3. 抓包截断统计:incl_len < orig_len 的报文数
tshark -r /work/net/DigiForensics-net-fixed.pcapng
-Y 'frame.cap_len < frame.len'
-T fields -e frame.number -e frame.len -e frame.cap_len 2>/dev/null
| tee logs/truncated.txt | wc -l
4. 截断报文里,最大帧长分布(判断 snaplen 实际取值)
awk -F'\ ' '{print $2}' logs/truncated.txt | sort -n | uniq -c | tail -5
5. 专家信息里的丢包与重组失败警告
tshark -r /work/net/DigiForensics-net-fixed.pcapng
-q -z expert 2>/dev/null | tee logs/expert.txt | head -40
6. 时间戳单调性检查
tshark -r /work/net/DigiForensics-net-fixed.pcapng
-T fields -e frame.number -e frame.time_epoch 2>/dev/null
| awk -F'\ ' 'NR>1 && $2 <