Introduction

While reading the WireGuard(R) source code and whitepaper, I used Wireshark for packet analysis. This article documents the process of capturing WireGuard encrypted packets with Wireshark and decrypting them in real time1.

Content

Approach

  • Set up two virtual machines to build a WireGuard VPN test network, where VM A acts as the initiator and VM B acts as the responder.
  • Install Wireshark (with sshdump support) on a local Windows laptop and connect to VM A via SSH for remote real-time packet capture.

Challenges

  • Forward secrecy2 complicates traffic analysis. To ensure forward secrecy, WireGuard rotates ephemeral keys (one-time use) every 120 seconds to encrypt data packets. Therefore, to obtain real-time plaintext data, Wireshark needs to dynamically retrieve ephemeral keys and decrypt packets on the fly.
  • Kernel replacement is required. WireGuard is a Linux kernel module. In my test environment, I needed to recompile and replace the kernel to dynamically retrieve ephemeral keys via Kprobe.

Step-by-Step Operations

Compiling and Replacing the Kernel

Since I capture packets on VM A, I need to compile and replace VM A’s kernel. See: Arch Linux Wiki

The kernel I used:

# Switch to the stable branch. At the time of writing, the kernel version was 6.5, commit: fa41884c1c6d
git clone git://git.zx2c4.com/wireguard-linux

Following the Arch Linux Wiki, the steps are roughly:

  • Prepare the kernel config: zcat /proc/config.gz > .config
  • Enable WireGuard Debug: make nconfig
  • Build the kernel: make -j
  • Build kernel modules: make modules
  • Install kernel modules: make modules_install
  • Build the boot image: make bzImage
  • Install the boot image: cp -v arch/x86/boot/bzImage /boot/vmlinuz-linux-wg
  • Prepare the boot preset: cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux-wg.preset && vim /etc/mkinitcpio.d/linux-wg.preset
  • Update the boot config: mkinitcpio -p linux-wg && grub-mkconfig -o /boot/grub/grub.cfg

If everything goes well, you should get a kernel like this:

# uname -r
6.5.0-rc4-gfa41884c1c6d

Reboot the server and select the new kernel at the GRUB menu.

Retrieving and Configuring Dynamic Ephemeral Keys

This section references the Wireshark Wiki.

Run the following on VM A:

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

As of the time of writing, the Kprobe function name at contrib/extract-handshakes/extract-handshakes.sh:46 needs to be modified:

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() {

After applying the fix, set up the WireGuard network.

On VM 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
# Assuming VM B's public key is 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

On VM 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
# Assuming VM A's public key is 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

In production WireGuard networks, I usually enable PSK (PresharedKey), but this feature prevents Wireshark from decrypting WireGuard packets, FYI.

Then, back on VM A, run the following command to capture ephemeral keys:

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

The contents of wg.keys look like this:

wg-keys

Since Wireshark is running locally, we also need to mount VM A’s disk to the local Windows machine via SSHFS so that Wireshark can read wg.keys in real time.

To install SSHFS on local Windows, run the following PowerShell command:

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

Mount the VM’s disk to Windows Explorer via SSHFS:

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

The address shown in the screenshots:

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

If everything goes well, it should look like this:

sshfs.png

Remote real-time packet capture via SSH in Wireshark:

Configuring dynamic ephemeral keys in Wireshark:

The end of the video demonstrates how to capture HTTP packets within WireGuard using Wireshark.

You can use the following capture filter to only capture WireGuard packets:

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

Capturing and decrypting HTTPS packets within WireGuard:

Summary

This covers the entire process of capturing WireGuard packets with Wireshark and decrypting them in real time. If you find any errors in this article, feel free to contact me via Email on the homepage. You can also find my WeChat QR code on the about page.

References


  1. If you only care about what traffic flows through the WireGuard network, you can capture plaintext packets on the WireGuard virtual interface (rather than capturing encrypted packets on the physical NIC and decrypting them). Capturing encrypted packets on the physical interface and decrypting them is intended for understanding WireGuard protocol details, such as how handshakes are established. ↩︎

  2. Forward secrecy. Suppose an attacker records all encrypted traffic and obtains the user’s long-term key at some point in the future. Since packets are encrypted using ephemeral keys (one-time use, derived from the user’s long-term key through key negotiation, rotated every 120 seconds), even if the attacker obtains the user’s long-term key, past traffic cannot be decrypted. This property is known as forward secrecy. ↩︎