15 KiB
Persistent state - every path the project touches
Reference catalogue for answering "where is this stored?", "who reads X?", and "what survives a reboot?". The storage source of truth is now the canonical JSON config; native runtime channels still use the v1 text wire from protocol.md.
For each entry: format, writer, reader, lifetime, and permissions when relevant.
SELinux contexts shown here are AOSP/default-root-manager expectations. Custom ROMs may relabel paths; if a write fails, check
dmesg | grep avc.
1. Canonical Config
/data/system/vpnhide_config.json
The single managed desired-state file.
- Format: JSON object,
version: 1,debug: Boolean,apps: { package -> roles },settings.rememberSuperkey: Boolean,settings.autoHideVpnServices: Boolean,settings.autoHideVpnName: Boolean,settings.autoHiddenPackages: [package]. - Roles per package:
java,native(Booleanor hook-name array),appHiding,ports, and the app-owned extensionhidden. - Writer: VPN Hide app via
su(StorageConfig.kt) on Save, startup migration/self-target preparation, debug toggle, and APatch SuperKey setting. - Readers:
- Rust activator bins (
crates/activator) for native and ports backends. - LSPosed hooks in
system_server(SystemServerConfigCache) directly. - App UI caches (
TargetsCache,DashboardData).
- Rust activator bins (
- Permissions:
0640 root:system, SELinuxsystem_data_file. - Lifetime: persistent across reboot, module reinstall, and app reinstall.
The app writes it atomically via temp-file + mv. If it is absent, native
activators treat it as an empty config; the app folds legacy inputs into it on
startup.
Legacy Migration Inputs
These files are no longer created or written by current code. If present on a
device without vpnhide_config.json, the app reads them once and folds their
state into the canonical JSON:
/data/adb/vpnhide_kmod/targets.txt/data/adb/vpnhide_kpm/targets.txt/data/adb/vpnhide_zygisk/targets.txt/data/adb/vpnhide_lsposed/targets.txt/data/adb/vpnhide_ports/observers.txt/data/system/vpnhide_hidden_pkgs.txt/data/system/vpnhide_observer_uids.txt
After canonical JSON exists, app startup removes these retired inputs best-effort. The LSPosed hooks do not read them anymore. These read paths are temporary migration shims; remove them after a few public releases once upgraded devices have had a chance to fold old files into canonical JSON.
2. Module Install Dirs
Module dirs under /data/adb/modules/ are replaced by Magisk/KSU/APatch on
module reinstall. They hold binaries and boot scripts, not user-managed config.
/data/adb/modules/vpnhide_kmod/
module.prop: module metadata, version, and stampedgkiVariant=.post-fs-data.sh: loadsvpnhide_kmod.ko, writes load diagnostics.service.sh: startsactivator --boot-waitin the background.uninstall.sh: removes kmod-specific persistent diagnostics and legacy targets under/data/adb/vpnhide_kmod/.activator: Rust bin that reads canonical JSON, resolves packages viapm list packages -U --user all, formats text wire, waits for/proc/vpnhide_ctlin boot mode, and writes/proc/vpnhide_ctl.vpnhide_kmod.ko: kernel module binary.
/data/adb/modules/vpnhide_kpm/
module.prop: module metadata.post-fs-data.sh: loads KPM automatically on keyless KPatch-Next; on APatch/FolkPatch records a deferred status and leaves load/config to service.service.sh: startsactivator --boot-waitin the background.uninstall.sh: removes KPM-specific persistent status, the ctl0 lock, and legacy targets under/data/adb/vpnhide_kpm/.activator: Rust bin that refuses to run when the.kobackend is present, reads optional/data/adb/vpnhide/superkey, loads/configures KPM through APatch/FolkPatch direct supercalls or KPatch-Nextkpatch kpm load+kpatch kpm ctl0(including the standalone KPatch-Next-Module CLI path).vpnhide.kpm: KernelPatch module binary.
/data/adb/modules/vpnhide_zygisk/
module.prop: module metadata.customize.sh: install hook; only preserves an old in-moduletargets.txtinto the legacy migration path if needed.service.sh: startsactivator --boot-waitin the background.uninstall.sh: removes Zygisk-specific legacy migration state under/data/adb/vpnhide_zygisk/.activator: Rust bin that writes the Zygisk runtime config.zygisk/arm64-v8a.so: Rust cdylib injected into app processes.targets.txt: runtimevpnhide 1 configtext snapshot read through Zygisk's module-dir fd. This is not persistent user config; it is regenerated by the activator from canonical JSON.
/data/adb/modules/vpnhide_ports/
module.prop: module metadata.service.sh: background-waits for netd baseline iptables, then runsactivator --boot-wait, and repeats once after 30 seconds.activator: Rust bin that reads canonical JSON, derivesports: truepackages, resolves UIDs, applies iptables rules, and records the latest apply status under/data/adb/vpnhide_ports/.uninstall.sh: removesvpnhide_out,vpnhide_out6, legacy/data/adb/vpnhide_ports/observers.txt, and portshide diagnostics.
3. Persistent /data/adb State
/data/adb/vpnhide_kmod/
| File | Format | Writer | Reader | Lifetime |
|---|---|---|---|---|
load_status |
key=value: timestamp, boot_id, uname_r, gki_variant, kmod_version, root_manager, kprobes, kretprobes, insmod_exit, loaded, insmod_stderr |
kmod/module/post-fs-data.sh |
app dashboard | overwritten each boot |
load_dmesg |
filtered dmesg excerpt | kmod/module/post-fs-data.sh |
app dashboard/debug export | overwritten each boot |
/data/adb/vpnhide_kpm/
| File | Format | Writer | Reader | Lifetime |
|---|---|---|---|---|
load_status |
key=value: timestamp, uname_r, runtime, loaded, detail |
kmod/kpm/module/post-fs-data.sh |
app dashboard | overwritten each boot |
ctl.lock |
empty advisory-lock inode, mode 0600 |
KPM activator | KPM activators serialize ctl0 config/status/stats calls with flock |
while the module is installed; removed on uninstall |
/data/adb/vpnhide_ports/
| File | Format | Writer | Reader | Lifetime |
|---|---|---|---|---|
load_status |
key=value: timestamp, boot_id, uname_r, runtime=ports, source, loaded, target_count, detail |
ports activator; portshide/module/service.sh when the activator is missing or crashes |
app dashboard/debug export | overwritten on every ports apply |
load_log |
stdout/stderr excerpt from the latest ports apply | ports activator; portshide/module/service.sh failure fallback |
debug export | overwritten on every ports apply |
/data/adb/vpnhide/superkey
- Format: APatch SuperKey as a plain text file with trailing newline.
- Writer: VPN Hide Settings when
settings.rememberSuperkeyis enabled. - Reader: KPM activator.
- Permissions:
0600 root:root. - Lifetime: persistent, root-only, direct-encrypted storage readable at boot.
The key is intentionally not stored inside the canonical JSON because that file
is exportable and readable by system_server.
4. system_server Files
/data/system/vpnhide_lsposed_state
- Format: protocol-shaped readback:
vpnhide 1 statusplus LSPosedmetarecords (version,boot_id,timestamp,aosp_sdk, optionalbroken_fields) andvpnhide 1 statssparse counters. - Writer: LSPosed hooks in
system_server. - Reader: app dashboard via root snapshot.
- Permissions: default root/system-server write behavior; app reads via
su. - Lifetime: per boot.
/data/system/vpnhide_hook_active is the retired status-only marker. App
startup removes it best-effort after canonical JSON migration.
The canonical config is also in /data/system, but it is covered in section 1
because it is the storage root for every layer.
5. Native Runtime Channels
/proc/vpnhide_ctl
Created by the .ko backend at module init. Mode 0600, root-only.
| Direction | Format | Writer/Reader |
|---|---|---|
| write | vpnhide 1 config text snapshot: debug + target <uid> <hookmask> |
kmod activator writes; kernel parses via shared protocol code |
| read | status + stats text response |
app/debug tooling reads |
The state is in-kernel and per-boot. The proc write replaces the whole config, so activator delivers one bounded snapshot.
KPM Supercall Channel
No file or proc node. The KPM activator sends the same vpnhide 1 config text
wire through the KernelPatch KPM ctl0 supercall. On APatch/FolkPatch it invokes
the supercall directly with the saved SuperKey when present, otherwise with the
runtime's trusted su token when that grant is available. On KPatch-Next it
uses kpatch kpm ctl0 from the runtime's own CLI. Status/stats use the same
ctl0 request/response channel.
Zygisk Module-Dir targets.txt
/data/adb/modules/vpnhide_zygisk/targets.txt is the runtime text wire consumed
through Zygisk's module-dir fd. It survives until module reinstall or the next
activator write, but it is derived state, not user storage.
6. LSPosed Runtime Resolution
The LSPosed hooks read canonical JSON directly in system_server and resolve
package names to appIds through /data/system/packages.list, never through
PackageManager. Caller matching uses callingUid % 100000, so all Android user
profiles for the same appId are covered. java: true enables every LSPosed VPN
sanitizer for that app; java: ["hook_name", ...] enables only the named
LSPosed Java hooks for the app. App-hiding package visibility is controlled by
appHiding, not by the Java VPN hook list.
Inotify watches /data/system/vpnhide_config.json; package reinstall UID/appId
changes are picked up by a periodic fingerprint of /data/system/packages.list.
7. iptables
Two chains in the filter/OUTPUT path:
vpnhide_outfor IPv4 loopback.vpnhide_out6for IPv6 loopback.
Writer: ports Rust activator through iptables-restore --noflush and
ip6tables-restore --noflush. The same activator writes
/data/adb/vpnhide_ports/load_status and load_log after each apply attempt.
Readers/checks: dashboard tests chain existence with iptables -L vpnhide_out -n.
Lifetime: in-kernel, per-boot. The ports service reapplies rules after reboot, and the app reruns the ports activator on Save.
8. App-Process State
SharedPreferences vpnhide_prefs
Accessed through context.getSharedPreferences("vpnhide_prefs", MODE_PRIVATE).
Keys currently in use:
last_seen_version: String: changelog dialog.help_collapsed_*: Boolean: help accordions.
debug / debugSwitch are not stored here anymore. Diagnostics and debug
capture state comes from canonical JSON (/data/system/vpnhide_config.json).
Vector LSPosed redirects this storage to:
/data/misc/<vector-uuid>/prefs/dev.okhsunrog.vpnhide/vpnhide_prefs.xml
Writing /data/data/dev.okhsunrog.vpnhide/shared_prefs/ by hand does not affect
the app when Vector is active.
filesDir
/data/user/0/dev.okhsunrog.vpnhide/files/vpnhide_zygisk_active
- Format:
key=value:version,boot_id,pid,timestamp. - Writer: Zygisk module when the current forked target process is the VPN Hide app. This is canonical/native self-targeting for the heartbeat, not LSPosed module scope.
- Reader: app root snapshot/dashboard/startup cleanup.
- Lifetime: per app launch, stale records removed when boot_id changes.
cacheDir
Used for temporary debug/export files and a copied read-only LSPosed config DB:
vpnhide_lspd_modules_config.db plus optional WAL/SHM sidecars. These are
deleted after use.
9. External Paths Read
| Path | Owner | Purpose |
|---|---|---|
/data/adb/lspd/config/modules_config.db |
LSPosed/Vector | Dashboard checks module enabled state and System Framework scope |
/data/misc/<vector-uuid>/prefs/<pkg>/ |
Vector LSPosed | Redirected SharedPreferences |
/data/system/packages.list |
Android system | system_server package -> appId resolution |
/proc/sys/kernel/random/boot_id |
kernel | Freshness checks for per-boot records |
/proc/version, /proc/modules, /proc/config.gz |
kernel | kmod diagnostics |
/proc/net/{route,ipv6_route,if_inet6,tcp,tcp6,udp,udp6,dev,fib_trie} |
kernel | diagnostics and native hiding vectors |
/sys/class/net/, /sys/class/net/<iface>/operstate |
kernel | VPN-active detection |
10. Boot-Time Sequence
post-fs-data:
kmod post-fs-data
-> insmod vpnhide_kmod.ko
-> write /data/adb/vpnhide_kmod/load_status and load_dmesg
KPM post-fs-data
-> refuse if .ko module is installed+enabled
-> keyless KPatch-Next: load vpnhide.kpm
-> APatch/FolkPatch: defer to service activator; service tries saved key,
then trusted su token, else writes awaiting_superkey
service:
kmod / KPM / Zygisk service.sh
-> start that module's activator with --boot-wait in the background
-> activator waits for PackageManager to expose dev.okhsunrog.vpnhide
-> kmod activator also waits for /proc/vpnhide_ctl
-> KPM activator uses saved APatch SuperKey or trusted su token when present
-> activator reads canonical JSON and writes exactly one native channel
ports service.sh
-> start background waiter
-> wait for netd baseline
-> run ports activator with --boot-wait
-> activator waits for PackageManager readiness and applies iptables from canonical JSON
system_server:
HookEntry.handleLoadPackage
-> install hooks
-> write /data/system/vpnhide_lsposed_state
-> watch canonical JSON
zygote app fork:
zygisk on_load
-> read module-dir targets.txt text wire
zygisk post_app_specialize
-> if target: install libc hooks
-> if target process is VPN Hide app: write filesDir/vpnhide_zygisk_active
11. Lifetime Cheat Sheet
| Lifetime | Examples |
|---|---|
| In-kernel per boot | /proc/vpnhide_ctl state, KPM in-kernel state, iptables chains |
| Per boot / last apply files | /data/adb/vpnhide_kmod/load_status, /data/adb/vpnhide_kmod/load_dmesg, /data/adb/vpnhide_kpm/load_status, /data/adb/vpnhide_ports/load_status, /data/adb/vpnhide_ports/load_log, /data/system/vpnhide_lsposed_state |
| Per app launch | filesDir/vpnhide_zygisk_active |
| Persistent root-managed | /data/system/vpnhide_config.json, /data/adb/vpnhide/superkey |
| Module-dir derived state | /data/adb/modules/vpnhide_zygisk/targets.txt |
| Removed on module uninstall | /data/adb/vpnhide_kmod/, /data/adb/vpnhide_kpm/, /data/adb/vpnhide_zygisk/, /data/adb/vpnhide_ports/ when empty after deleting module-specific files |
| Wiped on module reinstall | files under /data/adb/modules/vpnhide_*/ |
| Wiped on app reinstall | app SharedPreferences, except Vector redirects the physical path under /data/misc/<uuid>/prefs/ |