跪拜 Guibai
← All articles
Android · App · Security

Android Dual-App Detection: UID Contradictions, Hardening Pitfalls, and a Risk-Report Strategy

By Gemini001 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Client-side clone detection is never a final verdict; pairing UID and path checks with server-side risk scoring gives Android apps a workable anti-fraud signal, and avoiding ClassLoader and proxy checks under commercial hardening prevents legitimate users from being locked out.

Summary

Non-root clone tools such as VirtualApp, Parallel Space, and Dual Space work as user-space virtual sandboxes. They load the target APK into the host process with DexClassLoader, declare stub components to get a legitimate process entry, and hook both Java framework services and native libc calls to redirect data I/O. That architecture leaves detectable contradictions: the process UID no longer matches the package's registered UID, /proc/self/maps shows injected hook libraries, and the canonical filesystem path contains a 'virtual' marker.

The article walks through which detection methods survive commercial hardening. UID comparison and PendingIntent creator checks remain reliable because hardening shells don't touch those identity chains. But system-service proxy detection and ClassLoader chain inspection produce massive false positives when a hardening shell like iJiMi is present, since the shell itself proxies system services and swaps in its own ClassLoader. The practical recommendation is to keep identity-based checks, drop the fragile ones, and treat client hits as risk signals for server-side scoring instead of immediate crash-and-block.

Takeaways
— UID equality between Process.myUid() and PackageManager's recorded uid is a reliable clone indicator; to bypass Java hooks, read getuid() natively or parse /proc/self/status.
— A PendingIntent created by the app carries a creator UID; when that UID differs from the process UID, a clone container is likely (API 24+).
— /proc/self/maps can reveal hook libraries like libsandhook, libva, libepic, blackbox, and /virtual/data/ paths, but hardening shells also inject native libraries, so use a whitelist.
— Canonical path of getFilesDir() containing 'virtual' flags IO redirection; don't exclude /data/user/xx because system work profiles use non-zero user IDs.
— System-service proxy detection (checking for Java Proxy on PackageManager and ActivityManager) and ClassLoader chain checks are almost useless under commercial hardening; they should be logged only, never used for blocking.
— For hardened apps, the recommended approach is to use the vendor's enterprise anti-clone toggle that runs before app code decrypts, or, in self-built detection, keep UID, PendingIntent, and path checks, run after shell initialization, and cache PendingIntent results to avoid Binder IPC overhead.
— A positive detection should be reported as a risk label to the backend, not cause a crash; block sensitive operations and issue a warning.
Conclusions

The article's insistence on treating client checks as risk signals rather than evidence mirrors the reality that containers can hook almost every Java API, so the only robust signals are those that cross system layers (native UID, Binder token metadata).

Commercial hardening and clone containers share similar low-level techniques (DEX loading, native hooks), which is why naive detection methods collide: the same symptom (proxy classes, custom ClassLoader) indicates both security protections and clone frameworks.

The recommendation to use the vendor's enterprise anti-clone toggle reveals a division of labor: hardening vendors have already solved the compatibility problem, but that convenience comes with a dependency on a specific commercial vendor.

Concepts & terms
UID (Linux user ID) on Android
Each installed app gets a unique numeric ID assigned by the system at install time; used for sandboxing and permission checks. Comparing the runtime UID with the package's registered UID can expose cloned environments.
DexClassLoader
A class loader that loads APK or DEX files from a physical path at runtime; clone containers use it to load the target app's bytecode into the host process without installing it.
Stub components
Pre-declared, empty Activity/Service components in the host APK that the system treats as normal process entries; the container replaces the stub with the real app's components after launch.
IO redirection
Hooking native libc functions like open/openat/stat/access to remap the app's private data directory to a virtual path inside the host's own private storage.
System service proxy
Java dynamic proxies that replace the real Binder interfaces for ActivityManager and PackageManager, allowing the container to fake responses like package name and UID.
Key Attestation
An Android feature that provides hardware-backed proof of an app's identity to a server; mentioned as an advanced supplement for clone detection, though device compatibility varies in China.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