计网实验 - PCAP 侦听与分析

计网实验 - PCAP 侦听与分析

Coast23

实验要求

工具准备

流量分析工具

  • Wireshark:流量分析的瑞士军刀,功能强大但界面略显简陋。
  • EasyTshark:网上大佬对 Tshark 的二次开发,功能精简(很有限),界面美观(Web 做的能不美观吗),上手容易,但 BUG 有亿点多

二选一使用即可。

组件

Packet capture (and sending) library for Windows.

  • WinPcap:它的最后一次更新是在 2013.03.08… 应该被淘汰了。
  • Npcap:比 WinPcap 强大。

二选一安装即可。

其它

可选,一些可能有用的辅助工具。

  • Python-Scapy:数据包处理一把梭。可使用 pip install scapy 安装。
  • Filezilla:FTP Client & Server,需要自取。

实验过程

1. 抓包

先侦听流量并保存为 .pcapng 文件,后面再分析。

启动抓包软件,选择 Interface,软件会记录所有经过该网卡的数据包。然后停止并保存即可。

由于实验要求分析 FTP 和 HTTP 流量,所以需要人为地发起这 2 种协议的请求。

由于我的 2 个抓包软件都抓不到本地回环的流量,所以直接在 localhost 起服务器可能不太可行,我在实操中用了一些曲线救国的方法。

FTP

直接连学校交作业用的 FTP 服务器。我习惯用 Filezilla Client 来连接 FTP Server,不过直接用 Windows 的资源管理器应该也可以。

HTTP

可以在网上找找有没有 HTTP 服务器。我懒得找,所以自己搭了一个。

Python 自带 HTTP 服务器,终端使用命令 python -m http.server [port] 即可在指定端口开启 HTTP 服务,默认端口是 8000。

前面说了,我的抓包软件抓不到本地回环的流量,所以我是在虚拟机里起的服务器,然后监听虚拟网卡,捕获向虚拟机的 HTTP 服务器发起的请求。

HTTPS

HTTPs = HTTP + SSL / TLS,只要搞到证书,就能解密流量,解密之后就和 HTTP 一样了。我懒得搞这个,所以没做。

2. 数据包分析

观察数据格式

  • 帧格式 / IP 报文

在 Wireshark 里打开刚刚捕获的 .pcapng 文件,选择一个 TCP 包,查看它的帧信息。

各个字段的含义:

协议/封装单元OSI 层级说明
Frame物理层物理层的数据帧概况
Ethernet II数据链路层数据链路层以太网帧头部信息
Internet Protocol Version 4/6网络层互联网层 IP 包头部信息,属于网络层
Transmission Control Protocol传输层传输层的数据段头部信息,此处是 TCP
Hypertext Transfer Protocol应用层应用层的信息,如 HTTP 请求/响应

以太网的帧格式:

frame_format

前同步码、帧首定界符、CRC 校验位似乎并未在数据包里体现,从抓到的数据包头开始分别是 6 字节的目的地址,6 字节的源地址,2 字节的类型字段,以及 46-1500 字节的数据。

以下是一个示例:

目的地址是 40:fe:95:fe:80:01

dst_addr

源地址是 04:ec:d8:c9:52:ac

src_addr

类型是 0x86dd(IPv6 数据包):

type_field

Type 之后就是 IPv4 或 IPv6 的报文头部信息(位于帧的数据区段),课上没讲,这里亦不做展开。

对于实验要求验证的内容,帧格式 & MAC地址 见上图的 Ethernet IIIP 报文格式 见上图的 Internet Protocol Version 6TCP 段格式 见上图的 Transmission Control Protocol,这里就不把具体内容再贴一遍了。

FTP 协议命令与响应格式

FTP 协议命令格式<命令码> [<参数>] <CR><LF>

常见命令如下:

