// HackTricks · Mobile

Android Application-Level Virtualization (App Cloning)

Android Application-Level Virtualization (App Cloning)

Application-level virtualization (app cloning/container frameworks such as DroidPlugin-style loaders) can run one or more APKs inside a host that controls lifecycle, class loading, storage, and permission mediation. In frameworks where guests execute under the host UID, Android’s normal per-package UID isolation is collapsed; this behavior must be verified for the specific container rather than assumed for every cloning product.[1][2]

Baseline install/launch vs virtualized execution

  • Normal install: Package Manager installs an APK under an implementation-specific randomized /data/app/ path, assigns an application UID, and Zygote normally forks a process that loads the app’s classes.dex code. Historical layouts commonly resembled /data/app/<random>/com.pkg-<random>/base.apk; do not hardcode that shape on newer releases. Apps explicitly configured to share an identity are an exception to the one-package/one-UID simplification.[2]
  • Dex load primitive: DexFile.openDexFile() delegates to openDexFileNative() using absolute paths; virtualization layers commonly hook/redirect this to load guest dex from host-controlled paths.[1]
  • Virtualized launch: Host starts a process under its UID, loads the guest’s base.apk/dex with a custom loader, and exposes lifecycle callbacks via Java proxies. Guest storage API calls are remapped to host-controlled paths.[1]

Abuse patterns

  • Permission escalation via shared UID: Guests run under the host UID and can inherit all host-granted permissions even if not declared in the guest manifest. Over-permissioned hosts (massive AndroidManifest.xml) become “permission umbrellas”.
  • Stealthy code loading: Host hooks openDexFileNative/class loaders to inject, replace, or instrument guest dex at runtime, bypassing static analysis.
  • Malicious host vs malicious guest:
    • Evil host: acts as dropper/executor, instruments/filters guest behavior, tampers with crashes.
    • Evil guest: abuses shared UID to reach other guests’ data, ptrace them, or leverage host permissions.[1]

Fingerprinting & detection

  • Multiple base.apk in one process: A container often maps several APKs in the same PID.
    adb shell "cat /proc/<pid>/maps | grep base.apk"
    # Suspicious: host base.apk + unrelated packages mapped together
  • Hooking/instrumentation artifacts: Search for known libs (e.g., Frida) in maps and confirm on disk.
    adb shell "cat /proc/<pid>/maps | grep frida"
    adb shell "file /data/app/..../lib/arm64/libfrida-gadget.so"
  • Crash-tamper probe: Intentionally trigger an exception (e.g., NPE) and observe whether the process dies normally; hosts that intercept lifecycle/crash paths may swallow or rewrite crashes.[1]

Hardening notes

  • Server-side attestation: Use Play Integrity as one risk signal for sensitive operations, verify tokens on the server, and combine it with account and transaction controls; it is not proof that no runtime instrumentation exists.[3]
  • Use stronger isolation: For supported system components and specialized workloads, Android Virtualization Framework (AVF) provides VM isolation that is materially different from an app container sharing a UID.[4]

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.