Summary
Introduction Packed Light is an Easy-rated Network Forensics room from Hacker Holidays — The Byte Lotus Hotel. It’s a reminder that suspicious activity doesn’t always show up as a loud exploit or a massive file transfer. Sometimes it hides inside traffic that looks completely ordinary, like this capture: 1,348 packets in under 42 seconds. The room description sets the tone right away: “Tiny packets. Odd hours. Suspiciously regular.” That’s usually where a real investigation starts: not with a flag, but with behavior that doesn’t match normal user activity. The goal here was simple: find the covert channel, understand how the data was hidden inside it, and recover the message without just copying someone else’s answer key. Following the first lead The strongest early clue came from @0xMia: “…my laptop ping some random :8080 address every single second like clockwork…” That’s a textbook description of beaconing: a host contacting the same destination at fixed intervals, which usually points to malware checking in with a command-and-control server rather than a user browsing normally. Opening the PCAP in Wireshark and sorting the Conversations view (Statistics → Conversations) confirmed it in one glance: one internal address, one external address, hundreds of short connections. The internal host (192.168.1.141) was repeatedly hitting the same external address (34.41.103.191:8080), roughly once a second, like a heartbeat. The requests weren’t coming from a browser either — the User-Agent was ByteLotusClient/1.1 , which is what Mia meant by “not a real app.” At this point there was still no proof of exfiltration, just a pattern worth chasing further. Looking behind the HTTP requests Another line from the room description pointed further: “…folded neatly inside traffic that looks ordinary until you decode it.” Typing tcp.port==8080 && http into the filter bar shrank the capture down to a single conversation, and its shape told the story: one request for /temp/updates.py , then a long chain of identical GET / requests, one every second or so. That order is what made it suspicious. The download came first: the machine pulled a Python script from an unknown address, and only then started checking in with that same address on a schedule. That’s the delivery step of a malware infection — fetch the payload, then beacon — and normal browsing doesn’t look like that. On top of the timing, the request was easy to pick out of the list: every other row was a tiny GET / with a short reply, while this one asked for a different file and got back something big enough to be a program, not a page. Right-clicking the request and choosing Follow → HTTP Stream dumped the whole exchange, and the response body turned out to be a full Python script. That changed what kind of investigation this was: no longer just packet analysis, but reading someone else’s malware to figure out how it moved data. The script itself isn’t reproduced here. It’s linked separately so this write-up can stay focused on the investigation rather than handing over the implementation. Reading the malware instead of guessing Mia’s last message gave the final push: “…what is with the crypto 😭” The script explained it. This wasn’t a keylogger that just fired off keystrokes. Every character went through a small pipeline first: XOR with a key, Base64 encoding, then straight into an HTTP Cookie header on a GET request. The interesting part is where the data ended up: inside a cookie called hotel_sess_state , sent to a domain dressed up as byte-lotus-hotel.thm:8080 That’s why the traffic didn’t look suspicious at a glance: the payload wasn’t in a response body or a download. It was sitting inside a header nobody bothers to read closely. It also explains the traffic volume. Each request carried exactly one encrypted character, so across the whole capture there were only 30 of these requests, one per keystroke stolen. The key itself wasn’t lying in plain sight either Get AlZeaty’s stories in your inbox Join Medium for free to get updates from this writer. The malware builds it inside a getkey() function, concatenating two strings — and that detail nearly tripped me. My first instinct was that those two strings were the flag. They’re not. They’re the key. The flag is scattered across the traffic itself, one character per request. The script hands you the lock; the packets hold the message. The Cookie was not a session cookie Once the script explained the mechanism, the next step was pulling the actual cookie values out of the capture. A typical request looked like this: hotel_sess_state=HA In Wireshark, File → Export Packet Dissections → As Plain Text pulls every request out with its frame number intact. That trailing is a giveaway for Base64, but Base64 is only the outer layer. Each character was XOR-encrypted first and Base64-encoded second, so decoding the Base64 alone gets you gibberish. Reversing it means undoing the malware’s process in the opposite order: Base64 decode, then XOR again with the same key (XOR is its own inverse). The other thing that mattered was order. Each request held a single character, and the 30 requests only meant something if they were reassembled in the sequence they were sent, not the order they happened to appear while scrolling through Wireshark. Reconstructing the hidden message By this point the pieces were all there: the encoded cookie values in the PCAP, the XOR key and encoding process in the script. What was left was mechanical — or so I thought. I wrote a small script to:
- pull every hotel_sess_state value out of the exported capture, - Base64-decode each one,
- reverse the XOR using the key from the malware,
- and reassemble the characters in the order the frames were sent. The decoder script on GitHub is a template on purpose — it hands you the lock, not the answer. Three blanks are waiting for you: p1 andp2 — the two halves of the XOR key, which you can only get by readinggetkey() in the malware (HINT 1 in the script),PCAP_EXPORT — the path to your own plain-text export from Wireshark (HINT 2). The script sorts by frame number itself, so the ordering rule is already encoded — but only the packets can tell you why that order matters. Once the blanks are filled, run it: python decode_cookies_teaching_EN.py The output below is the shape of a real run — the message itself is redacted so this write-up doesn’t hand over the answer: [] Extracted 30 cookies [] Reassembled flag: THM{V3rxxxxxx} My first attempt produced garbage. The pipeline in the script was right, but the decoder was missing two inputs only the malware controlled: the key value and the ordering rule. It decoded happily and reassembled nothing meaningful. The fix was a reminder that the decoder only works when the person writing it understands the whole chain — key, encoding order, frame sequence — not just the algorithm. Once the decoded characters were joined in order, the message came out clean. The flag itself isn’t the interesting part of this room. What stands out is how little data it took to smuggle something meaningful out. What this room teaches This room isn’t really about breaking crypto. The XOR here is weak on purpose. It’s about noticing behavior: a host beaconing on a schedule, a header nobody double-checks, a script that explains everything once you bother to read it. If a host reaches out at fixed intervals, question the destination. If traffic looks ordinary but repeats too cleanly, check the headers before writing it off.