vpnhide/lsposed/README.md
okhsunrog 8025be14cc docs: align README and dev guide with current code and CI
Six unrelated drift fixes that accumulated since they were last
synced. Each is independent of the rest:

* README{.en,}.md — kmod claim "filters /proc/net/*" trimmed to
  /proc/net/route. The other /proc/net files are SELinux-blocked
  for untrusted apps and the coverage table already says so.

* kmod/README.md — hook table and architecture note updated from
  dev_ifconf to sock_ioctl. dev_ifconf gets inlined by Clang LTO
  on GKI 5.10 so the kretprobe silently never fires; sock_ioctl
  has been the actual hook target since the vpnhide_kmod.c fix.

* zygisk/README.md — five inline hooks now, not four (recv was
  added separately because bionic's recv tail-calls recvfrom).
  Also clarified pre_app_specialize runs in the forked child, not
  zygote, matching the lifecycle block in lib.rs.

* docs/development.md — JDK requirement matches CI image (17, not
  21); document ANDROID_NDK_ROOT quirk for Gobley; CI lint list
  expanded to match what ci.yml actually runs.

* docs/development.md + lsposed/README.md — explain Gobley (the
  Gradle plugin pair that builds lsposed/native/ and bundles the
  .so + UniFFI Kotlin bindings into the APK). Previously absent
  from all *.md.
2026-04-26 15:32:30 +03:00

3.7 KiB

vpnhide -- LSPosed module + target picker app

Hooks writeToParcel() in system_server to strip VPN data before Binder serialization reaches target apps. Part of vpnhide.

The APK also serves as the target management UI for the entire vpnhide project — it writes targets for both kmod and zygisk modules.

Zero presence in the target app's process -- only "System Framework" is needed in the LSPosed scope.

What it hooks

writeToParcel() on three classes inside system_server:

Class Effect
NetworkCapabilities VPN transport and capability flags stripped before serialization. Covers hasTransport(VPN), getAllNetworks() + VPN scan, getTransportInfo().
NetworkInfo VPN type rewritten to WIFI before serialization
LinkProperties VPN interface name and routes stripped before serialization

Uses a ThreadLocal save/restore pattern so the original values are preserved for non-target callers.

Per-UID filtering

Filtering is controlled by Binder.getCallingUid() -- only apps whose UID appears in the target list see the filtered view. System services, VPN clients, and everything else see real data.

Target management

Target UIDs are loaded from /data/system/vpnhide_uids.txt. A FileObserver (inotify) watches for changes and reloads the list immediately -- no reboot needed.

This file is written by:

  • The VPN Hide app (this APK's target picker UI)
  • The module's service.sh on boot

Target picker app

The APK includes a Compose UI for managing target apps across all vpnhide modules:

  • Lists all installed apps with icons, names, and package names
  • Text search filter
  • System apps toggle (selected system apps always visible)
  • Save writes to all target locations via su:
    • /data/adb/vpnhide_kmod/targets.txt (if kmod is installed)
    • /data/adb/vpnhide_zygisk/targets.txt (if zygisk is installed)
    • /data/adb/modules/vpnhide_zygisk/targets.txt (Magisk module dir copy)
    • /proc/vpnhide_targets (kmod live update, no reboot needed)
    • /data/system/vpnhide_uids.txt (system_server hooks, live reload via inotify)

Works on KernelSU, Magisk, and any other root solution.

Install

  1. Build the APK (./gradlew assembleDebug).
  2. Install: adb install app/build/outputs/apk/debug/app-debug.apk.
  3. Open LSPosed/Vector manager, go to Modules, enable VPN Hide.
  4. Add "System Framework" to the module's scope. No other apps should be in scope.
  5. Reboot.
  6. Open the VPN Hide app to manage target apps.

Combined use with kmod

For apps with aggressive anti-tamper SDKs, full VPN hiding requires covering both native and Java API paths without any hooks in the target app's process:

  • kmod covers native: ioctl, getifaddrs (netlink), /proc/net/route.
  • This module covers Java APIs: NetworkCapabilities, NetworkInfo, LinkProperties via writeToParcel() in system_server.

Together they provide complete VPN hiding with zero footprint in the target process.

Debugging

adb logcat | grep VpnHide

Build

./gradlew assembleDebug

Requires JDK 17 or later. Output: app/build/outputs/apk/debug/app-debug.apk.

The build cross-compiles lsposed/native/ (Rust crate) for aarch64-linux-android via cargo-ndk and bundles the resulting libvpnhide_checks.so into the APK's jniLibs/, plus auto-generated UniFFI Kotlin bindings under package dev.okhsunrog.vpnhide.checks. Both steps are driven by Gobley Gradle plugins (dev.gobley.cargo + dev.gobley.uniffi) — no manual cargo invocation needed. See ../docs/development.md for the full prereq list.

License

MIT. See LICENSE.