vpnhide/kmod/BUILDING.md
okhsunrog e35cf1a6b9 refactor: overhaul kmod build system, fix kernel module bugs
Build system:
- Replace hardcoded paths in Makefile with env vars (KERNEL_SRC, CLANG_DIR)
- Add .env.example and .envrc for direnv-based config
- Simplify build-zip.sh to delegate to make instead of duplicating build command
- Rewrite BUILDING.md: 5-step happy path with direnv, standalone prep as appendix
- Remove redundant quick-reference script and step 7 (manual module.lds hack)

Kernel module (vpnhide_kmod.c):
- Fix fib_route_seq_show hook: save seq_file pointer and buffer position in entry
  handler instead of reading regs->regs[0] in return handler (which holds the
  return value on arm64, not the original argument). Rewrite buffer scanning as
  clean forward iteration with memmove compaction.
- Remove dead SIOCGIFCONF case from dev_ioctl hook (confirmed in kernel source:
  SIOCGIFCONF goes through sock_ioctl -> dev_ifconf, not dev_ioctl on GKI 6.1)
- Fix header comment: remove false tcp4_seq_show claim, correct rtnl symbol name

Test app:
- Auto-run checks on launch (LaunchedEffect) for easier adb-driven testing
2026-04-11 18:50:18 +03:00

8.9 KiB

Building vpnhide-kmod for a new device

This guide walks through building the kernel module for any Android device with a GKI 2.0 kernel (Android 12+, kernel 5.10+). The process was developed and tested on Pixel 8 Pro (android14-6.1) and applies identically to any GKI generation — only the kernel branch and Module.symvers change.

Total time: ~15 minutes once toolchain is set up. Download size: ~500 MB (shallow kernel clone) + ~100 MB (toolchain if not already available).

Prerequisites

  • A rooted Android device with KernelSU or Magisk (for adb root shell)
  • Linux host (Arch, Ubuntu, Debian — anything with make, clang, git)
  • adb connected to the device
  • modprobe on the host (for extracting CRCs from .ko files)
  • direnv (for automatic env var loading)

Step 1: Identify the GKI generation

adb shell uname -r

Example outputs and what they mean:

uname -r output GKI generation Kernel branch
5.10.xxx-android13-4-g... android13-5.10 android13-5.10
5.15.xxx-android14-6-g... android14-5.15 android14-5.15
6.1.xxx-android14-11-g... android14-6.1 android14-6.1
6.1.xxx-android15-8-g... android15-6.1 android15-6.1
6.6.xxx-android15-... android15-6.6 android15-6.6

The androidXX part is the GKI generation (kernel branch name), NOT the Android version running on the device. A Pixel 7 Pro running Android 16 still has an android13-5.10 kernel because the generation is frozen at manufacturing time.

Step 2: Clone the full Pixel kernel tree

Google ships a single android14-6.1 kernel covering all Pixels from 6 to 9a. The full tree includes kernel sources, prebuilt toolchain, device trees, and all out-of-tree Google modules — everything needed with no extra downloads:

mkdir ~/pixel-kernel && cd ~/pixel-kernel

repo init --depth=1 \
    -u https://android.googlesource.com/kernel/manifest \
    -b android-gs-shusky-6.1-android16

repo sync -c --no-tags -j$(nproc)

Then build once to produce all artifacts:

./build_shusky.sh   # or build_raviole.sh etc. — same kernel image for all

Set an env var pointing to the tree root — subsequent steps reference it:

export PIXEL_KERNEL_TREE=~/pixel-kernel

Useful paths within the tree

Path Purpose
$PIXEL_KERNEL_TREE/aosp/ Full GKI kernel source tree — browse subsystems, understand exported APIs, read in-tree driver implementations
$PIXEL_KERNEL_TREE/out/shusky/dist/kernel-headers.tar.gz Kernel headers for building against (extract and use as KDIR)
$PIXEL_KERNEL_TREE/out/shusky/dist/vmlinux.symvers Symbol versions for modpost — use instead of extracting CRCs from device .ko files
$PIXEL_KERNEL_TREE/out/shusky/dist/System.map Symbol addresses for debugging
$PIXEL_KERNEL_TREE/private/google-modules/ Reference implementations of out-of-tree modules (Kconfig, Makefile patterns)
$PIXEL_KERNEL_TREE/prebuilts/clang/host/linux-x86/clang-r487747c/bin Bundled clang 17.0.2 — exact compiler used to build the kernel

raviole/dist/ and shusky/dist/ contain identical vmlinux.symvers and Image since it's the same kernel build — use either.

Prepare kernel headers for module builds

mkdir -p ~/pixel-kernel-headers
tar -xzf $PIXEL_KERNEL_TREE/out/shusky/dist/kernel-headers.tar.gz \
    -C ~/pixel-kernel-headers
cp $PIXEL_KERNEL_TREE/out/shusky/dist/vmlinux.symvers \
    ~/pixel-kernel-headers/Module.symvers

Then skip to step 3.

Alternative: shallow clone (non-Pixel or different GKI generation)

If you don't need the full tree, clone just the kernel source:

BRANCH="android13-5.10"  # ← replace with your generation from step 1

git clone --depth=1 -b $BRANCH \
    https://android.googlesource.com/kernel/common \
    ~/kernel-source

You'll then need to prepare it manually — see Preparing a standalone kernel source at the end.


Step 3: Configure .env

The build system uses direnv to load KERNEL_SRC and CLANG_DIR from a .env file. Copy the example and fill in your paths:

