上周在靶场项目收尾阶段,我在一台 Windows Server 的 SMB 共享里发现了一份可疑的incident.pcap。文件不大,导进 Wireshark 之后却是一整段模拟攻击会话的抓包:攻击者通过 SMB2 协议连上共享,期间上传了一个看起来不对头的载荷文件,随后又读走了其它几个文件。整个过程不算复杂,但很多人拿到 pcap 的第一反应都是直接翻包,结果被一堆 SMB2 会话刷屏后立刻懵掉。
这篇记录会把完整思路写下来:怎么从 SMB 共享把 pcap 取回来、怎么用 Wireshark 的统计和过滤功能定位攻击者、怎么把藏在会话里的传输载荷完整提取出来,以及遇到文件头损坏时怎么修。对做流量取证、应急响应或者刚接触 Wireshark 的人应该都有用。整个实验都在授权靶场环境里进行,所有 IP 都是模拟 IP。
1. 靶场里的 SMB 线索:先把 pcap 从共享目录取回来
1.1 环境拓扑与文件来源
先把环境说清楚。靶场里三台设备:攻击者是一台 Kali,IP 是 192.168.24.66;受害/共享服务器是一台 Windows Server,IP 是 192.168.24.11,开放了一个evidence共享目录;分析机是我用的 Ubuntu 工作站,192.168.24.100,装了 Wireshark 和 tshark。整个攻防模拟都在隔离网段里进行,抓包目标是搞清楚攻击者在共享目录里做了什么、传了什么东西。
| 角色 | IP | 系统 | 本次作用 |
|---|---|---|---|
| 攻击机 | 192.168.24.66 | Kali | 发起 SMB2 连接、上传/下载文件 |
| 受害/共享服务器 | 192.168.24.11 | Windows Server | 开放 evidence 共享目录,存放 pcap 与可疑载荷 |
| 分析机 | 192.168.24.100 | Ubuntu | 本次分析操作所在主机,安装 Wireshark/tshark |
这个拓扑本身也对应一种常见现象:SMB 是最常见的 Windows 文件共享协议,攻击者拿到口令后经常通过 SMB 共享读取敏感文件或投放工具。所以当 SMB 流量异常偏多时,一定要把它当重点会话来看。
1.2 通过 smbclient 或 cifs 挂载拉取 pcap
拿到共享账号后,我首先用smbclient列目录,确认文件确实存在:
smbclient -L //192.168.24.11 -U auditor smbclient //192.168.24.11/evidence -U auditor smb: \> ls smb: \> get incident.pcap smb: \> exit如果不想走交互式,也可以直接把共享挂到本地目录,适合 pcap 比较大、想直接喂给 Wireshark 的情况:
sudo apt install cifs-utils sudo mkdir -p /mnt/evidence sudo mount -t cifs //192.168.24.11/evidence /mnt/evidence \ -o username=auditor,password=你的密码,vers=3.0 cp /mnt/evidence/incident.pcap ~/cases/01/ sudo umount /mnt/evidence这里有个容易踩的坑:现在很多 Windows Server 默认关闭 SMB1,如果你挂载时没指定vers=3.0或vers=3.1.1,可能会报协议协商失败。不是账号或者密码的问题,先换 SMB 版本参数再试。
1.3 别急着双击,先做完整性检查
文件拿到手,第一件事不是立刻打开,而是确认它是一个完整可解析的 pcap。我习惯先跑三条命令:
file incident.pcap md5sum incident.pcap capinfos incident.pcapcapinfos的输出里,比较关键的是 File type、Data link type、Snapshot length。如果 Snapshot length 偏小,比如只有 520,说明当初抓包的人设置了很小的 snaplen,后续分析会遇到“每个包只捕获了前 520 字节”的情况。这个问题后面会专门展开。
用xxd incident.pcap | head看一眼文件头也是个好习惯。pcap 文件是二进制格式,直接拿 notepad 打开当然是乱码,不用慌。前 4 字节如果是d4 c3 b2 a1,说明是小端序 pcap,常见于 x86 机器;如果是a1 b2 c3 d4,就是大端序。后面跟着主版本号和次版本号,正常是 2.4。这几项没问题,再放进 Wireshark 分析。
提示:原始文件最好先只读备份,后续所有提取和修复操作都基于副本进行,取证习惯要从一开始就建立。
2. 先用统计面板给流量排队,再锁定攻击者的 SMB2 会话
2.1 Protocol Hierarchy 和 Conversations 能快速找到主战场
很多人打开 Wireshark 后的习惯是直接看包列表,然后滚轮往下翻。如果流量只有几百个包还好,一旦有 SMB 文件传输,动辄上万包,这么翻效率太低。我会先看两个地方:Statistics > Protocol Hierarchy和Statistics > Conversations。
Protocol Hierarchy 会告诉你这个 pcap 里有哪些协议族。如果看到大量 TCP、SMB2,说明文件共享是主角;如果看到 TLS、HTTP、DNS 占大头,分析方向就完全不同。这个靶场 pcap 里,SMB2 流量占了八成以上,所以问题大概率出在 SMB 会话里。
Conversations 则按 IP 对把流量聚合。打开后切到 IPv4 或 TCP 标签页,按 Bytes 或者 Packets 排序,异常会话会很快浮出来。我的 pcap 里是这个分布:
| 源 IP | 目的 IP | 协议 | 包数 | 字节数 |
|---|---|---|---|---|
| 192.168.24.66 | 192.168.24.11 | SMB2/TCP | 10842 | 18.9 MB |
| 192.168.24.12 | 192.168.24.11 | TCP | 214 | 91 KB |
66 对 11 的流量明显异常,先把分析范围锁定在ip.addr == 192.168.24.66 && ip.addr == 192.168.24.11。
2.2 时间轴和认证信息:攻击者是谁,什么时候活动
接着设置时间显示格式:View > Time Display Format > Seconds Since Previous Captured Packet。也可以在包列表里加一个frame.time显示列。这样当你锁定一个可疑操作时,能清楚看到前后间隔了多少秒,方便还原攻击步骤。
再看认证信息。SMB 会话里通常有 NTLMSSP 认证包,Wireshark 解析出来后,用显示过滤器ntlmssp.auth.username != ""就能看到通过 SMB 登录的账号名。比如我这次看到的是evidence\admin。把这个字段加到列里,后面哪个包是哪个用户发的,一眼就能区分。
这一步的目标是把“攻击者”落到具体的 IP、账号和时间,而不是停留在“这个包很可疑”的模糊感觉上。
2.3 SMB2 读写请求:攻击者在共享里做了什么
SMB2 的命令码可以通过显示过滤器直接筛,常用的几个如下:
| 显示过滤器 | 含义 |
|---|---|
smb2.cmd == 4 | SMB2 Read Request,读共享文件 |
smb2.cmd == 5 | SMB2 Write Request,写共享文件 |
smb2.cmd == 1 | SMB2 Create Request,创建/打开文件 |
smb2.cmd == 10 | SMB2 Query Directory Request,列出目录 |
想只看某个文件的读写,在过滤栏里追加smb2.filename contains "agent.bin"。注意 SMB2 的 filename 字段是完整 UNC 路径,反斜杠在显示过滤器里比较烦,直接用contains比==省事。为了后面提取,我一上来就用显示过滤器锁定攻击者的写请求:
ip.addr == 192.168.24.66 && ip.addr == 192.168.24.11 && smb2 && smb2.cmd == 5 && smb2.filename contains "agent.bin"用这组过滤器,我发现攻击者先创建了agent.bin,随后跟着一串 Write Request,文件内容被分成了很多块传上去。这就是我们要提取的传输载荷。
2.4 把行为链串起来
把关键事件按时间列出来,思路会非常清晰:
| 时间 | 源 IP | 目的 IP | 行为 |
|---|---|---|---|
| 10:12:01 | 192.168.24.66 | 192.168.24.11 | SMB2 Negotiate + Session Setup |
| 10:12:03 | 192.168.24.66 | 192.168.24.11 | SMB2 Create \evidence\incident.pcap |
| 10:12:06 | 192.168.24.66 | 192.168.24.11 | SMB2 Read 读取 pcap 的一部分 |
| 10:12:41 | 192.168.24.66 | 192.168.24.11 | SMB2 Create \evidence\agent.bin |
| 10:12:42 | 192.168.24.66 | 192.168.24.11 | 多次 SMB2 Write 上传 agent.bin |
这说明攻击者既从共享里读过 pcap,也往共享里写过agent.bin,后者更像是需要重点分析的传输载荷。如果想把关键字段导出成 CSV 方便写报告,也可以用 tshark 直接转:
tshark -r incident.pcap \ -Y "ip.addr == 192.168.24.66 && ip.addr == 192.168.24.11 && smb2" \ -T fields -E header=y \ -e frame.number -e frame.time -e ip.src -e ip.dst \ -e smb2.cmd -e smb2.filename -e tcp.stream \ > smb2_activity.csv这就是很多教程里说的“pcap 文件转 txt”,本质不是用记事本硬开二进制文件,而是用 tshark 按字段解析成可读文本。
3. 从 SMB2 Write 请求里按偏移拼回原始载荷
3.1 为什么直接 Follow TCP Stream 不是最优解
看到文件传输,很多人会右键一个包,选择Follow > TCP Stream,然后把数据 Save as 成文件。这个思路在 HTTP 下载场景没问题,但放到 SMB2 里就麻烦了:TCP 流里除了文件内容,还混着 SMB2 协议头、NetBIOS Session 头,甚至还有 Negotiate/Create/Close 控制消息。另存下来的文件开头会多一坨乱七八糟的东西,file命令大概率认不出来。
Wireshark 的 Export Objects 功能在一定程度上能解决这个问题。如果你的版本在File > Export Objects菜单里能看到 SMB/SMB2,可以先试一下。但我实际遇到的情况是:同一个文件被拆分到多个 Write Request 里,而且网络上存在重传和乱序,直接导出经常得到坏文件。所以下面这个方法才是最稳妥的。
3.2 先用 tshark 筛出所有 Write Request
用 tshark 把攻击者发给共享服务器的所有写请求抠出来。命令比较长,但一次到位:
mkdir -p extracted tshark -r incident.pcap \ -Y "ip.addr == 192.168.24.66 && ip.addr == 192.168.24.11 && smb2.cmd == 5 && smb2.filename contains \"agent.bin\"" \ -T fields -E separator='|' -E quote=n \ -e frame.number -e smb2.offset -e smb2.data \ > extracted/writes.csv这里smb2.offset表示这次写入在文件里的偏移量,smb2.data是真正的文件字节。输出到 CSV 后,每行对应一帧。不同 Wireshark 版本的字段名偶尔会有差异,如果你的版本导出为空,先在 GUI 里展开一个 Write Request,看 Data 字段的 Filter Reference 是什么,把-e后面的字段名换掉。
如果攻击者不是上传而是下载文件,把过滤器里的smb2.cmd == 5换成smb2.cmd == 4,数据在 Read Response 的smb2.data里,原理一样。
3.3 Python 按文件偏移拼接,而不是按帧号硬拼
拿到 writes.csv 后,用 Python 按 offset 写入,而不是按帧号顺序追加。这是最容易踩坑的地方:SMB2 Write 请求可能乱序到达,如果按frame.number顺序硬拼,文件内容会错位;按 offset 写,相当于让原始文件自己“归位”。
#!/usr/bin/env python3 import binascii chunks = [] with open("extracted/writes.csv", "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split("|") if len(parts) < 3: continue offset = int(parts[1]) hex_part = parts[2] # tshark 输出的字节字段通常是 00:11:22...,去掉非 hex 字符再转 raw_hex = "".join(c for c in hex_part if c in "0123456789abcdefABCDEF") raw = binascii.unhexlify(raw_hex) chunks.append((offset, raw)) chunks.sort(key=lambda x: x[0]) # 根据 offset + len 计算文件结束位置 file_end = max(off + len(raw) for off, raw in chunks) buf = bytearray(file_end) for off, raw in chunks: buf[off:off + len(raw)] = raw with open("extracted/payload_from_smb.bin", "wb") as out: out.write(bytes(buf)) print(f"written {file_end} bytes")如果脚本跑完后,文件长度明显小于原始信息里显示的大小,说明抓包有丢包或某个 Write 请求没抓到,后面会进入修复流程。
3.4 静态验证:先让 file 和 sha256sum 说话
文件拼出来先别急着深入分析,跑一下file和sha256sum:
file extracted/payload_from_smb.bin sha256sum extracted/payload_from_smb.bin ls -l extracted/payload_from_smb.bin理论上,如果一切正常,这一步就能看到常见文件类型,比如 PE32 executable 或者 Zip archive data。如果输出是data,那说明载荷可能在传输过程中损坏,或者被人为改过文件头。
4. 修复两类损伤:抓包截断和文件头被篡改
4.1 为什么 Wireshark 只显示 520 字节
这是流量分析里一个非常常见的坑,也是很多新手最容易困惑的地方。抓包时,tcpdump 或 Wireshark 都可以限制每个包保存多少字节。如果用了tcpdump -s 520,那么即使网卡上收到的是一个 2090 字节的完整 IP 包,pcap 里也只保存前 520 字节,后面的全部丢弃。这个值叫 snaplen,在capinfos输出里会直接显示为 Snapshot length。
在 Wireshark 的包列表里,被截断的包会在 Info 列显示[Packet size limited during capture]。点开 Frame 部分也能看到Frame Length: 2090 bytes (520 bytes captured)。想统计到底哪些包被截断,用这个过滤器:
frame.cap_len < frame.len如果要提取的传输载荷刚好落在被截断的部分,Wireshark 当然只能显示 520 字节。剩下的数据不是没显示,而是 pcap 里压根就没有。这种文件基本没法修复,唯一办法是找到一份 snaplen 足够大的重抓结果。这也是为什么做取证抓包时直接tcpdump -s 0 -w 输出.pcap,别为了省磁盘空间把关键字节丢掉。
提示:snapshot length 太小,后面再厉害的 Wireshark 操作也救不回来。证据抓取阶段一定别省这个参数。
4.2 文件头被改:从已知文件特征反推
另一种更常见的损坏是文件头被篡改。比如我这次提取出来的payload_from_smb.bin,file命令的输出是data。用xxd看前 64 字节:
xxd extracted/payload_from_smb.bin | head -4正常的一个 Windows PE 可执行文件,开头应该是4d 5a,也就是 ASCII 的MZ。但这次看到的前两个字节是4d 58,也就是MX。文件头被改了,后面的 DOS 头和 PE 头很可能还是完好的。修复方法很简单:
printf '\x4d\x5a' | dd of=extracted/payload_from_smb.bin bs=1 seek=0 conv=notrunc file extracted/payload_from_smb.bin修完后file会把它识别成 PE32 executable。
另一类常见情况是载荷前面被硬塞了一段垃圾字节,比如攻击者为了让检测工具不识别,在 ZIP 文件前加了 256 字节噪声。这时binwalk比人眼更高效:
binwalk extracted/payload_from_smb.bin如果输出里有一行Zip archive data和对应偏移,直接用 dd 跳过前面那段再保存:
dd if=extracted/payload_from_smb.bin of=extracted/clean.zip bs=1 skip=<偏移量> unzip -t extracted/clean.zipskip的数值要填 binwalk 报告里的偏移,单位是字节。
| 常见文件签名 | 开头字节 | 说明 |
|---|---|---|
| MZ | 4D 5A | DOS/Windows PE |
| ZIP | 50 4B 03 04 | ZIP 压缩包 |
| ELF | 7F 45 4C 46 | Linux 可执行文件 |
| pcap | D4 C3 B2 A1 / A1 B2 C3 D4 | 小端/大端 pcap |
有了这张表,看到file输出data时,可以快速判断该往哪个方向修。
4.3 修复后要验证完整结构,不只是让 file 认出来
修复文件头之后,至少再做两件事。
第一,重新计算哈希,把修复前后两个文件的 sha256 都记下来。哈希对不上不等于修复失败,因为文件内容本来就变了,但取证记录要留清楚。
第二,根据文件类型做完整性测试。ZIP 就unzip -t,PE 可以用objdump -f看入口点是否合理。如果这些命令能正常解析,说明载荷结构基本完整,可以进入后续恶意代码分析阶段。
如果这时候还是报错,比如 ZIP 有 CRC 错误,那基本可以确定是 pcap 丢帧,或者原来的文件本身就是坏的,不是改几个文件头字节能救回来的。
注意:修复文件头之前先复制一份,不要在原始提取文件上动手。真实应急场景里,原始 pcap 和原始提取文件都属于证据材料。
5. 几个实战里值得固化成习惯的小操作
这轮靶场做完,我沉淀了几个已经形成本能的习惯,写在这里供参考。
5.1 抓包前先确认 snaplen
不管用 tcpdump 还是 Wireshark,我都会先确认不是-s 520这种限制。尤其是做应急响应取证,宁可 pcap 大一点,也不要为了省空间丢掉关键字节。这个坑一旦踩到,后续所有文件还原和协议分析都会带着残缺数据。
5.2 先统计后翻包,比什么都管用
Protocol Hierarchy 和 Conversations 这两个入口,能在 30 秒内告诉你流量主角是谁。再配合显示过滤器把无关流量排除,面对上万包也不会慌。很多人觉得 Wireshark 难,其实是上来就翻包的姿势不对。
5.3 把 tshark + Python 的提取流程存成脚本
这次手动敲的命令,我后来又整理成了一个简单的tshark_extract_smb.py,把-e字段、offset 拼接、哈希校验都做了封装。以后遇到类似靶场题目,只要改一下文件名和 IP 就能复用。流量取证这种活儿,重复劳动越少越好,早一点把流程变成工具,下次就能把精力留给真正需要判断的部分。