Writeup on THM Holiday Hack 2026:
Day 04 - Forensics / Packed Light

Author: Hubert Feyrer / hubertf, 2026-07-30


A packet capture (traffic.pcapng) of a compromised host. A downloaded Python keylogger exfiltrates keystrokes one character at a time inside an HTTP cookie (hotel_sess_state=), Base64-wrapped and XOR-obfuscated. Reassemble the cookie stream, undo the XOR, and the typed text spells the flag.

Challenge description

Tiny packets. Odd hours. Suspiciously regular. Someone's smuggling out the data equivalent of a hotel towel every night, folded neatly inside traffic that looks ordinary until you decode it.

A short capture from the guest network is all VERA could pull before the connection dropped. Somewhere in that traffic, a quiet little errand is running on a loop, and it isn't part of any service the hotel actually offers.

Today's itinerary: Analyze the provided capture for a covert communication channel. Identify where the exfiltrated data is being hidden and reassemble it. Decode the recovered data and submit the flag.

1. Download

$ file traffic.pcapng
traffic.pcapng: pcapng capture file

2. Docker/nc - what we get

Only the capture. No live target.

3. Analysis steps

3.1 A downloaded keylogger (success)

Follow the HTTP objects; something was fetched and run.

$ # Wireshark -> Follow HTTP: GET /temp/updates.py
GET /temp/updates.py HTTP/1.1
Host: byte-lotus-hotel.thm:8080
...
import requests
import base64
from pynput import keyboard
...

Conclusion: updates.py is a pynput keylogger. It sends each captured character to the server - Base64-encoded, and (from the payload sizes) XOR'd first.

3.2 The exfil channel: one byte per cookie (success)

Pull every exfil cookie out of the capture in order.

$ tshark -r traffic.pcapng -Y 'frame contains "hotel_sess_state="' -T fields -e tcp.payload \
    | xxd -r -p | grep hotel_sess_state=
Cookie: hotel_sess_state=HA==
Cookie: hotel_sess_state=AA==
Cookie: hotel_sess_state=BQ==
... (one Base64 byte per request) ...

Conclusion: each cookie is Base64 of a single byte. Decoding gives a stream of bytes that is still obfuscated - a single-byte XOR against the key H (0x48) recovers ASCII.

3.3 Decode: Base64 -> XOR 0x48 -> flag (success)

For each cookie: strip to the Base64, decode to one byte, XOR with 'H', append.

$ perl 1hf.py
 ->  -> T
 ->  -> H
 ->  -> M
 -> 3 -> {
 ->  -> V
 -> { -> 3
...
 -> = -> u
 -> 5 -> }
flag: THM{V3r4_1s_w4tch1ng_0veR_y0u}

FLAG = THM{V3r4_1s_w4tch1ng_0veR_y0u}

Conclusion: the keylogger's XOR+Base64 exfil is trivially reversible from the capture - "VERA is watching over you".

4. Solution

The decoder, 1hf.py (Perl), verbatim:

#!perl

use MIME::Base64;

$p1 = "H0t3lSt@ff0Nly";
$p2 = "K3epS3cr3t!";

open(IN, "tshark -r traffic.pcapng -Y 'frame contains \"hotel_sess_state=\"' -T fields -e tcp.payload | xxd -r -p | grep \"^Cookie: hotel_sess_state=\" |") or die "open: $!";

$flag = "";
while (<IN>) {
        $b64 = $_;
        $b64 =~ s,.*e=,,;
	chomp($b64);
	$s = decode_base64($b64);
	$p = chr(ord($s) ^ ord("H"));
        print("$b64 -> $s -> $p\n");
	$flag .= $p;
}

close(IN);

print("flag: $flag\n");

(The keylogger's key is $p1.$p2, but it XORs each character with key[i % len] and sends every keystroke in its own request - so i is always 0 and only the first key byte, H, is ever used. Hence the single-byte XOR against 'H'.)

5. Run it

$ perl 1hf.py
flag: THM{V3r4_1s_w4tch1ng_0veR_y0u}

6. Summary of how the exploit works

#StageMechanism
1IdentifyHTTP object /temp/updates.py is a pynput keylogger.
2ReassembleEach keystroke is exfiltrated as one Base64 byte in the hotel_sess_state= cookie; pull them in order with tshark.
3DecodeBase64-decode each, XOR with key 0x48 ('H'), concatenate -> THM{V3r4_1s_w4tch1ng_0veR_y0u}.