前言

最近在读 WireGuard® 的源码和论文时使用了 Wireshark 进行抓包分析,本文是对这个过程(如何通过 Wireshark 实时抓取 WireGuard 加密包并解密1)的一点记录。

内容

思路

  • 在虚拟化准备两台虚拟机用以构建一个 WireGuard VPN 测试网络,其中虚拟机 A 作为请求发起方,虚拟机 B 作为响应方。
  • 在本地 Windows 笔记本上安装 Wireshark(包含 sshdump 功能)通过 SSH 的形式连接虚拟机 A,实现 Wireshark 远程实时抓包。

挑战

  • 前向保密性2对流量分析的挑战。WireGuard 为了保证前向保密性,每隔 120s 会更换一个临时密钥(一次性的)来加密数据包。因此,如果要获取实时的明文数据,Wireshark 需要动态的获取临时密钥,并基于临时密钥实时解密。
  • 需要更换内核。WireGuard 是 Linux 内核模块,笔者的测试环境需要重新编译并替换内核才能基于 Kprobe 的方式动态获取临时密钥。

具体操作

编译并更换内核

我是在虚拟机 A 上抓包,所以需要编译和替换虚拟机 A 的内核,操作见:Arch Linux Wiki

我使用的内核是这个:

# 切到 stable 分支,我实践的时候,内核版本是 6.5,commit:fa41884c1c6d
git clone git://git.zx2c4.com/wireguard-linux

按照 Arch Linux Wiki 操作,大概有这些步骤:

  • 准备内核配置 zcat /proc/config.gz > .config
  • 开启 WireGuard Debug make nconfig
  • 构建内核 make -j
  • 构建内核模块 make modules
  • 安装内核模块 make modules_install
  • 构建引导镜像 make bzImage
  • 安装引导镜像 cp -v arch/x86/boot/bzImage /boot/vmlinuz-linux-wg
  • 准备引导配置 cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux-wg.preset && vim /etc/mkinitcpio.d/linux-wg.preset
  • 更新引导配置 mkinitcpio -p linux-wg && grub-mkconfig -o /boot/grub/grub.cfg

一切顺利你会得到一个这样的内核:

# uname -r
6.5.0-rc4-gfa41884c1c6d

重启服务器,在 grub 页面选择新内核启动即可。

获取并配置动态临时密钥

这块参考了 Wireshark Wiki

在虚拟机 A 执行如下操作:

git clone git://git.zx2c4.com/wireguard-tools
cd wireguard-tools/contrib/extract-handshakes
make

截止笔者写这篇文章时:contrib/extract-handshakes/extract-handshakes.sh:46 中的 Kprobe 的函数名需要修改:

diff --git a/contrib/extract-handshakes/extract-handshakes.sh b/contrib/extract-handshakes/extract-handshakes.sh
index 688f4e4..ec8ab21 100755
--- a/contrib/extract-handshakes/extract-handshakes.sh
+++ b/contrib/extract-handshakes/extract-handshakes.sh
@@ -43,7 +43,7 @@ turn_off() {
 }

 trap turn_off INT TERM EXIT
-echo "p:wireguard/idxadd index_hashtable_insert ${ARGS[*]}" >> /sys/kernel/debug/tracing/kprobe_events
+echo "p:wireguard/idxadd wg_index_hashtable_insert ${ARGS[*]}" >> /sys/kernel/debug/tracing/kprobe_events
 echo 1 > /sys/kernel/debug/tracing/events/wireguard/idxadd/enable

 unpack_u64() {

修改好后,我们准备 WireGuard 网络。

在虚拟机 A (IP: 192.168.188.58) 执行:

wg genkey > ./private
ip link add dev wg0 type wireguard
ip addr add 10.0.0.4/24 dev wg0
wg set wg0 listen-port 51820
wg set wg0 private-key ./private
# 假设虚拟机 B 的 public key 是 kYsXxATA9sct/bLow/IgekJcrOWRhS6wDIBaqmBuuzM=
wg set wg0 peer 'kYsXxATA9sct/bLow/IgekJcrOWRhS6wDIBaqmBuuzM=' allowed-ips '10.0.0.0/24' endpoint '192.168.188.196:51820'
ip link set wg0 up

在虚拟机 B (IP: 192.168.188.196) 执行:

wg genkey > ./private
ip link add dev wg0 type wireguard
ip addr add 10.0.0.1/24 dev wg0
wg set wg0 listen-port 51820
wg set wg0 private-key ./private
# 假设虚拟机 A 的 public key 是 glGx5x9I/hmpv3XGx7YrAnEaq/dijVeqY9xiUocneik=
wg set wg0 peer 'glGx5x9I/hmpv3XGx7YrAnEaq/dijVeqY9xiUocneik=' allowed-ips '10.0.0.0/24' endpoint '192.168.188.58:51820'
ip link set wg0 up

生产的 WireGuard 网络,我一般还会开启 PSK(PreSharedKey),但这个功能会导致 Wireshark 无法解密 WireGuard 的数据包,FYI。

然后回到虚拟机 A,执行下面的命令捕获临时密钥,命令如下:

cd wireguard-tools/contrib/extract-handshakes
./extract-handshakes.sh > wg.keys &
ping -c 1 '10.0.0.1'

wg.keys 中的内容如下:

wg-keys

因为 Wireshark 是在本地,所以这里还需要通过 SSHFS 将虚拟机 A 的磁盘挂载到本地 Windows 上,这样 Wireshark 才能实时读取到 wg.keys。

在本地 Windows 上安装 SSHFS 的 PowerShell 命令如下:

winget install --id SSHFS-Win.SSHFS-Win --force

通过 SSHFS 将该虚拟机磁盘挂载到 Windows 资源管理器内:

create-network-drive-step-1.png create-network-drive-step-2.png

截图中的地址如下:

\\sshfs.k\\<username>@<host>

一切顺利的话会是这个样子:

sshfs.png

Wireshark 基于 SSH 远程实时抓包操作见:

Wireshark 配置动态临时密钥见:

视频末尾演示了如何通过 Wireshark 抓取 WireGuard 中的 HTTP 包。

抓取时可以使用下面的捕获规则,只抓取 WireGuard 的数据包:

udp[8:1] >= 1 and udp[8:1] <= 4 and udp[9:1] == 0 and udp[10:2] == 0

抓取 WireGuard 中的 HTTPS 包并解密:

总结

以上就是我通过 Wireshark 抓取 WireGuard 数据包并实时解密的全部操作。 如果你发现本文中存在任何语法错误,欢迎通过 主页 中 Email 联系我,另外你也可以在 关于 里面找到我的微信二维码。

参考


  1. 如果你只关心 WireGuard 网络中存在什么流量,可以通过 WireGuard 虚拟设备抓取明文包(而不是通过物理网卡抓取加密包,解密从而得到明文包)。通过物理设备抓取加密包并解密的目的是为了了解 WireGuard 协议的细节,比如:如何建立握手等。 ↩︎

  2. 前向保密性。假设攻击者记录了所有加密流量,并在未来某一天获得了使用者的密钥。因为数据包的加密是基于临时密钥(一次性的,通过使用者的密钥协商产生,每隔 120s 更换一次),因此,即使攻击者获得了使用者的密钥,过去的流量也无法被解密,即前向保密性。 ↩︎