命令格式说明
USERUSER <用户名><CR><LF>发送用户名
PASSPASS <密码><CR><LF>发送密码
AUTHAUTH <机制><CR><LF>认证机制,如 AUTH TLSAUTH SSL,用于加密控制连接
SYSTSYST<CR><LF>获取服务器操作系统类型
FEATFEAT<CR><LF>查询服务器支持的特性扩展列表
PWDPWD<CR><LF>打印当前工作目录(Print Working Directory)
TYPETYPE <类型码><CR><LF>设置传输类型,如 TYPE A(ASCII)、TYPE I(二进制/图像)
PASVPASV<CR><LF>进入被动模式,服务器返回IP和端口供客户端连接
MLSDMLSD [<路径>]<CR><LF>列出目录内容(机器可解析格式),比LIST更标准化
CWDCWD <路径><CR><LF>改变当前工作目录(Change Working Directory)
RETRRETR <文件名><CR><LF>下载文件
STORSTOR <文件名><CR><LF>上传文件
PORTPORT h1,h2,h3,h4,p1,p2<CR><LF>指定数据连接端口(主动模式)
LISTLIST [<路径>]<CR><LF>列出目录内容(人类可读格式)
QUITQUIT<CR><LF>断开连接

FTP 协议响应格式<状态码> <空格> <说明文本> <CR><LF>

具体见Wikipedia,或者对照如下 AI 给的表格(正确性未检验):

状态码表
状态码含义出现场景
110重新启动标记应答REST 命令后
120服务在 nnn 分钟内准备好服务器繁忙时
125数据连接已打开,准备传送数据传输开始前
150文件状态良好,打开数据连接LIST、RETR、STOR 命令后
200命令成功TYPE、PORT、PASV 等命令成功
211系统状态或帮助响应SYST、STAT 命令的响应
212目录状态STAT 命令
213文件状态STAT 命令
214帮助信息HELP 命令
215名字系统类型SYST 命令响应
220服务就绪连接建立后
221服务关闭,退出登录QUIT 命令后
225数据连接打开,无传输数据连接已建立
226关闭数据连接,传输成功文件传输完成
227进入被动模式PASV 命令响应,返回 IP 和端口
229进入扩展被动模式EPSV 命令响应
230用户登录成功PASS 命令验证通过
234AUTH 命令成功AUTH TLS/SSL 成功
250请求的文件操作完成CWD、RMD、MKD 等命令成功
257路径名已创建PWD、MKD 命令响应
331用户名正确,需要密码USER 命令后
332需要登录账户信息需要 ACCT 命令
350需要进一步命令RNFR 命令后需要 RNTO
421无法提供服务,关闭连接连接超时或服务器过载
425无法打开数据连接端口无法绑定或连接失败
426连接关闭,传输中止数据传输中断
450请求的文件操作未执行文件被占用或不可访问
451操作中止:本地错误服务器内部错误
452系统存储空间不足写入磁盘空间不足
500语法错误,命令不可识别未知命令
501参数语法错误参数格式错误
502命令未实现服务器不支持该命令
503命令顺序错误如登录前执行需要登录的命令
504命令参数未实现参数有效但不支持
530未登录需要先登录认证
532需要账户信息存储文件需要 ACCT 命令
550请求操作未执行文件不存在或无权限
551操作中止:页类型未知非标准页类型
552存储分配溢出磁盘空间或配额不足
553文件名不合法文件名包含非法字符

用过滤器过滤出 FTP 数据包:

ftp_list

过滤器使用示例
  • ftp:筛选所有 FTP 协议的数据包
  • ftp.request.command == "AUTH":筛选 FTP 请求中命令为 AUTH 的数据包
  • ftp.response.code == 220:筛选 FTP 特定相应状态码的数据包
  • 可参考过滤器的补全提示

一个一个包点开看显得很蠢,比较好的做法是分析会话数据流

选中一个数据包,如果是 Wireshark,右键 -> 追踪流 -> TCP Stream 或者按快捷键 Ctrl + Shift + Alt + T,如果是 EasyTshark,点击数据包右侧聊天气泡般的按钮即可,这里以 EasyTshark 为例。

会话时序图:

dialog

会话数据流里可看到 FTP 命令、响应、数据传输等信息:

stream1

stream2

可见,命令和相应格式与前面所说一致。

HTTP 流量分析

也是个明文协议,看看会话数据流就差不多了,没啥好分析的。

