diff --git a/src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md b/src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md index 31185de01fc..3cf5285bcdb 100644 --- a/src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md +++ b/src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md @@ -6,6 +6,29 @@ 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. +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. + +## Pre-root staging: SDK init hijack + PNG tail polyglot + +Before the `libandroid_runtime.so` replacement, NoVoice used an app-level bootstrap that is worth recognizing during APK triage: + +- **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. +- **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. +- **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. + +Quick checks: + +```bash +pngcheck -v suspicious.png +tail -c +1 suspicious.png | xxd | tail +binwalk -e suspicious.png +``` + +Hunting notes: + +- Search APK `assets/` for PNGs with **unexpected trailing bytes** or high entropy after `IEND`. +- 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. + ## Dropper path: native patch → RC4 → DexClassLoader - Hooked entry: extra call inside `println_native` to `__log_check_tag_count` (injected static lib `libVndxUtils.a`). - Payload storage: RC4-decrypt blob embedded in the `.so`, drop to `/data/dalvik-cache/arm[64]/system@framework@vndx_10x.jar@classes.jar`. @@ -40,13 +63,34 @@ struct KeenaduPayload { - Integrity: MD5 file check + DSA signature (only operator with private key can issue modules). - Decryption: AES-128-CFB, key `MD5("37d9a33df833c0d6f11f1b8079aaa2dc" + salt)`, IV `"0102030405060708"`. +## Post-root persistence: wrapper libraries + framework patching + self-heal + +Once root is available, replacing `libandroid_runtime.so` is only one layer of persistence. NoVoice shows a more resilient pattern: + +- **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. +- **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. +- **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. +- **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. + +Practical implications: + +- **Factory reset is insufficient** when the malware has modified the **system partition** or framework artifacts. Reflash the firmware instead. +- Diff both **`/system/lib*/libandroid_runtime.so`** and **framework oat/odex/vdex artifacts**; checking only the shared library can miss the bytecode persistence layer. +- 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. + ## Persistence & forensic tips - 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`). - 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`. - 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. +- For Android rootkits deployed from apps instead of factory supply chain, also inspect: + - APK `assets/` for polyglot PNGs with appended data after `IEND` + - Replaced `/system/lib*/libandroid_runtime.so` or `/system/lib*/libmedia_jni.so` + - Modified framework oat/odex/vdex files that preserve hook redirections + - Periodic watchdog processes that rewrite removed files or trigger forced reboots ## References - [Keenadu firmware backdoor analysis](https://securelist.com/keenadu-android-backdoor/118913/) - [lpunpack utility for Android sparse images](https://github.com/unix3dgforce/lpunpack) +- [Operation NoVoice: Rootkit Tells No Tales](https://www.mcafee.com/blogs/other-blogs/mcafee-labs/new-research-operation-novoice-rootkit-malware-android/) {{#include ../../banners/hacktricks-training.md}}