← back to posts

7 - The Mole: Reconstructing an Android Malware Beacon from a PCAP

Overview

The Mole was an Android malware challenge built around a single artifact: a PCAP. From that capture alone, the goal was to reconstruct the infection chain, recover the device token, derive the bot token, and finally reach the flag endpoint.

What made this one interesting is that the network trace already contained the important pivots. The APK was not provided up front, but the traffic exposed enough structure to rebuild the dropper’s behavior and the C2 flow.

What the PCAP Revealed

The capture showed HTTP traffic between a victim Android device and holabot.duckdns.org.

The sequence was simple but very deliberate:

  • A first GET /update using a generic browser User-Agent was rejected.
  • A second GET /update using an Android WebView User-Agent succeeded.
  • The response set a session cookie named sid and served a phishing page for a fake BancoSeguro update.
  • A later POST /heartbeat sent a plaintext HEARTBEAT|<device_token> message.

Those two values were the key outputs from the PCAP:

  • sid = 00000000-dead-beef-0000-000000000000
  • device_token = e6b4e43fd128972e84c9fb687fa0779d

The attack already had a server-issued identity and a device-derived identity, and the rest of the reverse engineering was about understanding how those values were used together.

Rebuilding the APK Download

The PCAP also exposed the request conditions used by the malware to fetch the update APK. The server expected a very specific Android WebView fingerprint and an X-Requested-With header matching the package name.

The helper script that reproduced that request used these exact values:

1PIXEL_6_USER_AGENT = (
2	"Mozilla/5.0 (Linux; Android 12; Pixel 6) "
3	"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
4	"Chrome/112 Mobile Safari/537.36 wv"
5)
6X_REQUESTED_WITH = "com.bancoseguro.update"

That was enough to recreate the download request and retrieve the APK for static analysis. Once decompiled, the structure became much clearer.

Static Analysis of the Dropper

The app presented itself as a BancoSeguro update flow, but the code told a different story.

The JADX output showed the app extracting the SID from the WebView cookie jar and scheduling the heartbeat worker immediately after the phishing page loaded:

1String allCookies = cookieMgr.getCookie("http://holabot.duckdns.org/");
2// ... parse sid= from cookie string ...
3prefs.edit().putString(PREFS_SID, sid).apply();
4WorkManager.getInstance(...).enqueue(heartbeat_worker_with_30s_delay);

MainActivity loaded the phishing page in a WebView, extracted the sid cookie, and stored it locally. A delayed worker then ran the heartbeat logic in the background.

That worker did three important things:

  1. It derived a device token from the Android build identity.
  2. It posted HEARTBEAT|<device_token> to the C2.
  3. It combined the device token with the sid to build a second-stage beacon.

The device token came from an MD5 hash of Build.SERIAL + Build.MODEL, which is a compact but effective way to tie a victim installation to a specific device profile.

The decompiled worker logic in the workshop notes makes that relationship explicit:

1String seed = Build.SERIAL + Build.MODEL;
2byte[] hash = MessageDigest.getInstance("MD5").digest(seed.getBytes("UTF-8"));
3// device_token = MD5(serial + model)

The same script-driven chain also shows the beacon step that followed:

1String sidHex   = sid.replace("-", "");
2String token    = xorHex(sidHex, deviceMd5);
3String message  = StringVault.get(3).replace("{TOKEN}", token);

Token Derivation

Two separate secrets had to be reconstructed.

The first was the Telegram bot token embedded in the APK. It was split across two halves, with the second half XORed against the session cookie bytes. That design made the token depend on the server-issued sid, so the malware could not fully assemble the bot credentials until it had contacted the C2.

The token assembly logic from the decompiled helper was:

1String half1 = StringVault.get(0);          // "8736012157:"
2String sid   = prefs.getString("sid", "");
3byte[] sidBytes = sid.getBytes(StandardCharsets.US_ASCII);
4for (int i = 0; i < ENCRYPTED_HALF2.length; i++)
5	half2bytes[i] = (byte)(ENCRYPTED_HALF2[i] ^ sidBytes[i % sidBytes.length]);
6return half1 + new String(half2bytes, StandardCharsets.US_ASCII);

The second secret was the flag-bearing device token from the heartbeat. That value was already visible in the PCAP, so the real work was understanding how it was used.

The important connection is that the malware treated the session cookie and the device token as paired inputs. Together they formed the challenge’s identity chain:

  • the server gives the victim a sid
  • the malware derives a device token locally
  • the app uses both to authenticate later requests

Reconstructing the Flag Flow

Once the bot token was derived, the final endpoint became reachable. The last request required the live session cookie, the assembled bot token, and the device token from the heartbeat.

The /flag endpoint itself was found by enumerating the C2 surface after the earlier request flow was understood. The final request pattern was:

GET /flag?token=<bot_token>&device_token=<device_token>

The automation script that tied it together used the same sequence:

1sid, _ = fetch_sid(base_url)
2half1, half2_bytes, token_bytes = derive_token_bytes_from_sid(sid)
3device_token = "e6b4e43fd128972e84c9fb687fa0779d"
4flag_code, flag_body, flag_url = call_flag(base_url, sid, token_bytes, device_token)

That produced the flag and completed the loop between packet capture, APK analysis, and endpoint reconstruction.

Takeaways

This challenge was a good reminder that a PCAP can be more than a record of traffic. If the server behavior is distinctive enough, the capture can expose the complete application protocol and reveal which parts of the APK matter most.

The biggest lessons from this one were:

  • User-Agent and header checks are often enough to fingerprint malware download paths.
  • Session cookies can become part of a malware’s internal key schedule.
  • A heartbeat in plaintext is often the easiest place to recover a device-derived identifier.
  • Network captures and reverse engineering are strongest when used together, not in isolation.
series
BotConf 2026 Android Workshop