简介:这份文档面向信息安全技术应用专业的学生与网络协议分析初学者,聚焦通过 WireShark 抓包还原 SSH 协议完整工作流程,帮助读者理解加密远程登录背后的密钥交换、用户认证与加密数据传输三大阶段。资源包共 1 个 docx 文件,约 37KB,内容以图文步骤与协议报文解析为主,便于边看边在实验环境中对照操作。文档结合单兵模式赛题资源,详细拆解算法协商、密钥交换、密码与公钥认证、服务请求及连接关闭等环节,并给出从配置 IP、设置过滤条件到分析 TCP 连接、SSH 版本信息、密钥交换消息与加密数据的实训步骤,可帮助读者掌握抓包过滤、报文定位与协议排错思路。目前已有 111 人学习,适合作为网络安全课程实验、协议分析练习与赛前查漏补缺的参考材料。
1. 从一次 SSH 登录抓包说起:为什么你看到的全是加密乱码
你在 PC 上打开 Wireshark,选好网卡,过滤ssh,然后ssh user@192.168.1.100登录一台 Linux 主机。抓到的包长这样:TCP 三次握手正常,紧接着一串SSHv2 Client: Key Exchange Init、SSHv2 Server: Key Exchange Init,再往后全是SSHv2 Client: Encrypted packet。点开 Payload,十六进制一片,ASCII 区什么都读不出来。这不是抓错了,而是 SSH 协议本身的设计——协商阶段明文,密钥交换完成后全部加密。很多人第一次做 SSH 协议分析就卡在这里:以为 Wireshark 能像看 HTTP 那样直接读出用户名密码,结果发现是个黑匣子。这篇笔记要解决的就是这件事:在只有 Wireshark 的条件下,怎么把 SSH 会话拆到能讲清楚的程度——版本协商看了什么、算法怎么谈的、密钥交换走的是哪条曲线、加密通道建立后还能从哪些字段判断会话健康度。适合信息安全技术应用专业做协议分析实验的学生,也适合刚接触流量分析的运维。前提是你已经装好 Wireshark,能选对网卡抓到包,剩下的跟着走就行。
2. SSH 协议在 Wireshark 里的可见边界:哪些能看,哪些永远看不到
2.1 明文阶段与加密阶段的分界线在哪
SSH 会话的生命周期可以粗分为四段:TCP 连接建立、协议版本交换、算法协商与密钥交换、加密会话。Wireshark 能完整解析前三段,第四段只能看到包的长度、方向和时序。分界线的标志是第一条SSHv2 Client: Encrypted packet出现——在这之前,所有字段都是可读的;在这之后,Wireshark 的 SSH 解析器会停止解码 Payload,只保留包头信息。
版本交换阶段只有两行:客户端发SSH-2.0-OpenSSH_8.9p1,服务端回SSH-2.0-OpenSSH_7.4。这两行直接以 ASCII 出现在 TCP Payload 里,不需要任何解码。算法协商阶段是SSH_MSG_KEXINIT报文,里面列了双方支持的密钥交换算法、主机密钥算法、加密算法、MAC 算法、压缩算法。Wireshark 会把这些列表展开成树状结构,你能看到客户端提议了curve25519-sha256、ecdh-sha2-nistp256、diffie-hellman-group14-sha256等,服务端回自己的列表。最终选哪一套,取决于双方列表的第一个交集。
密钥交换阶段根据协商结果不同,报文数量不一样。以curve25519-sha256为例,客户端发SSH_MSG_KEX_ECDH_INIT,携带 32 字节的客户端公钥;服务端回SSH_MSG_KEX_ECDH_REPLY,携带服务端公钥、主机密钥、签名。Wireshark 能解析出这些字段的原始字节,但不会帮你算共享密钥——那是协议栈内部的事。
提示:如果你在 Wireshark 里看不到
SSH_MSG_KEXINIT的详细字段,检查是否用了ssh作为过滤条件。更精确的过滤是tcp.port == 22,然后手动展开SSHv2协议树。
2.2 加密后还能从包头拿到什么信息
加密通道建立后,每个 SSH 包的结构是:4 字节 packet_length + 1 字节 padding_length + payload + padding + MAC。Wireshark 能读到 packet_length 和 padding_length,payload 和 MAC 是密文。这意味着你能知道每个包有多大、发送方向、时间间隔,但不知道里面是SSH_MSG_CHANNEL_DATA还是SSH_MSG_CHANNEL_REQUEST。
这些信息并非没有价值。交互式会话中,用户每敲一个字符,客户端就发一个小包,packet_length 通常在 16 到 48 字节之间;执行ls这种命令,服务端返回的数据包会明显变大。通过包长分布和时序,可以粗略判断会话是在交互操作还是批量传输。更实用的场景是排查连接异常:如果客户端发了SSH_MSG_KEX_ECDH_INIT后服务端迟迟不回SSH_MSG_KEX_ECDH_REPLY,说明密钥交换阶段卡住了,可能是算法不匹配或网络中间设备干扰。
2.3 用 Wireshark 过滤表达式锁定 SSH 关键报文
默认的ssh过滤器会抓所有 22 端口的包,包括加密后的。做协议分析时,我一般用组合过滤把阶段拆开:
# 只看版本交换和算法协商阶段(前 10 个包通常够) tcp.port == 22 && tcp.len > 0 && frame.number <= 10 # 只看 SSHv2 协议报文,排除纯 ACK tcp.port == 22 && ssh.protocol # 定位密钥交换报文 tcp.port == 22 && (ssh.message_code == 30 || ssh.message_code == 31) # 查看加密包的长度分布 tcp.port == 22 && ssh.encrypted_packetssh.protocol这个字段在 Wireshark 3.x 里对应 SSHv2 协议树,能过滤掉 TCP 层的纯 ACK。ssh.message_code是 SSH 消息类型编号,30 是SSH_MSG_KEX_ECDH_INIT,31 是SSH_MSG_KEX_ECDH_REPLY。ssh.encrypted_packet是布尔字段,标记加密包。
参数说明:tcp.len > 0排除零长度的 ACK 包;frame.number <= 10是经验值,正常 SSH 握手在 10 个包内完成,如果超过说明有重传或延迟。这些过滤条件可以直接粘贴到 Wireshark 的显示过滤栏,不需要改捕获过滤器。
3. 跟着一次完整 SSH 登录走一遍:从 SYN 到加密通道的逐包拆解
3.1 搭建可复现的抓包环境
要复现后面的分析,你需要一台能 SSH 登录的 Linux 主机(物理机、虚拟机都行),一台装了 Wireshark 的 PC,两者在同一网段。如果手头没有 Linux 主机,用 Docker 起一个 OpenSSH 容器也行:
# 在 Linux 主机上启动一个带 SSH 的容器,映射 2222 端口 docker run -d --name ssh-test -p 2222:22 \ -e PASSWORD_ACCESS=true \ -e USER_PASSWORD=test123 \ -e USER_NAME=testuser \ linuxserver/openssh-server # 在 PC 上确认能连通 ssh testuser@192.168.1.100 -p 2222逻辑说明:-p 2222:22把容器 22 端口映射到主机 2222,避免和主机自带 SSH 冲突。PASSWORD_ACCESS=true启用密码登录,方便抓包时看到认证阶段。USER_PASSWORD和USER_NAME设置测试账号。
参数说明:如果你的 PC 就是 Linux 主机本身,直接ssh localhost抓 loopback 接口也行,但 loopback 的包不走物理网卡,Wireshark 要选Loopback: lo接口。生产环境抓包建议用镜像端口或 TAP,不要在业务主机上直接抓,避免影响性能。
3.2 版本交换与算法协商的字段解读
启动 Wireshark,选对网卡,过滤tcp.port == 2222(如果你用了上面的容器映射),然后在终端执行ssh testuser@192.168.1.100 -p 2222。抓到的前几个包:
第一个包是 TCP SYN,第二个是 SYN-ACK,第三个是 ACK。第四个包是客户端发的SSHv2 Client: Protocol (SSH-2.0-OpenSSH_8.9p1),第五个是服务端回的SSHv2 Server: Protocol (SSH-2.0-OpenSSH_7.4)。点开第四个包的 SSHv2 协议树,Protocol Version字段显示SSH-2.0-OpenSSH_8.9p1。这个字符串的格式是SSH-<协议版本>-<软件版本>,协议版本固定2.0,软件版本因实现而异。
第六个包是SSHv2 Client: Key Exchange Init。展开后重点看这几个字段:
| 字段 | 含义 | 示例值 |
|---|---|---|
| Cookie | 随机数,防重放 | 随机 16 字节 |
| kex_algorithms | 密钥交换算法列表 | curve25519-sha256, ecdh-sha2-nistp256, diffie-hellman-group14-sha256 |
| server_host_key_algorithms | 主机密钥算法 | ssh-ed25519, rsa-sha2-512, rsa-sha2-256 |
| encryption_algorithms_client_to_server | 客户端到服务端加密算法 | chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr |
| mac_algorithms_client_to_server | MAC 算法 | umac-64-etm@openssh.com, hmac-sha2-256-etm |
| compression_algorithms | 压缩算法 | none, zlib@openssh.com |
第七个包是服务端回的SSHv2 Server: Key Exchange Init,字段结构相同。Wireshark 不会自动高亮双方的交集,需要你自己对比。以密钥交换算法为例,客户端列表第一个是curve25519-sha256,服务端列表里也有,那最终就选它。如果客户端第一个是curve25519-sha256但服务端不支持,就看客户端第二个,以此类推。
注意:算法协商遵循“客户端偏好优先”原则,但前提是服务端也支持。如果双方没有交集,SSH 会直接断开,Wireshark 里表现为服务端发
SSH_MSG_DISCONNECT后 TCP FIN。
3.3 密钥交换报文的原始字节与字段含义
协商完成后,第八个包是SSHv2 Client: Key Exchange ECDH Init。展开 SSHv2 协议树,Message Code是 30,Client Public Key是 32 字节的十六进制串。这个公钥是客户端临时生成的,每次连接都不一样。
第九个包是SSHv2 Server: Key Exchange ECDH Reply。这个包信息量最大,包含三个关键字段:
Server Public Key:服务端临时公钥,也是 32 字节Host Key:服务端的主机密钥,类型取决于协商结果。如果是ssh-ed25519,这里是 32 字节公钥;如果是rsa-sha2-512,这里是一段 ASN.1 编码的 RSA 公钥Signature:服务端用主机私钥对交换哈希的签名,客户端用这个验证服务端身份
Wireshark 会把Host Key展开成可读结构。如果是 Ed25519,显示Key Type: ssh-ed25519和Public Key: <hex>;如果是 RSA,显示模数和指数。Signature字段通常不展开,因为验证签名需要计算交换哈希,Wireshark 不做这件事。
第十个包是SSHv2 Client: New Keys,第十一个是SSHv2 Server: New Keys。这两个包没有 Payload,只是通知对方“我这边密钥已经就绪,后面的包开始加密”。从第十二个包开始,就是SSHv2 Client: Encrypted packet了。
3.4 加密通道建立后的包长与时序分析
加密后的包,Wireshark 只显示Packet Length和Padding Length。以一次ls命令为例,抓到的包序列大致是:
Client -> Server Encrypted packet len=48 Server -> Client Encrypted packet len=64 Server -> Client Encrypted packet len=112 Server -> Client Encrypted packet len=96 Client -> Server Encrypted packet len=48len=48的包通常是客户端发送的按键或命令,len=64到112是服务端返回的命令输出。如果服务端返回大量数据,比如cat一个大文件,会看到连续多个len=1448左右的包——这是 TCP MSS 限制下的分片。
时序上,交互式会话的包间隔通常在几十毫秒到几百毫秒;如果看到某个包后间隔超过 2 秒才有下一个包,可能是网络延迟或服务端处理慢。排查连接卡顿时,这个信息比看加密 Payload 有用得多。
4. 避坑与排查:SSH 抓包分析里最容易翻车的五个地方
4.1 抓不到包:选错网卡或过滤条件写反
现象:Wireshark 界面一片空白,或者只有广播包,看不到 SSH 流量。
原因:最常见的是选错网卡。PC 上有多个网卡(有线、无线、虚拟网卡),如果 SSH 流量走有线但抓了无线,什么都抓不到。其次是捕获过滤器写成了ssh,但 SSH 服务跑在非 22 端口,比如 2222,ssh过滤器只认 22 端口。
解决:先ipconfig或ifconfig确认目标 IP 走哪个网卡,在 Wireshark 里选对应接口。捕获过滤器用tcp port 2222而不是ssh。如果还是抓不到,检查是否有防火墙或交换机 ACL 拦截了流量。
4.2 只看到加密包:握手阶段被过滤掉了
现象:过滤ssh后,所有包都是Encrypted packet,看不到Key Exchange Init。
原因:Wireshark 的ssh显示过滤器会匹配所有 SSH 协议包,包括加密的。但如果抓包开始时 SSH 会话已经建立,握手包在抓包之前就完成了,自然看不到。
解决:先启动 Wireshark 抓包,再执行 SSH 登录。如果已经登录了,退出重新登录。另外,ssh过滤器在某些 Wireshark 版本里对加密包的匹配不稳定,改用tcp.port == 22更可靠。
4.3 算法协商失败:双方没有交集
现象:SSH 连接建立后立刻断开,Wireshark 里看到SSH_MSG_DISCONNECT,错误信息是no matching key exchange method found。
原因:客户端和服务端的算法列表没有交集。常见于老旧设备只支持diffie-hellman-group1-sha1,而新版 OpenSSH 默认禁用了这个算法。
解决:在 Wireshark 里对比双方的kex_algorithms列表,找到缺失的算法。临时方案是在客户端 SSH 配置里显式启用旧算法:ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 user@host。长期方案是升级服务端 SSH 版本。
4.4 主机密钥验证失败:抓包看到 Host Key 但客户端不认
现象:SSH 登录时提示Host key verification failed,Wireshark 里能看到服务端发了SSH_MSG_KEX_ECDH_REPLY,Host Key 字段也有值。
原因:客户端本地~/.ssh/known_hosts里存的主机密钥和服务端当前提供的不一致。可能是服务端重装了 SSH,或者中间有设备做了主机密钥替换。
解决:先确认服务端主机密钥指纹是否变化。在 Wireshark 里展开Host Key字段,计算指纹,和服务端ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub的输出对比。如果确认是服务端正常变更,删除本地known_hosts里对应条目再连。
4.5 时间戳错乱:抓包文件跨时区或时钟不同步
现象:Wireshark 里包的时间顺序看起来不对,或者和日志时间对不上。
原因:抓包 PC 和服务端的系统时钟不同步,或者抓包文件在另一台时区不同的机器上打开。
解决:抓包前用 NTP 同步双方时钟。Wireshark 里可以设置时间显示格式为 UTC,避免时区干扰。如果分析的是已有抓包文件,用Edit -> Time Shift调整时间偏移。
5. 用 tshark 批量提取 SSH 会话特征:从单次抓包到可复用的分析脚本
Wireshark 图形界面适合单次分析,但如果你要处理几十个抓包文件,或者想把 SSH 会话特征提取出来做统计,tshark更顺手。它是 Wireshark 的命令行版本,支持同样的过滤表达式和字段提取。
下面这个脚本从指定 pcap 文件里提取每个 SSH 会话的版本号、协商算法、加密包数量和平均包长:
#!/bin/bash # ssh_session_summary.sh # 用法: ./ssh_session_summary.sh capture.pcap PCAP=$1 # 提取版本交换信息 echo "=== SSH 版本信息 ===" tshark -r "$PCAP" -Y "ssh.protocol" -T fields \ -e frame.number -e ip.src -e ip.dst -e ssh.protocol 2>/dev/null # 提取算法协商列表 echo "=== 密钥交换算法 ===" tshark -r "$PCAP" -Y "ssh.message_code == 20" -T fields \ -e ip.src -e ssh.kex_algorithms 2>/dev/null # 统计加密包数量和平均长度 echo "=== 加密包统计 ===" tshark -r "$PCAP" -Y "ssh.encrypted_packet" -T fields \ -e ip.src -e ip.dst -e tcp.len 2>/dev/null | \ awk '{sum[$1"->"$2]+=$3; count[$1"->"$2]++} END {for (k in sum) printf "%s: %d packets, avg len %.1f\n", k, count[k], sum[k]/count[k]}'逻辑说明:第一段用ssh.protocol过滤版本交换报文,输出帧号、源 IP、目的 IP 和协议字符串。第二段用ssh.message_code == 20过滤SSH_MSG_KEXINIT,输出源 IP 和算法列表。第三段用ssh.encrypted_packet过滤加密包,用 awk 按方向统计包数和平均长度。
参数说明:-Y是显示过滤器,和 Wireshark 界面里的过滤栏语法一致。-T fields指定输出字段模式,-e指定字段名。2>/dev/null屏蔽 tshark 的警告信息。如果你的 tshark 版本不支持ssh.kex_algorithms字段,用tshark -G fields | grep ssh查一下实际字段名。
这个脚本的输出可以直接贴到实验报告里,也可以作为批量分析的基础。我一般会再加一段用capinfos统计抓包时长和总包数,判断会话是否完整。
提示:tshark 的字段名和 Wireshark 界面里显示的名称不完全一样。比如界面里叫
Key Exchange Algorithms,tshark 字段是ssh.kex_algorithms。不确定的时候,在 Wireshark 里点开对应字段,状态栏会显示字段名。
最后说一个我自己的习惯:每次做 SSH 协议分析实验,先抓一个完整的登录会话存成基线 pcap,后面遇到异常会话就和基线对比。基线里握手包的数量、算法列表、加密包起始帧号都是固定的,异常会话一眼就能看出差异。这个习惯帮我省了很多翻来覆去查文档的时间。希望帮到你。
本文还有配套的精品资源,点击获取