[PATCH 2/2] extract-handshakes: add a more user-friendly BTF approach

Peter Wu <[email protected]> Mon, 1 Jun 2026 00:55:21 +0200
Newsgroups com.zx2c4.lists.wireguard
Message-ID <[email protected]>
The previous implementation requires a copy of WireGuard kernel module
module sources matching the current kernel and a writeable directory.
Add an alternative implementation that reads the module offsets at
runtime using BTF (BPF Type Format).

The previous module-based implementation is maintained in case BTF is
unavailable, like hardened systems with structure layout randomization.

A fprobetrace-based approach was also considered as that would avoid the
need to manually extract BTF offsets. It is currently not feasible
however since the wg_index_hashtable_insert entry parameter cannot be
typecast to the larger structure it was embedded in.

Signed-off-by: Peter Wu <[email protected]>
---
 contrib/extract-handshakes/README             |  7 +-
 .../extract-handshakes/extract-handshakes.sh  |  8 ++-
 contrib/extract-handshakes/make-offsets.sh    | 70 +++++++++++++++++++
 3 files changed, 82 insertions(+), 3 deletions(-)
 create mode 100755 contrib/extract-handshakes/make-offsets.sh

diff --git a/contrib/extract-handshakes/README b/contrib/extract-handshakes/README
index 1d030fa..568d142 100644
--- a/contrib/extract-handshakes/README
+++ b/contrib/extract-handshakes/README
@@ -6,7 +6,12 @@ to them being sent, via kprobes. It exports the bare minimum to be
 able to then decrypt all packets in the handshake and in the subsequent
 transport data session.
 
-Build:
+These probes depend on knowledge of kernel structure offsets. Ideally
+from BTF (BPF Type Format), available with CONFIG_DEBUG_INFO_BTF=y
+and Linux 5.2+. On systems where this is not available, an alternative
+approach is possible when WireGuard module sources are available.
+
+Build (only if BTF is unavailable):
 
     $ make
 
diff --git a/contrib/extract-handshakes/extract-handshakes.sh b/contrib/extract-handshakes/extract-handshakes.sh
index 57d397e..284749f 100755
--- a/contrib/extract-handshakes/extract-handshakes.sh
+++ b/contrib/extract-handshakes/extract-handshakes.sh
@@ -2,13 +2,17 @@
 # SPDX-License-Identifier: GPL-2.0
 #
 # Copyright (C) 2015-2026 Jason A. Donenfeld <[email protected]>. All Rights Reserved.
-# Copyright (C) 2017-2018 Peter Wu <[email protected]>. All Rights Reserved.
+# Copyright (C) 2017-2026 Peter Wu <[email protected]>. All Rights Reserved.
 
 set -e
 
 ME_DIR="${BASH_SOURCE[0]}"
 ME_DIR="${ME_DIR%/*}"
-source "$ME_DIR/offsets.include" || { echo "Did you forget to run make?" >&2; exit 1; }
+if [ -e "$ME_DIR/offsets.include" ]; then
+	source "$ME_DIR/offsets.include"
+else
+	source "$ME_DIR/make-offsets.sh"
+fi
 
 case "$(uname -m)" in
 	x86_64) ARGUMENT_REGISTER="%si" ;;
diff --git a/contrib/extract-handshakes/make-offsets.sh b/contrib/extract-handshakes/make-offsets.sh
new file mode 100755
index 0000000..ed7e6b4
--- /dev/null
+++ b/contrib/extract-handshakes/make-offsets.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2026 Peter Wu <[email protected]>. All Rights Reserved.
+
+set -eu -o pipefail
+
+if ! [ -e /sys/kernel/btf/vmlinux ]; then
+	echo "kernel BTF is missing, requires Linux v5.2+ with CONFIG_DEBUG_INFO_BTF=y" >&2
+	exit 1
+fi
+
+if ! [ -e /sys/kernel/btf/wireguard ]; then
+	echo "BTF not available for wireguard, ensure kernel module is loaded." >&2
+	exit 1
+fi
+
+for tool in bpftool jq; do
+	if ! type "$tool" &>/dev/null; then
+		echo "Extracting offsets using BTF requires tool: $tool" >&2
+		exit 1
+	fi
+done
+
+declare -A struct_offsets
+while read -r struct field offset; do
+	struct_offsets["$struct.$field"]="$offset"
+done < <(
+	bpftool -j btf dump file /sys/kernel/btf/wireguard |
+		jq -cr '.types[]|select(.kind=="STRUCT" and
+		(.name=="noise_handshake" or .name=="noise_static_identity")
+		) as $t|.members[]|[$t.name, .name, .bits_offset/8]|@tsv'
+)
+
+struct_offset() {
+	local struct="$1" field="$2" offset
+	offset="${struct_offsets[$struct.$field]:-}"
+	if [ -z "$offset" ]; then
+		echo "Failed to find offset for struct $struct $field" >&2
+		exit 1
+	fi
+	echo "$offset"
+}
+
+declare -A OFFSETS
+OFFSETS[LOCAL_STATIC_PRIVATE_KEY]=$(struct_offset noise_static_identity static_private),$(struct_offset noise_handshake static_identity)
+OFFSETS[LOCAL_EPHEMERAL_PRIVATE_KEY]=$(struct_offset noise_handshake ephemeral_private)
+OFFSETS[REMOTE_STATIC_PUBLIC_KEY]=$(struct_offset noise_handshake remote_static)
+OFFSETS[PRESHARED_KEY]=$(struct_offset noise_handshake preshared_key)
+
+if [ -e /sys/kernel/cpu_byteorder ]; then
+	# Since Linux 6.2 (February 2023)
+	read -r ENDIAN < /sys/kernel/cpu_byteorder
+else
+	[[ "$(lscpu)" == *Byte\ Order:*Big* ]] && ENDIAN=big || ENDIAN=little
+fi
+
+# Pretty-print values if not sourced.
+if ! (return 0 2>/dev/null); then
+	# Pretty-print version of 'declare -p OFFSETS'
+	cat <<EOF
+declare -A OFFSETS=(
+	[LOCAL_STATIC_PRIVATE_KEY]=${OFFSETS[LOCAL_STATIC_PRIVATE_KEY]}
+	[LOCAL_EPHEMERAL_PRIVATE_KEY]=${OFFSETS[LOCAL_EPHEMERAL_PRIVATE_KEY]}
+	[REMOTE_STATIC_PUBLIC_KEY]=${OFFSETS[REMOTE_STATIC_PUBLIC_KEY]}
+	[PRESHARED_KEY]=${OFFSETS[PRESHARED_KEY]}
+)
+ENDIAN=$ENDIAN
+EOF
+fi
-- 
2.54.0