Hardening a rooted Android device against app detection
June 10, 2026 · 10 min read

What you can and can't hide

The full map of how a non-privileged app detects a rooted custom ROM, what closes each channel, and the two walls that nothing in userspace will move.

androiddetectionsynthesisrasp
Cover illustration for “What you can and can't hide”

After weeks of engineering against detection channels, I synthesized the final map of how an app probes a rooted custom ROM, and at what layer the countermeasure must exist. A fix at the wrong layer is fatal against RASP.

ChannelProbeCountermeasureLayer
Package listgetInstalledPackagesHideMyApplistsystem_server
System featureshasSystemFeatureStockMasksystem_server
Custom permissionspermission enumStockMasksystem_server
Build identityBuild.FINGERPRINTresetprop / PIFproperty layer
Boot statero.boot.verifiedbootstateresetpropproperty layer
Root binariesFile.exists("/system/bin/su")Magiskimage/mount layer
Process listenumerate /proc/<pid>hidepid=invisiblekernel
In-process hooks/proc/self/mapsShamiko denylistinjection layer
Magisk mounts/proc/self/mountinfoShamikoper-app namespace
Global /proccmdlinebind-mountglobal /proc
Custom servicesgetServicedeny ... findSELinux
Device logsREAD_LOGSrevoke + StockMasklogd/SELinux
Isolated /proccmdline inside namespaceNonekernel
AttestationsetAttestationChallengeNoneTEE

I adhered strictly to filtering by caller instead of injecting into apps. The strongest covers lived in system_server, rewriting responses based on caller UID. My app processes remained absolutely pristine. Consistency beat spoofing: a single mismatched partition fingerprint triggered RASP alerts instantly.

But I hit two immovable walls that userspace simply cannot touch:

+--------------------------------+       +--------------------------------+
|  Shamiko-isolated /proc        |       |  Hardware key attestation      |
+--------------------------------+       +--------------------------------+
| Isolation hides magisk mounts  |       | The TEE reports the real boot- |
| but restores the real cmdline. |       | loader state. Strict backends  |
| A module can't reach it; only  |       | reject forgeries.              |
| a boot-image/kernel edit does. |       | Only stock passes.             |
+--------------------------------+       +--------------------------------+

First, Shamiko’s mount-namespace isolation gives the app a clean view, stripping Magisk bind-mounts. But doing so restores the genuine /proc/self/cmdline and /proc/version. Since my Zygisk module didn’t inject into the isolated app, there was no code present to rewrite those files.

Second, the Android Key Attestation (Google, 2024) evaluates hardware reality. The TEE records the boot state natively and signs it via a key userspace cannot read. A forged chain can satisfy local checks, but server-side validation against the hardware root fails instantly. A userspace module is powerless against silicon math.

Comments