mirror of
https://github.com/okhsunrog/vpnhide.git
synced 2026-05-05 18:53:49 +00:00
Root README is now the single source for shared content (verified against, split tunneling, threat model, component overview). Sub-READMEs focus on component-specific technical details and link back to root. - Remove ~700 lines of duplicated content across sub-READMEs - Update all cross-references to use monorepo relative paths - Add test-app to components table - Update zygisk README: mark openat/recvmsg/SIOCGIFCONF as implemented - Fix stale links to archived repos
189 lines
7 KiB
Markdown
189 lines
7 KiB
Markdown
# vpnhide -- LSPosed module
|
|
|
|
Hooks Java network APIs to hide VPN presence from selected apps. Part of [vpnhide](../README.md).
|
|
|
|
## Modes
|
|
|
|
1. **App-process mode** (default) -- hooks installed directly in target app processes via LSPosed/Xposed. More complete coverage (all APIs below) but visible to anti-tamper SDKs that scan for Xposed artifacts.
|
|
2. **system_server mode** -- hooks `writeToParcel()` in `system_server` so VPN data is stripped before Binder serialization. Zero presence in the app process. Use with [kmod](../kmod/) for native coverage.
|
|
|
|
---
|
|
|
|
## What it hooks (app-process mode)
|
|
|
|
### 1. `android.net.NetworkCapabilities`
|
|
| Method | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `hasTransport(TRANSPORT_VPN)` | always returns `false` |
|
|
| `hasCapability(NET_CAPABILITY_NOT_VPN)` | always returns `true` |
|
|
| `getTransportTypes()` | `TRANSPORT_VPN` stripped from the returned `int[]` |
|
|
| `getTransportInfo()` | returns `null` whenever the real value is `VpnTransportInfo` |
|
|
| `toString()` | post-processed: `\|VPN` stripped from `Transports:`, `VpnTransportInfo{...}` replaced with `null`, stray `IS_VPN` flags dropped from `&`-joined lists. Uses string manipulation (not regex) to avoid `PatternSyntaxException` on edge cases. |
|
|
|
|
### 2. `android.net.NetworkInfo`
|
|
| Method | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `getType()` | returns `TYPE_WIFI` instead of `TYPE_VPN` |
|
|
| `getTypeName()` | returns `"WIFI"` instead of `"VPN"` |
|
|
| `getSubtypeName()` | any string containing "VPN" becomes empty |
|
|
|
|
### 3. `android.net.ConnectivityManager`
|
|
| Method | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `getAllNetworks()` | VPN networks removed from the returned array |
|
|
| `getActiveNetwork()` | if the active one is a VPN, substitute the first non-VPN network |
|
|
| `getActiveNetworkInfo()` | if VPN, substitute the first non-VPN `NetworkInfo` |
|
|
| `getAllNetworkInfo()` | VPN entries removed |
|
|
| `getNetworkInfo(int type)` | returns `null` when asked about `TYPE_VPN` |
|
|
| `getNetworkInfo(Network)` | returns `null` if the result would be a VPN |
|
|
|
|
### 4. `android.net.LinkProperties`
|
|
| Method | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `getInterfaceName()` | rewrites VPN interface names (`tun0`, `ppp0`, `wg0`, etc.) to `"wlan0"` |
|
|
| `getRoutes()` | routes whose interface is a VPN tunnel are dropped |
|
|
| `getDnsServers()` | returns empty list for VPN LinkProperties |
|
|
| `getHttpProxy()` | returns `null` for VPN LinkProperties |
|
|
|
|
### 5. `java.net.NetworkInterface`
|
|
| Method | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `getNetworkInterfaces()` | VPN tunnel interfaces removed from the enumeration |
|
|
| `getByName(name)` | returns `null` for VPN-like names |
|
|
| `getByIndex(int)` | returns `null` if the looked-up interface is a VPN tunnel |
|
|
| `getByInetAddress(addr)` | returns `null` if the matched interface is a VPN tunnel |
|
|
|
|
### 6. `/proc/net/*` file reads
|
|
`FileInputStream` and `FileReader` constructors (both `String` and `File` variants) are hooked. Opens to the following paths are redirected to `/dev/null`:
|
|
|
|
```
|
|
/proc/net/route
|
|
/proc/net/ipv6_route
|
|
/proc/net/if_inet6
|
|
/proc/net/tcp
|
|
/proc/net/tcp6
|
|
/proc/net/udp
|
|
/proc/net/udp6
|
|
/proc/net/dev
|
|
/proc/net/arp
|
|
/proc/net/route_cache
|
|
/proc/net/rt_cache
|
|
/proc/net/fib_trie*
|
|
/proc/net/fib_triestat*
|
|
/proc/net/xfrm_stat*
|
|
```
|
|
|
|
### 7. `System.getProperty`
|
|
|
|
Proxy-related system properties that can leak VPN presence:
|
|
|
|
| Key | Behaviour with VpnHide |
|
|
|---|---|
|
|
| `http.proxyHost` | returns `null` |
|
|
| `http.proxyPort` | returns `null` |
|
|
| `https.proxyHost` | returns `null` |
|
|
| `https.proxyPort` | returns `null` |
|
|
| `socksProxyHost` | returns `null` |
|
|
| `socksProxyPort` | returns `null` |
|
|
|
|
### VPN interface name prefixes
|
|
|
|
`tun`, `ppp`, `tap`, `wg`, `ipsec`, `xfrm`, `utun`, `l2tp`, `gre`, plus anything whose name contains the substring `vpn` (case-insensitive).
|
|
|
|
---
|
|
|
|
## system_server mode
|
|
|
|
### When to use
|
|
|
|
For apps with aggressive anti-tamper SDKs that detect app-process hooks (crashes, NFC payment degradation, etc.). The default app-process hooks cannot be used for these apps.
|
|
|
|
### How to enable
|
|
|
|
1. In LSPosed/Vector manager, add **"System Framework"** to this module's scope.
|
|
2. Reboot so the module loads into `system_server`.
|
|
3. Install [vpnhide-kmod](../kmod/) for native-side coverage.
|
|
4. Manage targets via kmod's WebUI, which writes UIDs to `/data/system/vpnhide_uids.txt`.
|
|
|
|
### What it hooks
|
|
|
|
`writeToParcel()` on `NetworkCapabilities`, `NetworkInfo`, and `LinkProperties`. Uses a ThreadLocal save/restore pattern so the original values are preserved for non-target callers. Per-UID filtering via `Binder.getCallingUid()` ensures only target apps see the filtered view.
|
|
|
|
### Target management
|
|
|
|
A `FileObserver` (inotify) watches `/data/system/` and reloads the UID list immediately when the file changes. No reboot needed.
|
|
|
|
**Important:** apps with aggressive anti-tamper SDKs must NOT be added to this module's LSPosed app-process scope. Only "System Framework" should be in scope for these apps.
|
|
|
|
---
|
|
|
|
## 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 target apps to the module's scope and **force-stop** them (or reboot).
|
|
5. Open the **VPN Hide** launcher icon. Tick the apps you want VPN hidden from.
|
|
6. Force-stop the target app again so it re-reads prefs on next launch.
|
|
|
|
### Double-gate logic
|
|
|
|
Two conditions must both be true for hooks to run inside an app:
|
|
|
|
1. The app is in the module's scope in LSPosed manager.
|
|
2. The app is checked in VPN Hide's picker UI.
|
|
|
|
Gate (1) controls which processes LSPosed loads the module into (performance). Gate (2) is the per-app allowlist read at hook time via `XSharedPreferences`.
|
|
|
|
---
|
|
|
|
## What it does NOT cover
|
|
|
|
- **Native code path** -- apps checking VPN from C/C++/JNI/Flutter bypass all Java hooks. Use [zygisk](../zygisk/) or [kmod](../kmod/).
|
|
- **Server-side detection** -- DNS leakage, IP blocklists, latency/TLS fingerprinting. Unfixable client-side; use split tunneling.
|
|
- **`Runtime.exec` / `ProcessBuilder` shell-outs** -- e.g. `cat /proc/net/route` in a subprocess.
|
|
- **`NetworkCallback` event counting** -- inferring VPN from `onAvailable()` call count.
|
|
- **`VpnService.prepare()`** -- not currently hooked.
|
|
|
|
See [the project README](../README.md) for the full threat model and split-tunnel requirements.
|
|
|
|
---
|
|
|
|
## Debugging
|
|
|
|
```bash
|
|
adb logcat | grep VpnHide
|
|
```
|
|
|
|
On target app startup you should see:
|
|
|
|
```
|
|
VpnHide: installing hooks for com.example.targetapp
|
|
```
|
|
|
|
Any hook that fails to install is logged with the hook category and exception message.
|
|
|
|
If hooks installed but the app still detects VPN:
|
|
|
|
```bash
|
|
adb logcat -c
|
|
# trigger the app's VPN check, then:
|
|
adb logcat -d > /tmp/detect.log
|
|
grep -iE "tun0|ppp0|wg0|vpn|TRANSPORT_VPN|NetworkInterface|/proc/net" /tmp/detect.log
|
|
```
|
|
|
|
---
|
|
|
|
## Build
|
|
|
|
```bash
|
|
./gradlew assembleDebug
|
|
```
|
|
|
|
Requires JDK 17. Output: `app/build/outputs/apk/debug/app-debug.apk`.
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
Personal / educational project. No explicit license -- do whatever you want with it but don't hold me responsible if a target app updates its detection logic and breaks things.
|