Description
A suspicious cell tower has been detected. Despite the cellular framing, the capture is ordinary HTTP traffic: a compromised handset beacons to a rogue 'tower' over HTTP, leaking its IMSI in a User-Agent header and exfiltrating the flag as base64 in the request bodies. The flag is XOR-encrypted with a key derived from that IMSI, recovered with a known-plaintext attack.
Open the PCAP in Wireshark. Filter on UDP first: look for a broadcast packet advertising an unauthorized test network. That packet names the rogue tower's cell ID.
Once you know the rogue cell ID, filter on http and find the device whose User-Agent shows that same cell ID - confirming it connected to the rogue tower.
Follow the HTTP conversation between the victim device and the rogue tower.
tshark -r rogue_tower.pcap -Y http -T fields -e http.host -e http.user_agent -e http.request.uriwireshark rogue_tower.pcapSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Pull the IMSI from the HTTP User-Agent
ObservationThe capture is plain HTTP, and the IMSI rides in a User-Agent header. Filter on that rather than any GSM or NAS layer to get the device identity and the key seed.Filter on http and read the victim's requests. The device identifies itself in a User-Agent like 'MobileDevice/1.0 (IMSI:310410187936156; CELL:91043)'. Save that IMSI; the last 8 digits are the XOR key.bashtshark -r rogue_tower.pcap -Y 'http.user_agent contains "IMSI"' -T fields -e http.user_agentbash# -> MobileDevice/1.0 (IMSI:310410187936156; CELL:91043)Expected output
MobileDevice/1.0 (IMSI:310410187936156; CELL:91043)
What didn't work first
Tried: Filtering on GSMTAP or UDP layers looking for NAS or MCC/MNC fields to extract the IMSI from real cellular signaling
The capture has no GSMTAP encapsulation or GSM radio frames. Wireshark shows no NAS dissector output because the traffic is plain HTTP. The IMSI only appears as a literal string inside the HTTP User-Agent header, so any cellular-layer filter returns zero packets.
Tried: Using tshark with -e frame.coloring_rule.name or -e wlan.ta to find the rogue cell identity before switching to http filters
The pcap contains no 802.11 or radio-layer frames, so those fields are empty. The rogue tower's cell ID (CELL:91043) appears only inside the User-Agent string of HTTP requests, not in any packet metadata. The correct approach is to filter on http.user_agent directly.
Learn more
Do not chase real cellular signaling. The challenge is dressed up as an IMSI-catcher scenario, but the capture is plain HTTP. There is no GSMTAP, no NAS, no MCC/MNC to filter on. The only cellular artifact is the IMSI string the malware embeds in its User-Agent, which is both the device fingerprint and the seed of the decryption key.
Step 2Reassemble the base64 exfil from the request bodies
ObservationThe tower receives several POST requests, and each body ends in base64 padding. The flag left in chunks, so concatenate them in request order before decoding.The victim sends several HTTP POSTs to the rogue tower, each body carrying a base64 segment (the trailing == is the tell). Follow the HTTP streams and concatenate the segments in request order into one base64 blob.bashtshark -r rogue_tower.pcap -Y 'http.request.method == "POST"' -T fields -e http.file_databash# concatenate the base64 chunks in orderWhat didn't work first
Tried: Base64-decoding the body from a single HTTP POST and treating the result as the complete flag payload
Each POST body carries one segment. Decode a single chunk and you get a short binary fragment that decrypts to nothing recognizable. Follow every stream and join the segments in request order first.
Tried: Using tshark -e http.response.body instead of http.file_data to extract the POST contents
The exfiltrated data is in the request bodies sent by the victim, not in server responses. The field http.file_data captures request body bytes for POST packets. Using http.response.body yields the server's (empty or irrelevant) reply and misses the base64 payload entirely.
Learn more
Why base64 then XOR. Naively base64-decoding the blob yields binary garbage, because the plaintext was XOR-encrypted before encoding. The base64 is just transport. You need the key before the bytes mean anything, which is what the next step recovers.
Step 3XOR-decrypt with the IMSI tail (known-plaintext check)
ObservationDecoding the reassembled blob gives binary garbage, and the challenge says it is XOR-encrypted with a key from the IMSI. XOR the first bytes against the known picoCTF{ prefix and the right IMSI substring falls out.First, try the full IMSI (310410187936156) as the XOR key directly - it produces output but no recognizable flag. Next, apply known-plaintext: XOR the first 8 ciphertext bytes against 'picoCTF' to extract the actual repeating key bytes. Observe that those bytes match the last 8 digits of the IMSI (87936156). Finally, use that derived key to decrypt the full blob. CyberChef: From Base64 -> XOR(87936156, UTF8).pythonpython3 - <<'PY' import base64 blob = base64.b64decode("<concatenated base64 from the POST bodies>") key = b"87936156" # last 8 digits of IMSI 310410187936156 flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(blob)) print(flag.decode()) # picoCTF{...} PYThe known-plaintext step is the safety net: if XORing the first 8 ciphertext bytes against
picoCTF{does not give a run of identical digits, you concatenated the base64 chunks in the wrong order or grabbed the wrong IMSI substring.What didn't work first
Tried: Using the full 15-digit IMSI (310410187936156) as the XOR key directly, treating it as a UTF-8 byte string
All 15 digits produce output with no readable prefix, because the key is only the last 8. The known-plaintext check catches it at once: XOR the first eight ciphertext bytes against the flag prefix and the digits that come back tell you the real key length.
Tried: Applying XOR in CyberChef with key type set to Hex instead of UTF8 for the 8-digit key
The key is those digits as ASCII characters, one byte each, not the hex number they spell. Choose Hex mode in CyberChef and it reads the string as two 4-byte integers and produces garbage. Use UTF8 or Latin1 so each digit is one key byte.
Learn more
Known-plaintext XOR. For a repeating-key XOR,
ciphertext XOR plaintext = keyat every position. Knowing any stretch of plaintext (the flag prefix) directly exposes that many key bytes. Here it both recovers and confirms the key derivation (the IMSI tail), so you are not guessing. See Wireshark and PCAP for CTF for the stream-following workflow.
Interactive tools
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
Flag
Reveal flag
picoCTF{r0gu3_c3ll_t0w3r_...}
Not real cellular forensics: the pcap is HTTP. The victim leaks its IMSI in the User-Agent (310410187936156) and exfiltrates the flag as base64 in POST bodies. Concatenate the base64, then XOR with the last 8 IMSI digits (87936156), confirming with the picoCTF{ known plaintext.