vpnhide/README.md

5.3 KiB

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

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

  • kmod requires a GKI kernel with CONFIG_KPROBES=y (standard on Pixel 6-9a with android14-6.1)
  • lsposed requires LSPosed or a compatible Xposed framework
  • zygisk is arm64 only
  • Direct svc #0 syscalls 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)