[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