Skip to content

Commit 7643c45

Browse files
authored
Merge pull request #2086 from HackTricks-wiki/update_Operation_NoVoice__Rootkit_Tells_No_Tales_20260402_131507
Operation NoVoice Rootkit Tells No Tales
2 parents 6145112 + dd003a7 commit 7643c45

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

src/mobile-pentesting/android-app-pentesting/firmware-level-zygote-backdoor-libandroid_runtime.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66

77
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.
88

9+
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.
10+
11+
## Pre-root staging: SDK init hijack + PNG tail polyglot
12+
13+
Before the `libandroid_runtime.so` replacement, NoVoice used an app-level bootstrap that is worth recognizing during APK triage:
14+
15+
- **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.
16+
- **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.
17+
- **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.
18+
19+
Quick checks:
20+
21+
```bash
22+
pngcheck -v suspicious.png
23+
tail -c +1 suspicious.png | xxd | tail
24+
binwalk -e suspicious.png
25+
```
26+
27+
Hunting notes:
28+
29+
- Search APK `assets/` for PNGs with **unexpected trailing bytes** or high entropy after `IEND`.
30+
- 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.
31+
932
## Dropper path: native patch → RC4 → DexClassLoader
1033
- Hooked entry: extra call inside `println_native` to `__log_check_tag_count` (injected static lib `libVndxUtils.a`).
1134
- 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 {
4063
- Integrity: MD5 file check + DSA signature (only operator with private key can issue modules).
4164
- Decryption: AES-128-CFB, key `MD5("37d9a33df833c0d6f11f1b8079aaa2dc" + salt)`, IV `"0102030405060708"`.
4265

66+
## Post-root persistence: wrapper libraries + framework patching + self-heal
67+
68+
Once root is available, replacing `libandroid_runtime.so` is only one layer of persistence. NoVoice shows a more resilient pattern:
69+
70+
- **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.
71+
- **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.
72+
- **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.
73+
- **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.
74+
75+
Practical implications:
76+
77+
- **Factory reset is insufficient** when the malware has modified the **system partition** or framework artifacts. Reflash the firmware instead.
78+
- Diff both **`/system/lib*/libandroid_runtime.so`** and **framework oat/odex/vdex artifacts**; checking only the shared library can miss the bytecode persistence layer.
79+
- 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.
80+
4381
## Persistence & forensic tips
4482
- 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`).
4583
- 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`.
4684
- 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.
85+
- For Android rootkits deployed from apps instead of factory supply chain, also inspect:
86+
- APK `assets/` for polyglot PNGs with appended data after `IEND`
87+
- Replaced `/system/lib*/libandroid_runtime.so` or `/system/lib*/libmedia_jni.so`
88+
- Modified framework oat/odex/vdex files that preserve hook redirections
89+
- Periodic watchdog processes that rewrite removed files or trigger forced reboots
4790

4891
## References
4992
- [Keenadu firmware backdoor analysis](https://securelist.com/keenadu-android-backdoor/118913/)
5093
- [lpunpack utility for Android sparse images](https://github.com/unix3dgforce/lpunpack)
94+
- [Operation NoVoice: Rootkit Tells No Tales](https://www.mcafee.com/blogs/other-blogs/mcafee-labs/new-research-operation-novoice-rootkit-malware-android/)
5195

5296
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)