daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

firmware-level-zygote-backdoor-libandroid-runtime.md (12459B)


      1 ---
      2 title: "Firmware-level Android Backdoor via libandroidruntime Zygote Injection"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Firmware-level Android Backdoor via libandroid_runtime Zygote Injection
     14 
     15 ## Overview
     16 
     17 Supply-chain tampering of `/system/lib[64]/libandroid_runtime.so` can hijack `android.util.Log.println_native` so that **every app forked from Zygote executes attacker code**. The Keenadu backdoor adds a single call inside `println_native` that drives a native dropper. Because all app processes run this code, Android sandbox boundaries and per-app permissions are effectively bypassed.<sup>[[1]](#references)</sup>
     18 
     19 The same **post-root execution model** also appears in multi-stage Android rootkits delivered from apparently benign Play-distributed apps. In McAfee's **Operation NoVoice** analysis, the malware first landed in user space, obtained root with device-tailored exploits, and then **replaced `libandroid_runtime.so` and `libmedia_jni.so` on the system partition** so that every app spawned by `zygote` inherited the attacker hooks after reboot.<sup>[[3]](#references)</sup>
     20 
     21 ## Recent supply-chain patterns (2025+)
     22 
     23 Recent Triada-linked investigations show the same **"infect Zygote once, reach every app"** outcome being delivered through **counterfeit/pre-infected firmware** and, in some cases, through **OTA-related system components** instead of a purely post-sale root step.<sup>[[4]](#references)</sup> Two practical consequences:
     24 
     25 - **Compare `ro.build.fingerprint` with vendor-published factory images**. In recent counterfeit-device cases, near-match fingerprints were a useful clue that the device was not running a legitimate build.
     26 - **Hunt beyond `libandroid_runtime.so`**. Recent firmware families have also used privileged system APKs / services (launcher, facial-recognition utilities, OTA/update paths) as bootstrap or fallback footholds, so a "clean" shared library does not fully rule out a supply-chain compromise.
     27 
     28 ## Pre-root staging: SDK init hijack + PNG tail polyglot
     29 
     30 Before the `libandroid_runtime.so` replacement, NoVoice used an app-level bootstrap that is worth recognizing during APK triage:<sup>[[3]](#references)</sup>
     31 
     32 - **Auto-exec on first launch**: bootstrap code ran from a tampered Facebook SDK init path, so no extra user interaction or suspicious permission prompt was required.
     33 - **Payload smuggling in assets**: the app shipped a valid **PNG** with an encrypted blob appended **after the PNG `IEND` marker**. Android image decoders ignore trailing bytes, but the loader carved the tail into `enc.apk`, decrypted it into `h.apk`, loaded it, and deleted the staging files.
     34 - **Triage clue**: if bytes immediately after `IEND` begin with a magic such as `CAFEBABE`, assume the image is acting as a **polyglot carrier** for a Java class / JAR / APK payload rather than as a pure media asset.
     35 
     36 Quick checks:
     37 
     38 ```bash
     39 pngcheck -v suspicious.png
     40 tail -c +1 suspicious.png | xxd | tail
     41 binwalk -e suspicious.png
     42 ```
     43 
     44 Hunting notes:
     45 
     46 - Search APK `assets/` for PNGs with **unexpected trailing bytes** or high entropy after `IEND`.
     47 - Trace early init paths such as `Application.onCreate`, third-party SDK bootstrap code, or native `JNI_OnLoad` handlers that open asset PNGs and then write `*.apk` / `*.jar` files into the app sandbox.
     48 
     49 ## Dropper path: native patch → RC4 → DexClassLoader
     50 - Hooked entry: extra call inside `println_native` to `__log_check_tag_count` (injected static lib `libVndxUtils.a`).<sup>[[1]](#references)</sup>
     51 - Payload storage: RC4-decrypt blob embedded in the `.so`, drop to `/data/dalvik-cache/arm[64]/system@framework@vndx_10x.jar@classes.jar`.
     52 - Load & execute: `DexClassLoader` loads the jar and invokes `com.ak.test.Main.main`. Runtime logs use tag `AK_CPP` (triage artifact).
     53 - Anti-analysis: aborts in Google/Sprint/T-Mobile system apps or if kill-switch files exist.
     54 - Zygote role split:
     55   - In `system_server` → instantiate `AKServer`.
     56   - In any other app → instantiate `AKClient`.
     57 
     58 ## What changes on newer Android releases?
     59 
     60 - In AOSP, legitimate `android.util.Log.println_native` is a **minimal JNI wrapper** whose relevant job is calling `__android_log_buf_write`. When auditing a vendor build, **any extra helper call or control-flow branch around this path deserves immediate review**.
     61 - On **Android 14+**, apps using dynamic code loading must mark `DEX` / `JAR` / `APK` payloads **read-only before loading**. Older rootkit chains that drop a writable jar to disk therefore stand out more clearly on modern targets.<sup>[[5]](#references)</sup>
     62 - A common adaptation is to keep the final payload **off disk** and switch the last stage to `InMemoryDexClassLoader`. If you confirm the zygote hook but never recover a stable `classes.dex`, assume the decrypted DEX may only exist in a `ByteBuffer` at runtime. For generic fileless DEX hunting, see [this Android malware post-exploitation page](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/generic-methodologies-and-resources/basic-forensic-methodology/android-malware-post-exploitation.md).
     63 
     64 ## Binder-based client/server backdoor
     65 - `AKServer` (running in `system_server`) sends protected broadcasts:<sup>[[1]](#references)</sup>
     66   - `com.action.SystemOptimizeService` → binder interface for clients.
     67   - `com.action.SystemProtectService` → binder interface for downloaded modules.
     68 - `AKClient` (inside every app) receives the interface via broadcast and performs an `attach` transaction, handing an IPC wrapper so the server can load arbitrary DEX **inside the current app process**.
     69 - Exposed privileged operations (via `SystemProtectService`): grant/revoke any permission for any package, retrieve geolocation, and exfiltrate device info. This centralizes privilege bypass while still executing code in chosen target apps (Chrome, YouTube, launcher, shopping apps, etc.).
     70 
     71 ## C2 staging, crypto, and gating
     72 - Host discovery: Base64 → gzip → AES-128-CFB decrypt with key `MD5("ota.host.ba60d29da7fd4794b5c5f732916f7d5c")`, IV `"0102030405060708"`.<sup>[[1]](#references)</sup>
     73 - Victim registration: collect IMEI/MAC/model/OS, encrypt with key `MD5("ota.api.bbf6e0a947a5f41d7f5226affcfd858c")`, POST to `/ak/api/pts/v4` with params `m=MD5(IMEI)` and `n=w|m` (network type). Response `data` is encrypted identically.
     74 - Activation delay: C2 serves modules only after ~2.5 months from an "activation time" in the request, frustrating sandbox detonations.
     75 - Module container (proprietary):
     76 ```text
     77 struct KeenaduPayload {
     78     int32_t  version;
     79     uint8_t  padding[0x100];
     80     uint8_t  salt[0x20];
     81     KeenaduChunk config;   // size + data
     82     KeenaduChunk payload;  // size + data
     83     KeenaduChunk signature;// size + data
     84 } __packed;
     85 ```
     86   - Integrity: MD5 file check + DSA signature (only operator with private key can issue modules).
     87   - Decryption: AES-128-CFB, key `MD5("37d9a33df833c0d6f11f1b8079aaa2dc" + salt)`, IV `"0102030405060708"`.
     88 
     89 ## Post-root persistence: wrapper libraries + framework patching + self-heal
     90 
     91 Once root is available, replacing `libandroid_runtime.so` is only one layer of persistence. NoVoice shows a more resilient pattern:<sup>[[3]](#references)</sup>
     92 
     93 - **Wrapper replacement instead of inline patching**: the installer backed up the original system library and replaced it with an **architecture-matched hook wrapper**. The same campaign also replaced `libmedia_jni.so`, giving multiple code-execution choke points inside the framework.
     94 - **Second-stage persistence in framework bytecode**: after the library swap, a dedicated patcher modified **pre-compiled Android framework bytecode on disk**. This means restoring the original `.so` may still leave injected redirections active.
     95 - **Self-healing watchdog**: a daemon checked the installation roughly every 60 seconds, restored missing components, and could **force a reboot** if reinsertion kept failing. The malware also replaced the system crash handler / recovery flow so rebooting re-launched the rootkit.
     96 - **Per-app payload assembly at runtime**: after reboot, the replaced `libandroid_runtime.so` caused each spawned app to load attacker code. NoVoice stored secondary payloads as fragments inside the malicious library, assembled them in memory, and deleted the disk copies immediately after load.
     97 
     98 Practical implications:
     99 
    100 - **Factory reset is insufficient** when the malware has modified the **system partition** or framework artifacts. Reflash the firmware instead.
    101 - Diff both **`/system/lib*/libandroid_runtime.so`** and **framework oat/odex/vdex artifacts**; checking only the shared library can miss the bytecode persistence layer.
    102 - If a device keeps restoring the malicious library after manual cleanup, look for a **watchdog daemon**, modified recovery scripts, or a replaced crash-handler path that is re-seeding the implant on boot.
    103 
    104 ## Persistence & forensic tips
    105 - Supply chain placement: malicious static lib `libVndxUtils.a` linked into `libandroid_runtime.so` during build (e.g., `vendor/mediatek/proprietary/external/libutils/arm[64]/libVndxUtils.a`).<sup>[[1]](#references)</sup>
    106 - Firmware auditing: firmware images ship as Android Sparse `super.img`; use `lpunpack` (or similar) to extract partitions and inspect `libandroid_runtime.so` for extra calls in `println_native`.<sup>[[2]](#references)</sup>
    107 - On-device artifacts: presence of `/data/dalvik-cache/arm*/system@framework@vndx_10x.jar@classes.jar`, logcat tag `AK_CPP`, or protected broadcasts named `com.action.SystemOptimizeService`/`com.action.SystemProtectService` indicate compromise.<sup>[[1]](#references)</sup>
    108 - For Android rootkits deployed from apps instead of factory supply chain, also inspect:<sup>[[3]](#references)</sup>
    109   - APK `assets/` for polyglot PNGs with appended data after `IEND`
    110   - Replaced `/system/lib*/libandroid_runtime.so` or `/system/lib*/libmedia_jni.so`
    111   - Modified framework oat/odex/vdex files that preserve hook redirections
    112   - Periodic watchdog processes that rewrite removed files or trigger forced reboots
    113 - Compare the device `ro.build.fingerprint` and hashes of privileged system APKs / libraries against vendor-published firmware. In recent counterfeit-device cases, **subtle fingerprint drift** was one of the few early hints that the image was not authentic.<sup>[[4]](#references)</sup>
    114 - Because the legitimate `println_native` implementation is tiny, added action strings such as `com.action.SystemOptimizeService`, `com.action.SystemProtectService`, the `AK_CPP` log tag, or stray loader paths like `system@framework@vndx_10x.jar@classes.jar` are high-signal triage artifacts.<sup>[[1]](#references)</sup>
    115 
    116 ## Quick rooted-device triage
    117 
    118 ```bash
    119 adb shell getprop ro.build.fingerprint
    120 adb shell 'for f in /system/lib*/libandroid_runtime.so /system/lib*/libmedia_jni.so; do [ -e "$f" ] && sha256sum "$f"; done'
    121 adb pull /system/lib64/libandroid_runtime.so . 2>/dev/null || adb pull /system/lib/libandroid_runtime.so .
    122 strings -a libandroid_runtime.so | rg 'AK_CPP|com.action.SystemOptimizeService|com.action.SystemProtectService|vndx_10x|com.ak.test.Main'
    123 adb shell logcat -d | rg 'AK_CPP|SystemOptimizeService|SystemProtectService'
    124 ```
    125 
    126 If the firmware ships as `super.img`, extract the dynamic partitions first (`simg2img` when needed, then `lpunpack`) and diff the resulting `system*.img` contents against a trusted factory image.
    127 
    128 ## References
    129 - [1] [Keenadu firmware backdoor analysis](https://securelist.com/keenadu-android-backdoor/118913/)
    130 - [2] [lpunpack utility for Android sparse images](https://github.com/unix3dgforce/lpunpack)
    131 - [3] [Operation NoVoice: Rootkit Tells No Tales](https://www.mcafee.com/blogs/other-blogs/mcafee-labs/new-research-operation-novoice-rootkit-malware-android/)
    132 - [4] [A new version of Triada spreads embedded in the firmware of Android devices](https://securelist.com/triada-trojan-modules-analysis/116380/)
    133 - [5] [Behavior changes: Apps targeting Android 14 or higher](https://developer.android.com/about/versions/14/behavior-changes-14)