YinkoShield

Knowledge Center / Mobile runtime attacks / mobile runtime attacks · 2026·02

Magisk and Zygisk — rootkit-class module abuse

Magisk is the dominant Android root-management framework. Zygisk is its in-zygote module loader: a Magisk feature that lets modules run code inside the Android zygote process before each app fork. By the time the target app's first line of code runs, Zygisk modules are already mapped into its address space. The injection precedes everything the app does to defend itself.

[ Magisk + Zygisk — module injection at zygote-fork time ] 1. zygote (system_server child) · waits on socket for fork requests from ActivityManager long-lived parent process; every app on the device is a fork of this process 2. ActivityManager · send fork(com.bank.app) · zygote prepares to fork 3. Zygisk pre-fork hook · runs inside the zygote process, before the fork loads matching modules' .so files into the about-to-fork zygote module sees: target package name, can decide to inject or skip 4. fork() · child copies the parent's address space · modules already mapped 5. child · com.bank.app · Application.onCreate() runs modules present from line 1 of the app's execution substrate signal: code.integrity { unexpected_lib_loaded · loaded_before_app_main: true }
Zygisk's pre-fork hook runs inside the zygote process before the fork. Modules are mapped into the parent and copied across the fork; the app's first line of code executes with the modules already present.

1. Mechanism

The Android zygote [3] is a long-running process forked at boot that serves as the parent of every application process. When ActivityManagerService decides to start an app, it sends a fork request to the zygote, which copies its address space and hands the child to the platform’s process-launcher.

Zygisk [2] hooks into this flow. Magisk-mounted as part of the boot sequence, Zygisk attaches to the zygote process and registers a pre-fork callback. When a fork is about to happen, Zygisk:

  • Receives the target package’s identity.
  • Iterates installed Zygisk modules; each module’s preAppSpecialize() hook decides whether to inject for this target.
  • Modules that opt to inject have their native libraries (module.so files) dlopen-ed into the zygote’s address space.
  • The fork then proceeds. The child’s address space is a copy of the parent’s — including the just-loaded modules.

The result: when the target app’s Application.onCreate() runs, the Zygisk modules are already there. There is no opportunity for the app to detect the modules at startup before they’ve had a chance to install hooks.

2. Where in the runtime it operates

Three platform layers are involved:

  • The Magisk boot stack. magiskinit runs early — Magisk is mounted into the boot path via a patched boot image, before init has finished bringing up the rest of the system — and re-mounts system partitions to a copy-on-write overlay. Boot integrity is broken at this point — Verified Boot reports Unverified.
  • The zygote process. Zygisk attaches a hook into the zygote’s fork pipeline. init forks the zygote at boot; system_server is itself forked from the zygote (so system_server is the zygote’s child, not the other way around), and every app process is also a zygote fork. Zygisk intercepts at preAppSpecialize / preServerSpecialize so modules are present in the forked child before any app code runs.
  • The forked app process. The app inherits the modules. Hooks installed into ART, libc, or app-specific symbols can be in place from instruction zero.

3. Which checkpoints it bypasses

  • MEETS_DEVICE_INTEGRITY. Often defeated by the combination of Zygisk + DenyList + Shamiko + a current device-profile workaround. The device profile reported to Play Integrity comes from a Zygisk-modified set of values.
  • MEETS_STRONG_INTEGRITY. Generally not defeated. MEETS_STRONG_INTEGRITY reads from the chip-level RootOfTrust, which records the unlocked bootloader.
  • Android Key Attestation. Not defeated for the same reason — the chain reflects the bootloader state at the chip layer (verifiedBootState = Unverified, deviceLocked = false).

4. Which signals make it observable

code.integrity and device.integrity. The Trusted Runtime Primitive observes:

  • The presence of unexpected .so files mapped into the process’s address space at the earliest app callback (Application.attachBaseContext) — these mappings exist before any app code runs because they were inherited from the zygote parent’s address space at fork time. The signal is presence at first user-code observation, inferred from the maps snapshot taken at attachBaseContext; /proc/<pid>/maps does not carry per-mapping load timestamps, so the inference is from order-of-presence, not from a clock.
  • The chip-level RootOfTrust’s verifiedBootState from the hardware-attestation chain, which is Unverified (or Failed) whenever Magisk is active.
  • The Magisk-specific paths and properties when not cloaked (the weakest of the three signals — DenyList / Shamiko target exactly this kind of probe).

5. Evidence Token shape when observed

The following example is illustrative; field names, type values, and schema are defined in YEI-001 §4 (available through the spec-access process).

{
  "ev": [
    {
      "ts":   "2026-06-15T10:23:14Z",
      "class": "device.integrity",
      "type":  "verified_boot.unverified",
      "data": {
        "verified_boot_state": "Unverified",
        "device_locked":       false,
        "source":              "key_attestation_extension"
      }
    },
    {
      "ts":   "2026-06-15T10:23:14.040Z",
      "class": "code.integrity",
      "type":  "lib.zygote_inherited",
      "data": {
        "lib":             "/data/adb/modules/example/lib/arm64/libmod.so",
        "evidence":        "mapping_present_at_first_app_callback",
        "method":          "maps_read_in_application_attachBaseContext"
      }
    }
  ]
}

The signal is “this .so was already mapped before any user code ran”, inferred from the maps snapshot taken at the earliest app callback. Distinguishes Zygisk-style pre-fork injection (present at fork) from post-launch ptrace injection (mapped after). Execution Evidence Infrastructure (EEI) — the device-identity infrastructure layer for banking and payments — signs the pair (Verified-Boot state + zygote-inherited mappings) so the operator’s verifier reads both together.

6. Cross-references

7. External references

[1] Magisk. Developer Guide. topjohnwu.github.io/Magisk/guides.html. Cited 2026-02-08.

[2] Magisk. Zygisk API. topjohnwu.github.io/Magisk/guides.html#zygisk. Cited 2026-02-08.

[3] AOSP. Zygote and Application Process. source.android.com/docs/core/runtime/process-lifecycle. Cited 2026-02-08.