- Add Google's AOSP clang (clang-r487747c, same as Pixel kernel build) to the CI Docker image via sparse checkout. Distro clang caused ABI mismatches leading to bootloops on device. - Update kmod workflow to use the Docker image + AOSP clang instead of system clang from apt. - Replace symvers with real vmlinux.symvers from Pixel kernel build (8050 symbols vs 4060 from device .ko extraction). - Add kmod build deps (bc, kmod, cpio, binutils-aarch64) to Docker image. |
||
|---|---|---|
| .github | ||
| kmod | ||
| lsposed | ||
| test-app | ||
| zygisk | ||
| .gitignore | ||
| .gitmodules | ||
| README.md | ||
vpnhide
Hide an active Android VPN connection from selected apps.
Three components work together to cover all detection vectors -- from Java APIs down to kernel syscalls. A diagnostic test app verifies everything works.
Components
| Directory | What | How |
|---|---|---|
| kmod/ | Kernel module (C) | kretprobe hooks in kernel space. Zero footprint in the target app's process -- invisible to any userspace anti-tamper SDK. |
| lsposed/ | LSPosed module (Kotlin) | Hooks writeToParcel in system_server for per-UID Binder filtering. Only "System Framework" in LSPosed scope -- no in-process hooks. |
| zygisk/ | Zygisk module (Rust) | Inline-hooks libc.so in the target app's process. Alternative to kmod for users who can't install a kernel module. |
| test-app/ | Diagnostic app (Kotlin + C++) | 22 checks covering all detection vectors. Logs to logcat under tag VPNHideTest. |
Detection coverage
| # | Detection vector | SELinux | kmod | zygisk | lsposed |
|---|---|---|---|---|---|
| 1 | ioctl(SIOCGIFFLAGS) on tun0 |
x | x | ||
| 2 | ioctl(SIOCGIFNAME) resolve index to name |
x | x | ||
| 3 | ioctl(SIOCGIFCONF) interface enumeration |
x | x | ||
| 4 | getifaddrs() (uses netlink internally) |
x | x | ||
| 5 | netlink RTM_GETLINK dump |
blocked | x | x | |
| 6 | netlink RTM_GETADDR dump (IPv4 + IPv6) |
blocked | x | ||
| 7 | netlink RTM_GETROUTE dump |
blocked | |||
| 8 | /proc/net/route |
blocked | x | x | |
| 9 | /proc/net/ipv6_route |
blocked | x | ||
| 10 | /proc/net/if_inet6 |
blocked | x | ||
| 11 | /proc/net/tcp, tcp6 |
blocked | |||
| 12 | /proc/net/udp, udp6 |
blocked | |||
| 13 | /proc/net/dev |
blocked | |||
| 14 | /proc/net/fib_trie |
blocked | |||
| 15 | /sys/class/net/tun0/ |
blocked | |||
| 16 | NetworkCapabilities (hasTransport, NOT_VPN, transportInfo) |
x | |||
| 17 | NetworkInfo (getType, getTypeName) |
x | |||
| 18 | ConnectivityManager (activeNetwork, allNetworks) |
x | |||
| 19 | LinkProperties (interfaceName, routes, DNS) |
x | |||
| 20 | NetworkInterface.getNetworkInterfaces() |
x | x | ||
| 21 | System.getProperty (proxy settings) |
x | |||
| 22 | /proc/net/route via Java FileInputStream |
blocked | x | x |
blocked = SELinux denies access for untrusted apps (Android 10+). No hook needed.
x = actively filtered by this component.
Rows 1-4 and 20 are the only vectors reachable by regular apps. Everything else is either blocked by SELinux or goes through Java APIs (covered by lsposed).
Which modules do I need?
- Apps with aggressive anti-tamper SDKs (banking, government):
kmod+lsposed. Zero in-process footprint -- undetectable by integrity checks. - Other apps:
zygisk+lsposed. Simpler to install (no kernel module), covers all reachable vectors. - To verify your setup: install
test-app, add it to target lists, run with VPN active -- all checks should pass.
Configuration
Both kmod and zygisk modules have a WebUI (KernelSU/Magisk manager -> module settings) to select target apps. On save, the WebUI writes to:
targets.txt-- persistent package names (survives module updates)/proc/vpnhide_targets-- resolved UIDs for the kernel module (kmod only)/data/system/vpnhide_uids.txt-- resolved UIDs for the lsposed system_server hooks
All changes apply immediately -- no reboot needed.
Building
- kmod:
cd kmod && make && ./build-zip.sh(kernel source + clang, see kmod/BUILDING.md) - zygisk:
cd zygisk && ./build-zip.sh(Rust + NDK + cargo-ndk) - lsposed:
cd lsposed && ./gradlew assembleDebug(JDK 17) - test-app:
cd test-app && ./gradlew installDebug(JDK 17 + NDK)
Verified against
- RKNHardering -- all detection vectors clean
- YourVPNDead -- all detection vectors clean
Both implement the official Russian Ministry of Digital Development VPN/proxy detection methodology (source).
Split tunneling
Works correctly with split-tunnel VPN configurations. Only the apps in the target list are affected -- all other apps see normal VPN state.
Note: detection apps that compare device-reported public IP against external checkers require split tunneling -- the detection app's HTTPS requests must exit through the carrier, not the tunnel.
Threat model
vpnhide is designed for one scenario: "I have a VPN running and certain apps refuse to work because they detect it. I want those specific apps to think the VPN isn't there."
It is NOT designed for:
- Hiding root or custom ROM presence
- Bypassing Play Integrity
- Fooling server-side detection (DNS leakage, IP blocklists, latency fingerprinting, TLS fingerprinting)
Known limitations
kmodrequires a GKI kernel withCONFIG_KPROBES=y(standard on Pixel 6-9a withandroid14-6.1)lsposedrequires LSPosed or a compatible Xposed frameworkzygiskis arm64 only- Direct
svc #0syscalls bypass zygisk's libc hooks (that's what kmod is for) - Server-side detection is unfixable client-side -- use split tunneling
License
- zygisk: 0BSD
- lsposed: unlicensed (do whatever you want)
- kmod: GPL-2.0 (required for kernel modules)