Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,56 @@ Triage ideas:
- fake media/font assets later passed into `ZipInputStream`, `DexClassLoader`, or custom RC4 helpers
- reflection on `pathList`, `dexElements`, `ContextImpl`, or `LoadedApk` close to asset decryption

### Manifest-declared components resolved only at runtime

A useful dropper hunting pattern is when `AndroidManifest.xml` declares activities/services that are **missing from the root `classes.dex`**. The visible APK often keeps the real implementations inside an encrypted asset (`*.ttf`, `*.dat`, `*.jar`, `*.base`), decrypts it into private storage, and only then exposes the missing classes through `DexClassLoader` or reflective `DexPathList` injection.<sup>[[19]](#references)</sup>

Practical workflow:

1. Extract every class name referenced by `AndroidManifest.xml`.
2. Compare them against all packaged `classes*.dex` files, not just the first DEX.
3. Treat unresolved components as a strong signal for staged code loading.
4. Hook asset reads, private-file writes, and class-loader APIs to recover the hidden module at runtime.

High-signal events to correlate:
- `AssetManager.open()` / `Resources.openRawResource()` on non-media assets with high entropy
- `FileOutputStream` / `openFileOutput()` writing `*.dex`, `*.jar`, `*.apk` under app-private paths
- `DexClassLoader`, `PathClassLoader`, `InMemoryDexClassLoader`, or reflection touching `pathList` / `dexElements`
- `adb logcat` entries revealing generated payload paths or class-loading failures

Quick triage:

```bash
# Manifest vs DEX diff
apkanalyzer manifest print app.apk | rg 'service|activity|receiver'
unzip -l app.apk | rg 'classes.*\.dex|assets/|res/raw/'

# Recover runtime payloads from the app sandbox
adb shell run-as <pkg> sh -c 'find . -type f \( -name "*.dex" -o -name "*.jar" -o -name "*.apk" -o -name "*.json" -o -name "*.db" -o -name "*.xml" \) 2>/dev/null'
adb logcat | rg 'DexClassLoader|ClassNotFoundException|PackageInstaller|app_'
```

### Asset-to-`PackageInstaller` staging and nested child payloads

Another useful evasion pattern is to hide stage 2 inside `assets/` and **stream it directly into a `PackageInstaller` session** instead of first dropping a plainly named APK in shared storage. After the child package is installed, that second package may generate another `DEX`/`JAR` under `/data/user/0/<child_pkg>/app_*` and dynamically load the final module.<sup>[[19]](#references)</sup>

Hunting ideas:
- correlate `ACTION_MANAGE_UNKNOWN_APP_SOURCES` / `REQUEST_INSTALL_PACKAGES` with `PackageInstaller.createSession` -> `openWrite` -> `fsync` -> `commit`
- watch for the parent package stopping a temporary service (often VPN / overlay / lure UI) immediately after install, then launching the new package
- inspect `PACKAGE_ADDED` / `PACKAGE_REPLACED` receivers, private `app_*` directories, and follow-on `DexClassLoader` activity in the child app

### `AccountManager` + Sync Adapter persistence

A less common but very useful Android persistence primitive is to register a **fake account** and attach a **Sync Adapter** to it. The malware then uses `ContentResolver.setSyncAutomatically()`, `addPeriodicSync()`, or `requestSync()` so Android wakes it on a schedule even when no long-running service is visible.<sup>[[19]](#references)</sup>

What to look for:
- authenticator XML/resources plus code calling `AccountManager.addAccountExplicitly`
- a sync adapter service with `android.content.SyncAdapter` metadata
- suspicious sync intervals (for example every 30 minutes) or forced immediate sync right after connectivity returns
- boot receivers / WorkManager jobs whose only purpose is to re-register the account or reschedule sync

This is especially useful in samples that already store queue/state locally (SQLite + SharedPreferences): periodic sync becomes the exfil/reconnect trigger for offline-collected SMS, credential captures, contacts, or phishing results.

### Anti-analysis kill-switch

Packed loaders often **self-terminate** when emulator or analysis checks fail (e.g., `CPU_ABI` validation) by calling:
Expand Down Expand Up @@ -880,5 +930,6 @@ struct Header {
- [16] [Rokarolla : Android Banker with Complete Device Takeover Capabilities](https://zimperium.com/blog/rokarolla-android-banker-with-complete-device-takeover-capabilities)
- [17] [Zimperium IOC – Rokarolla commands](https://github.com/Zimperium/IOC/blob/master/2026-06-Rokarolla/commands.md)
- [18] [Kimwolf Android TV Botnet: ENS-Based C2 Evasion, TLS+ECDSA C2 Protocol, and Large-Scale Proxy/DDoS Operations](https://blog.xlab.qianxin.com/kimwolf-botnet-en/)
- [19] [Octagon: Technical Analysis of a Fake Bahrain Civil Defense Application](https://labs.k7computing.com/index.php/octagon-technical-analysis-of-a-fake-bahrain-civil-defense-application/)

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