cd kmod/
cp .env.example .env

Edit .env:

# Pixel kernel tree approach (after extracting headers above):
KERNEL_SRC=~/pixel-kernel-headers
CLANG_DIR=~/pixel-kernel/prebuilts/clang/host/linux-x86/clang-r487747c/bin

# Or standalone kernel source approach:
# KERNEL_SRC=~/kernel-source
# CLANG_DIR=/path/to/clang/bin

Allow direnv to load it:

direnv allow

From now on, entering the kmod/ directory automatically exports KERNEL_SRC and CLANG_DIR. The Makefile reads both from the environment.

Step 4: Build and package

make            # builds vpnhide_kmod.ko
./build-zip.sh  # builds .ko (if needed) + packages vpnhide-kmod.zip

Step 5: Install and test

adb push vpnhide-kmod.zip /sdcard/Download/
# Install via KernelSU-Next manager -> Modules -> Install from storage
# Reboot

After reboot, verify:

# Module loaded?
adb shell "su -c 'lsmod | grep vpnhide'"

# kretprobes registered?
adb shell "su -c 'dmesg | grep vpnhide'"

# UIDs loaded?
adb shell "su -c 'cat /proc/vpnhide_targets'"

Pick target apps via the WebUI in KernelSU-Next manager.


Preparing a standalone kernel source

If you used the shallow clone (non-Pixel path), you need to prepare the kernel source before building. If you used the Pixel kernel tree with extracted headers, skip this section entirely.

Pull .config from the device

adb shell "su -c 'gzip -d < /proc/config.gz'" > ~/kernel-source/.config

If /proc/config.gz doesn't exist, use the GKI defconfig:

cd ~/kernel-source
make ARCH=arm64 LLVM=1 CC=$CLANG_DIR/clang gki_defconfig

Generate Module.symvers from device .ko files

The Module.symvers file contains CRC checksums for every exported kernel symbol. These must match the running kernel exactly (CONFIG_MODVERSIONS):

# Pull all vendor modules from the device
mkdir -p /tmp/device-modules
adb shell "su -c 'ls /vendor/lib/modules/*.ko'" | tr -d '\r' | while read ko; do
    adb shell "su -c 'cat $ko'" > "/tmp/device-modules/$(basename $ko)"
done

# Extract CRCs from all modules and build Module.symvers
for ko in /tmp/device-modules/*.ko; do
    modprobe --dump-modversions "$ko" 2>/dev/null
done | sort -u -k2 | \
    awk '{printf "%s\t%s\tvmlinux\tEXPORT_SYMBOL\t\n", $1, $2}' \
    > ~/kernel-source/Module.symvers

echo "Generated Module.symvers with $(wc -l < ~/kernel-source/Module.symvers) symbols"

Expect 3000-5000 symbols. If you get 0, check that modprobe is installed (apt install kmod or pacman -S kmod).

Alternative: if the device's ROM has a -kernels repo on GitHub (e.g. crdroidandroid/android_device_google_shusky-kernels), you can download the .ko files from there instead of pulling from device.

Prepare headers

cd ~/kernel-source

# Create empty ABI symbol list (GKI build expects it)
touch abi_symbollist.raw

# Generate headers
make ARCH=arm64 LLVM=1 LLVM_IAS=1 \
    CC=$CLANG_DIR/clang LD=$CLANG_DIR/ld.lld AR=$CLANG_DIR/llvm-ar \
    NM=$CLANG_DIR/llvm-nm OBJCOPY=$CLANG_DIR/llvm-objcopy \
    OBJDUMP=$CLANG_DIR/llvm-objdump STRIP=$CLANG_DIR/llvm-strip \
    CROSS_COMPILE=aarch64-linux-gnu- \
    olddefconfig prepare

Common issue: make prepare may fail on tools/bpf/resolve_btfids due to host clang version mismatch. This is fine — the module can still build without BTF. Ignore this error.

Set UTS_RELEASE (vermagic)

KernelSU bypasses vermagic checks, so any value works if using KSU. For Magisk or manual insmod, the vermagic must match uname -r.

cd ~/kernel-source

# For KernelSU users (any placeholder works):
echo '#define UTS_RELEASE "6.1.0-vpnhide"' > include/generated/utsrelease.h
echo -n "6.1.0-vpnhide" > include/config/kernel.release

# For Magisk users (must match exactly):
KVER="$(adb shell uname -r | tr -d '\r')"
echo "#define UTS_RELEASE \"$KVER\"" > include/generated/utsrelease.h
echo -n "$KVER" > include/config/kernel.release

Then set KERNEL_SRC=~/kernel-source in your .env and proceed to step 4.


Troubleshooting

insmod: Exec format error

  • Vermagic mismatch (Magisk doesn't bypass it). Set UTS_RELEASE to exact uname -r value.
  • Or Module.symvers CRCs don't match — re-extract from device .ko files.

insmod: File exists

  • Module already loaded. rmmod vpnhide_kmod first.

modprobe --dump-modversions: no output

  • .ko files might be stripped. Try pulling from a different path (/vendor/lib/modules/, /system/lib/modules/, /lib/modules/$(uname -r)/).

make prepare fails on resolve_btfids

  • Ignore — BTF is optional. The module builds without it.

No /proc/config.gz

  • Use make gki_defconfig instead.

kretprobe not firing (ioctl not filtered)

  • Check dmesg | grep vpnhide for registration messages.
  • Check /proc/vpnhide_targets has the right UIDs.
  • The target app's UID changes on reinstall — re-resolve via WebUI.