From Encrypted Blob to Trojan: Decrypting the mParivahan Dropper's Payload (Part 2)¶
In Part 1 we covered
the parivahansewaa.apk dropper's phishing vector, anti-analysis tricks, and
its VPN-sinkhole trick for blinding Google Play Protect. This part covers
how its encrypted second-stage payload is decrypted, and what the resulting
trojan — spoofing the package name com.transport.gov.in — actually does
once it's running.
Payload decryption pipeline¶
The primary payload sits inside assets/wpjfts (4,637,501 bytes), decrypted
by a three-tier pipeline implemented in
com.tmobile.android.provider.xhvc9n.o:
Encrypted asset: wpjfts
[0..15]: XOR key | [16..31]: RC4 key | [32..end]: ciphertext
1. XOR with the first 16 bytes
2. Modified RC4 (4 KSA rounds)
3. Stream slice: [0..11] IV (12 bytes), [12..end] AES-GCM ciphertext + tag
4. AES-256-GCM decrypt (key = XOR of two hardcoded 32-byte arrays)
5. Zlib decompression (magic: MSZ1)
6. Split-container unpack (magic: MSP1) -> base.apk + split_config.dex.apk
Reproducing the decryption¶
We reproduced the routine in Python to independently verify the extracted artifacts and their hashes:
import zipfile, zlib, struct
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
with zipfile.ZipFile("parivahansewaa.apk") as z:
raw = z.read("assets/wpjfts")
# Hardcoded key arrays recovered from o.java
b1 = [124, 57, 44, 42, -25, -119, -28, 19, 127, 125, -95, 41, -87, 116, -117, -83,
-126, -44, 120, 21, 88, -128, 31, 88, 106, -73, -70, -103, 42, -106, -78, 124]
b2 = [33, -81, -33, 39, 112, -32, 41, 34, 29, 66, -53, -122, -6, 26, 1, 90,
-68, -11, 59, 53, -118, -117, -28, -103, -71, 53, -31, 60, -29, 21, -57, -98]
key = bytes([(x & 0xFF) ^ (y & 0xFF) for x, y in zip(b1, b2)])
# Layer 1: XOR
bArr2, bArr3 = raw[:16], raw[16:32]
bArr4 = bytearray(raw[32:])
for i in range(len(bArr4)):
bArr4[i] ^= bArr2[i % 16]
# Layer 2: 4-round RC4 KSA
s = list(range(256))
for _ in range(4):
j = 0
for i in range(256):
j = (j + s[i] + (bArr3[i % 16] & 0xFF)) & 0xFF
s[i], s[j] = s[j], s[i]
bArr5 = bytearray(len(bArr4))
i = j = 0
for idx in range(len(bArr4)):
i = (i + 1) & 0xFF
j = (j + s[i]) & 0xFF
s[i], s[j] = s[j], s[i]
bArr5[idx] = (s[(s[i] + s[j]) & 0xFF] & 0xFF) ^ bArr4[idx]
# Layer 3: AES-GCM, then zlib
iv, ct = bytes(bArr5[:12]), bytes(bArr5[12:])
pt = AESGCM(key).decrypt(iv, ct, None)
decomp = zlib.decompress(pt[4:])
# Unpack the MSP1 split-APK container
magic, count = struct.unpack_from("<II", decomp, 0)
offset = 8
for _ in range(count):
n_len = struct.unpack_from("<H", decomp, offset)[0]; offset += 2
name = decomp[offset:offset + n_len].decode("utf-8"); offset += n_len
sz = struct.unpack_from("<I", decomp, offset)[0]; offset += 4
with open(name, "wb") as f:
f.write(decomp[offset:offset + sz])
offset += sz
Extracted artifacts
| Artifact | Size | SHA-256 |
|---|---|---|
base.apk |
1,305,613 bytes | 5cbce92c326d64438ac22a61210a7bad40fe72ec25a96d52fd7005879684b451 |
split_config.dex.apk |
10,744,151 bytes | 0cb02ec8cb3c30e735083b739c6499a116e497b28ad23f236d6c5b9bd0ea4a49 |
Both are signed under alias DROPPERK, issued to CN=NxtGen mParivahan
Updates (SHA-256: 25:F5:B4:03:A7:6E:D9:A5:97:B9:4E:F9:50:7B:9D:EC:FB:CF:4E:10:AF:E9:A1:E0:FC:37:77:16:85:33:B2:AD).
Stage 2: Trojan Core Analysis (com.transport.gov.in)¶
The dropped package spoofs com.transport.gov.in — identical to the real
government transport package naming scheme — with the core trojan logic
under com.app.messenger.
Lure config (assets/config.json): app name "NxtGen mParivahan",
admin_token/user_id v_GpgVz5NrflZHqM8GBeBjWGIhp3GGsNhF0_duyACUI, app
type mparivahanv3. The lure prompts victims for a ₹1 "Verification Fee" to
harvest card, net-banking, or UPI credentials.
Command & control¶
| Endpoint | Purpose |
|---|---|
https://api.cloudsettle.org |
Primary C2 backend |
wss://api.cloudsettle.org/ws/tunnel/device-b |
Reverse interactive command tunnel |
POST /sms/batch |
Bulk SMS/OTP exfiltration |
POST /call-logs/batch |
Call log upload |
POST /pin-data |
Gzip-compressed stolen UPI/banking PINs (X-Device-ID) |
GET /api/staged/fetch/ |
Dynamic secondary module delivery |
Firebase (used as a dynamic C2 failover — see Part 3): project
andromeda-d389d, app ID 1:651390976306:android:53c99731aa157ea66f972b,
sender ID 651390976306.
SMS & OTP theft engine¶
SmsReceiver (priority 999) and InboxReceiver (priority 998) register for
SMS_RECEIVED/SMS_DELIVER at the highest broadcast priority available,
intercepting bank 2FA OTPs before the user's default messaging app even sees
them. SmsPollingWorker/SmsUploadWorker periodically harvest the full SMS
store and batch-upload it to /sms/batch.
Accessibility Service abuse (SystemAccessibilityService)¶
Binding to BIND_ACCESSIBILITY_SERVICE gives the trojan continuous
surveillance and automation over the UI:
- Automated permission granting — watches for and taps "Allow" in both English (allow, allow all the time, while using the app, grant, permit, accept, turn on, enable) and Hindi (अनुमति दें, हमेशा अनुमति दें, ऐप का उपयोग करते समय, अनुमति, हां, ठीक है) prompts.
- OEM battery/autostart bypass — targets MIUI, ColorOS, FuntouchOS, and
EMUI security apps (
com.miui.securitycenter,com.miui.powerkeeper,com.coloros.safecenter) to self-enable background execution. - Anti-uninstall shielding — watches the foreground package; if the
victim opens
com.android.settings,com.android.packageinstaller, or a device security manager, it firesperformGlobalAction(GLOBAL_ACTION_HOME)to bounce them back to the home screen before they can remove it. - UPI PIN theft — an invisible touch overlay ("ghost overlay") records keystroke coordinates to extract 4–6 digit PINs when the foreground app is one of 60+ targeted payment apps, including PhonePe, Google Pay, Paytm, BHIM, CRED, SBI UPI, HDFC PayZapp, Axis Pay, MobiKwik, BharatPe, Groww, Jupiter Money, and OneCard.
Part 3 zooms out to the wider campaign — 24 typosquatted repositories sharing this infrastructure, the Firebase-based dynamic C2 failover, a full IOC list, and takedown report templates for CERT-In, GitHub, and Google.