This analysis was conducted in three sequential phases, each building on the previous. No single layer was treated as sufficient on its own — findings were only reported with confidence once corroborated across at least two independent methods.
Phase 1: Static Analysis
↓
Phase 2: Dynamic Analysis
↓
Phase 3: Cross-verification + Reconciliation
The APK was unpacked using standard ZIP extraction to inspect its raw structure:
unzip NetMirror.apk -d netmirror_extract/Artifacts examined:
AndroidManifest.xml— decoded via androguard (AXMLPrinter) for human-readable outputMETA-INF/contents — inventoried for signing scheme and certificate filesclasses.dex— counted and noted (single dex, noclasses2.dex)assets/index.android.bundle— identified as Hermes bytecode v96
All Java classes decompiled using JADX:
jadx -d netmirror_jadx_out/ NetMirror.apkKey focus areas:
- App's own package (
app.netmirror.netmirrornew) — 2 classes:MainActivity,MainApplication - Third-party libraries — attributed carefully to known open-source packages before drawing conclusions
- Permission-related classes — traced to React Native library constants, not app logic
Critical methodology note: Every suspicious-looking class was cross-referenced against its likely open-source library origin before being attributed to the app developer. Many initial false positives came from misattributing standard react-native-webview, react-native-system-setting, and react-native-device-info library code as custom malicious logic.
React Native JS bundles compiled with Hermes cannot be decompiled by JADX. The bundle was disassembled separately using hermes-dec:
pip install hermes-dec
python3 -c "
import sys
sys.argv = ['hbc_disassembler.py', 'assets/index.android.bundle', 'bundle.disasm.txt']
from hermes_dec.disassembly import hbc_disassembler
hbc_disassembler.main()
"Output: 278,825 lines of disassembly (17MB).
Key searches performed:
| Search term | Purpose |
|---|---|
mobidetect |
C2 domain pool claim |
mpanyinadiingsinsp |
Affiliate redirect claim |
pressfromAPP |
WebView JS injection claim |
dlUrl / stdata / streamDomain |
Resolver POST flow |
READ_SMS / READ_CALL_LOG |
Permission escalation claim |
getAndroidId / getCarrier / getSerialNumber |
Device fingerprinting claim |
isEmulator |
Anti-analysis evasion claim |
KillerApplication / SignatureKiller |
Signature spoofing claim |
Each hit was examined in context (surrounding bytecode) to distinguish actual app logic from library export tables and constants.
All dynamic testing was performed on a rooted Pixel 6a running Android 14 with:
- Frida server 17.8.0 (matching host Frida version)
- Burp Suite configured as MITM proxy
- tcpdump for packet-level capture
Burp Suite was configured as a system-wide MITM proxy on the device. The app has no SSL/certificate pinning (confirmed in static analysis), so all TLS traffic was decryptable.
Sessions captured:
- Cold launch
- Home screen browsing
- Video playback (multiple titles)
- Download flow
- Background/foreground cycling
All captured requests were exported and sanitized before inclusion in this repository (cookies, private IPs, and precise timestamps were redacted).
Limitation: HTTP Toolkit / Burp Suite only intercepts TCP-based traffic. QUIC (HTTP/3, UDP port 443) bypasses standard MITM proxies and was only visible at the packet layer.
Full packet capture was performed alongside HTTP interception to catch any traffic bypassing the proxy (QUIC, direct IP connections, non-standard ports):
# On device (rooted)
adb shell su -c "tcpdump -i any -w /sdcard/netmirror_full.pcap"
# Pull to host
adb pull /sdcard/netmirror_full.pcap ./Analysis performed with tshark:
# SNI extraction (TLS hostnames, even without decryption)
tshark -r netmirror_full.pcap -Y "tls.handshake.type==1" \
-T fields -e tls.handshake.extensions_server_name | sort -u
# DNS query extraction
tshark -r netmirror_full.pcap -Y "dns.flags.response==0" \
-T fields -e dns.qry.name | sort -u
# Protocol breakdown
tshark -r netmirror_full.pcap -q -z io,phsThe full .pcap file is not included in this repository for privacy reasons (contains device IP, precise timing data, and session metadata). Extracted summaries (SNI list, DNS list) are included instead.
The app was spawned under Frida to hook key Java methods at runtime:
frida -U -f app.netmirror.netmirrornew -l hook.js --no-pauseImportant discovery: This app uses com.android.okhttp (the legacy AOSP-bundled OkHttp fork), not the standalone okhttp3 library. Any Frida hooks targeting okhttp3.* class paths will silently fail. Hooks must target com.android.okhttp.* instead.
Methods hooked:
PermissionsModule.requestPermission— catch any runtime permission requestsPermissionsModule.requestMultiplePermissions— multi-permission variantActivity.requestPermissions— raw framework-level fallbackIntentModule.openURL— catchLinking.openURL()calls (dlUrl handoff)AsyncStorageModule.multiGet/multiSet— storage read/write activityWebView.loadUrl— hidden WebView navigationOkHttpClient.newCall— all outbound HTTP requests
Test sequence for each run:
- Cold launch → 30 second idle
- Home screen browse
- Video playback
- Forced network error (throttle mid-stream)
- Download button tap
- App backgrounded → foregrounded
App data extracted directly from device storage (requires root):
adb shell su -c "cp -r /data/data/app.netmirror.netmirrornew /sdcard/netmirror_data_dump"
adb pull /sdcard/netmirror_data_dump ./Files examined:
databases/RKStorage— React Native AsyncStorage (SQLite)sqlite3 RKStorage "SELECT key, value FROM catalystLocalStorage;"shared_prefs/*.xml— Native Android SharedPreferences
Each finding from static analysis was cross-checked against dynamic results:
| If static found X | Dynamic check |
|---|---|
| String/domain in bundle | Did it appear in traffic captures? |
| Permission in code | Did Frida hook fire? Was it in manifest? |
| Network endpoint | Was it in Burp capture AND/OR pcap SNI/DNS? |
| Library method | Was it called from app code, or just exported by the library? |
| Class claimed to exist | Was it in JADX class tree? Could it be searched in 4,814 classes? |
Findings were classified as:
- ✅ Confirmed — present in code AND observed in live behavior
⚠️ Unconfirmed — present in code but not triggered dynamically (capability, not active use)- ❌ False positive — attributed to wrong source (library vs app) or does not exist at all
| Tool | Version | Purpose |
|---|---|---|
| JADX | 1.5.1 | Java decompilation |
| hermes-dec | 0.1.7 | Hermes bytecode disassembly |
| Frida | 17.8.0 | Runtime instrumentation |
| Burp Suite | Community Edition | HTTP interception |
| tcpdump | Device-native | Packet capture |
| tshark / Wireshark | 4.x | Packet analysis |
| androguard | 4.x | AndroidManifest.xml parsing |
| adb | Platform Tools | Device communication |
| Python | 3.12 | Scripting / analysis automation |
| SQLite3 | 3.x | AsyncStorage database reading |
Test Device: Rooted Google Pixel 6a, Android 14
1. Single APK snapshot This analysis covers one specific version of NetMirror. The app has no Play Store presence and is distributed via direct links — version history is not available. A future update could change behavior significantly.
2. Server-side gating Several behaviors (affiliate redirect, potential second-stage payload) may be gated server-side by geography, IP reputation, device profile, or A/B testing. Not triggering them in this test environment does not prove they never fire.
3. Hermes disassembly vs decompilation hermes-dec produces bytecode disassembly (human-readable opcodes), not reconstructed JavaScript source. Function boundaries are visible, but complex logic analysis requires manual tracing through the instruction stream — less readable than decompiled Java.
4. QUIC traffic content QUIC (HTTP/3) traffic was visible at the packet level (DNS + IP conversations) but could not be decrypted without TLS session keys. Content of these connections is unconfirmed.
5. Native libraries
This APK contains minimal native code. No deep analysis of native libraries was performed beyond confirming that the specific native libraries claimed in prior reports (libSignatureKiller.so, xhook) do not exist.
6. Frida OkHttp hook gap
The OkHttpClient.newCall hook was attached successfully but produced no output during testing. This is unexplained — possible that the build's network calls are routed differently than expected (e.g., via HttpURLConnection directly rather than through the OkHttp Request builder pattern). HTTP Toolkit proxy capture was used as the primary source of network content instead.
See Reproduciability/instructions/setup_instructions.md for full step-by-step reproduction guide.