stream3

TCP 机制观察

连接建立与拆除

TCP 有三次握手与四次挥手。

  • 三次握手:客户端 SYN,服务端 SYN + ACK,客户端 ACK
  • 四次挥手:主动关闭方 FIN + ACK,被动关闭方 ACK,被动关闭方 FIN + ACK,主动关闭方 ACK

挑一个 TCP 包,查看时序图,即可观察到 TCP 的三次握手与四次挥手的过程:

tcp

序列号与确认号的变化

seq

窗口机制与拥塞控制

这部分参考了以下两篇文章:

具体实验做法很简单,找个站点下载文件,并用 Wireshark 捕捉整个过程,分析 tcptrace 图。

如果得不到理想的图,可能要换几个站点,多次尝试。

我得到的 tcptrace 图如下:

tcptrace

这个图一共有三条线,分别代表:

  • 绿线:接收方通告的窗口大小,即接收方当前能接收多少数据(窗口机制的体现)。
  • 蓝线:发送方实际发出的数据量。它是连续攀升的,每向上一步,都代表一个数据包被发出。线的斜率直接反映了当时的发送速率。
  • 黄线:发送方已 ACK 的数据量,也就是实际收到的数据。
  • 红色竖线:代表 SACK(Selective Acknowledgment),每条红色竖线代表一个被接收方成功收到但“不连续”的数据块,意味着出现了丢包数据包乱序

从这张图就可以清晰地看到 TCP 的慢启动和拥塞避免阶段:

tcptrace2

3. 调库侦听网络数据并记录统计

Ether/IP

侦听网络上的数据流,解析发送方与接收方的 MAC 和 IP 地址,并按如下 CSV 格式输出日志:

时间,源MAC,源IP,目标MAC,目标IP,帧长度
2015-03-14 13:05:16,60-36-DD-7D-D5-21,192.168.33.1,60-36-DD-7D-D5-72,192.168.33.2,1536

使用 Python + Scapy 一把梭:

import csv
import time
from collections import defaultdict
from scapy.all import Ether, IP, sniff

stats = defaultdict(int)
start_time = time.time()

def process_packet(packet):
global start_time

# 确保是以太网帧且是 IP 包
if Ether in packet and IP in packet:
pkt_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(packet.time))
src_mac = packet[Ether].src
dst_mac = packet[Ether].dst
src_ip = packet[IP].src
dst_ip = packet[IP].dst
pkt_len = len(packet)

# 写入 CSV
row = [pkt_time, src_mac, src_ip, dst_mac, dst_ip, pkt_len]
print(",".join(map(str, row)))
with open("traffic_log.csv", "a", newline = "") as f:
writer = csv.writer(f)
writer.writerow(row)

# 长度记录
stats[f"From {src_mac}({src_ip})"] += pkt_len
stats[f"To {dst_mac}({dst_ip})"] += pkt_len

# 每 15 秒输出一次统计
current_time = time.time()
if current_time - start_time >= 15:
for key, val in stats.items():
print(f"{key}: {val} Bytes")
print("-------------------------\n")
stats.clear()
start_time = current_time

with open("traffic_log.csv", "w", newline = "") as f:
f.write("时间,源MAC,源IP,目标MAC,目标IP,帧长度\n")

sniff(prn = process_packet, store = False)

HTTP/HTTPS

侦听并观察 HTTP 与 HTTPS(在可控情况下、指定证书或单向加密场景)数据,分析访问特征。

懒得 vibe 一个网站出来,故没有做。

FTP

侦听 FTP 流量,提取用户名和口令并记录登录行为。日志格式示例如下:

时间,源MAC,源IP,目标MAC,目标IP,登录名,口令,成功与否
2015-03-14 13:05:16,60-36-DD-7D-D5-21,192.168.33.1,60-36-DD-7D-D5-72,192.168.33.2,student,software,SUCCEED
2015-03-14 13:05:16,60-36-DD-7D-D5-21,192.168.33.1,60-36-DD-7D-D5-72,192.168.33.2,student,software1,FAILED

和 Task1 一样。

