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:

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:

The address shown in the screenshots:
\\sshfs.k\\<username>@<host>
If everything goes well, it should look like this:

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
-
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. ↩︎
-
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. ↩︎