6 - DeadDrop: Emulating an MQTT Bot to Recover a Flag
Overview
DeadDrop presented an Android malware sample that used MQTT as its C2 channel, with RC4 encryption on every message. The challenge was to reverse the network protocol, recover the broker credentials and encryption keys from the APK, and then write a bot emulator that could register a fake device and trigger flag delivery.
Decoding the Config
The APK’s Cfg.java class stored all configuration values as Base64-encoded strings. This is a minimal evasion technique — strings won’t appear in a naive grep for IP addresses or credentials — but a single Base64 decode reveals everything:
1BROKER_URL = base64.b64decode("dGNwOi8vNjguMTgzLjIxOC41OToxODgz").decode()
2# → tcp://68.183.218.59:1883
3
4USERNAME = base64.b64decode("Ym90Y29uZl9jbGllbnQ=").decode()
5# → botconf_client
6
7PASSWORD = base64.b64decode("TTBiMWwzX0IwdF8yMDI2").decode()
8# → M0b1l3_B0t_2026The broker ran on the same IP as the C2 server from the earlier challenges — the workshop deliberately reused infrastructure.
Recovering the RC4 Key
The RC4 implementation lived in RC4.java. The static key was stored as a byte array _K[] that was XOR-obfuscated with 0x55:
1static final byte[] _K = { 7, 22, 97, 10, 24, 4, 1, 1, 10, 30, 102, 12, 116 };Applying XOR 0x55 to each byte:
1_RAW_K = bytes([7, 22, 97, 10, 24, 4, 1, 1, 10, 30, 102, 12, 116])
2STATIC_KEY = bytes(b ^ 0x55 for b in _RAW_K)
3# → b"RC4_MQTT_K3Y!"The RC4 implementation itself was a standard KSA + PRGA — nothing unusual. Messages were RC4-encrypted, then Base64-encoded for transit.
The Protocol
Connecting to the broker and subscribing to the relevant topics revealed a two-key architecture:
Static key (RC4_MQTT_K3Y!) encrypted three channels:
/info— session key delivery:SESSION:<hex_key>/commands— operator commands:CMD:PING,CMD:STOP,CMD:CHECK,CMD:CONFIG/heartbeat— acknowledgments from the client to the server
Session key (delivered via /info, rotated per session) encrypted:
/data— telemetry JSON: battery, sensor readings, process lists, etc./flag— flag delivery, sent as{"type":"flag","data":"FLAG{...}"}
The session key was the pivot. Once received via /info, the client was expected to acknowledge it and then send a device registration payload to /data using the session key. That registration — a JSON object containing device metadata — was what prompted the server to emit the flag on /flag.
The Bot Emulator
c2_client.py implemented the complete protocol flow using paho-mqtt:
1def _handle_info(client, raw):
2 plain = dec(raw) # decrypt with static key
3 if not plain.startswith("SESSION:"):
4 return
5 hex_key = plain[8:].strip()
6 session_key = bytes.fromhex(hex_key)
7
8 # 1. Acknowledge the session key
9 ts = int(time.time())
10 client.publish("/heartbeat", enc(f"KEY_ACK:{hex_key[:8]}:{ts}"))
11
12 # 2. Publish device info — this triggers the flag
13 _publish_device_registration(client)The device registration payload mirrored what DeviceCollector.buildInfoPayload() would produce on a real Android device:
1info = json.dumps({
2 "type": "info",
3 "android_id": ANDROID_ID,
4 "manufacturer": "Google",
5 "model": "Pixel 6",
6 "sdk": 33,
7 "release": "13",
8 "fingerprint": "google/oriole/oriole:13/TQ3A.230805.001/...",
9 "locale": "en_US",
10 ...
11})
12client.publish("/data", enc(info, session_key))The server validated the payload and, on receiving a well-formed device info message encrypted with the correct session key, published the flag on /flag.
What the connection.out Showed
Running the emulator and capturing output to connection.out showed the full session lifecycle: receiving the session key, acknowledging it, publishing device info, and watching the server stream telemetry back. The server continuously pushed {"type":"process"}, {"type":"telemetry"}, {"type":"sensor"} messages to /data — all decryptable with the session key.
The server also issued CMD:CHECK version=1.2.1 via /commands, which the emulator acknowledged with ACK:CHECK:version=1.2.1.
The flag arrived on /flag after the device info was accepted.
Why MQTT
Using MQTT as a C2 transport is an underused technique in real malware. The reasons it works well:
- MQTT traffic on port 1883 blends in with legitimate IoT device communication
- Publish/subscribe decouples the C2 operator from the victim — the operator never connects directly to the infected device
- The session key architecture means the APK can be fully analyzed without ever obtaining the flag — the server controls key issuance
- Broker authentication (username/password) provides a minimal access barrier against casual connection attempts
The workshop used MQTT here specifically to illustrate how C2 channels don’t have to look like HTTP REST APIs, and how the message format analysis changes when the transport is a message broker rather than a web server.
Key Techniques
| Technique | Where |
|---|---|
| Base64-encoded config | Broker URL and credentials hidden from naive string search |
| XOR-obfuscated RC4 key | _K[] XOR 0x55 → "RC4_MQTT_K3Y!" |
| Two-key protocol | Static key for control plane; session key for data plane |
| Session key gating | Server issues session key only to connected clients; flag requires it |
| Device registration trigger | Server delivers flag only after receiving valid session-key-encrypted device info |
| Full protocol emulator | c2_client.py implements KSA+PRGA, subscribe, handle, publish loop |