from scapy.all import Ether, IP, TCP, Raw, sniff, rdpcap
import time
import csv
from collections import defaultdict

# key = (client_ip, server_ip),value = {"user":..., "pass":..., "src_mac":..., ...}
ftp_sessions = {}

def log_session(session, result):
user = session.get("user", "UNKNOWN")
passwd = session.get("pass", "UNKNOWN")
row = [
session.get("time", ""),
session.get("src_mac", ""),
session.get("src_ip", ""),
session.get("dst_mac", ""),
session.get("dst_ip", ""),
user,
passwd,
result
]
print(",".join(map(str, row)))
with open("ftp_session.csv", "a", newline = "") as f:
writer = csv.writer(f)
writer.writerow(row)

def process_packet(packet):
global start_time

if Ether in packet and IP in packet and TCP in packet and Raw in packet:
pkt_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(packet.time)))
src_mac = packet[Ether].src
dst_mac = packet[Ether].dst
src_ip = packet[IP].src
dst_ip = packet[IP].dst
pkt_len = len(packet)

sport = packet[TCP].sport
dport = packet[TCP].dport

# 正常来说,得做一个协议分析来判断是否为 FTP 流量,这里我选择简化处理,默认 21 号端口的 TCP 数据包都是 FTP 流量。
if dport != 21 and sport != 21: return

try:
payload = packet[Raw].load.decode("utf-8", errors = "ignore").strip()
except Exception: return

# Client -> Server
if dport == 21:
session_key = (src_ip, dst_ip)
if payload.upper().startswith("USER "):
username = payload[5:].strip()
ftp_sessions[session_key] = {
"user": username,
"pass": "",
"src_mac": src_mac,
"dst_mac": dst_mac,
"src_ip": src_ip,
"dst_ip": dst_ip,
"time": pkt_time
}
print(f" USER: {username} ({src_ip} -> {dst_ip})")

elif payload.upper().startswith("PASS "):
password = payload[5:].strip()
if session_key in ftp_sessions:
ftp_sessions[session_key]["pass"] = password
ftp_sessions[session_key]["time"] = pkt_time
print(f" PASS: {password} ({src_ip} -> {dst_ip})")

# Server -> Client
elif sport == 21:
session_key = (dst_ip, src_ip)

if session_key not in ftp_sessions: return

session = ftp_sessions[session_key]

# 230 Login successful
if payload.startswith("230"):
log_session(session, "SUCCEED")
del ftp_sessions[session_key]

# 530 Login incorrect / not logged in
elif payload.startswith("530"):
log_session(session, "FAILED")
del ftp_sessions[session_key]

# 331 User name okay, need password.
elif payload.startswith("331"):
print(f" User name okay, need password.")


with open("ftp_session.csv", "w") as f:
f.write("时间,源MAC,源IP,目标MAC,目标IP,登录名,口令,成功与否\n")

sniff(prn = process_packet, filter = "tcp port 21", store = False)

测试输出:

USER: hello  (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: world (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:52:40,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,hello,world,FAILED
USER: teacher (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: world (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:53:13,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,teacher,world,FAILED
USER: root (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: root (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:53:24,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,root,root,FAILED
USER: root (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: 123456 (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:53:29,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,root,123456,FAILED
USER: admin (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: admin123 (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:53:43,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,admin,admin123,FAILED
USER: admin (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: admin (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:53:50,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,admin,admin,FAILED
USER: student (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: 123456 (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:54:04,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,student,123456,FAILED
USER: student (10.30.38.117 -> 121.192.180.236)
User name okay, need password.
PASS: Iwonttellu (10.30.38.117 -> 121.192.180.236)
2026-04-03 14:54:17,04:ec:d8:c9:52:a7,10.30.38.117,40:fe:95:fe:80:01,121.192.180.236,student,Iwonttellu,SUCCEED
  • 标题: 计网实验 - PCAP 侦听与分析
  • 作者: Coast23
  • 创建于 : 2026-04-03 15:08:58
  • 更新于 : 2026-04-03 15:08:58
  • 链接: https://coast23.github.io/2026/04/03/计网实验-PCAP-侦听与分析/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论