← Back
May 1, 20261 min read

TryHackMe W1seGuy Walkthrough: Known-Plaintext Attacks on XOR Encryption

Author

reconraven

TryHackMe W1seGuy Walkthrough: Known-Plaintext Attacks on XOR Encryption

Room overview

W1seGuy is a cryptography-focused room on TryHackMe (by creators hadrian3689 and DrGonz0) that tests your understanding of XOR encryption and known-plaintext attacks. The tagline says it all: "A w1se guy 0nce said, the answer is usually as plain as day."

The room has two tasks:

  1. Task 1: Download and review the provided Python source code
  2. Task 2: Connect to the server, recover the encryption key, and capture both flags

Step 1: Source Code Analysis

The server runs on port 1337. When you connect, it:

  1. Generates a random 5-character key using string.ascii_letters + string.digits (62 possible characters per position)
  2. XORs a hardcoded placeholder flag (THM{thisisafakeflag}) with that key, cycling the key as needed
  3. Sends you the result as a hex-encoded string
  4. Prompts you for the encryption key
  5. If correct, sends you the real flag from flag.txt

The critical insight: the flag format is always THM{...}, so you know the first 4 characters are THM{ and the last character is }.

Step 2: Connecting to the Server

typescript
nc <target-ip> 1337

You will get something like: 

W1seGUY BY GUY ASONG
W1seGUY BY GUY ASONG

Step 3: The XOR Math

XOR decryption is simple:

typescript
plaintext XOR key = ciphertext
ciphertext XOR key = plaintext
ciphertext XOR plaintext = key

Since we know the plaintext starts with THM{, we can XOR those 4 known bytes against the first 4 encrypted bytes to recover the first 4 characters of the key.

For the 5th character, you have two options:

Option A: Brute Force

The 5th key character is one of 62 possibilities (a-z, A-Z, 0-9). Just try them all.

Option B: Use the Closing Brace

The flag ends with }. The key length is 5, so if you know the total length of the encrypted data, the last byte corresponds to key[4] (if the flag length modulo 5 is 0), or whichever key position maps to the final character. XOR the last ciphertext byte with } to get the 5th key character.

Step 4: Python Solution Script

W1SEGUY PYTHON SCRIPT
W1SEGUY PYTHON SCRIPT
typescript
import binascii
import string
import itertools
from concurrent.futures import ThreadPoolExecutor, as_completed
import argparse

def xor_bytes(data, key):
    return bytes([b ^ ord(key[i % len(key)]) for i, b in enumerate(data)])

def find_initial_key(encrypted_hex, known_plaintext):
    encrypted_bytes = binascii.unhexlify(encrypted_hex)
    return ''.join([chr(b ^ ord(known_plaintext[i])) for i, b in enumerate(encrypted_bytes[:len(known_plaintext)])])

def brute_force_key(encrypted_hex, known_prefix="THM{", known_suffix="}"):
    encrypted_bytes = binascii.unhexlify(encrypted_hex)
    key_prefix = find_initial_key(encrypted_hex, known_prefix)
    print(f"[+] Discovered key prefix: {key_prefix}")

    charset = string.ascii_letters + string.digits
    remaining_length = 5 - len(key_prefix)

    def try_key(key):
        full_key = key_prefix + key
        decrypted = xor_bytes(encrypted_bytes, full_key).decode(errors='ignore')
        if decrypted.startswith(known_prefix) and decrypted.endswith(known_suffix):
            return full_key, decrypted
        return None

    found = []
    with ThreadPoolExecutor(max_workers=10) as executor:
        tasks = {
            executor.submit(try_key, ''.join(candidate)): ''.join(candidate)
            for candidate in itertools.product(charset, repeat=remaining_length)
        }
        for future in as_completed(tasks):
            result = future.result()
            if result:
                found.append(result)
                break

    return found

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="XOR Key Brute Forcer for W1seGuy Challenge")
    parser.add_argument("-e", "--encrypted", required=True, help="Hex encoded XOR ciphertext")
    args = parser.parse_args()

    print("[*] Starting brute-force...")
    results = brute_force_key(args.encrypted)

    if results:
        for key, flag in results:
            print(f"\n[+] Key Found: {key}")
            print(f"[+] Decrypted Flag: {flag}")
    else:
        print("[-] No matching flag found.")

Step 5: Capturing Flag 2

Once you submit the correct key, the server responds with the congratulations message and the real flag from flag.txt. That's your Flag 2

THE W1SEGUY FLAG2
THE W1SEGUY FLAG2

Key Learning Points

  1. XOR Properties: XOR is symmetric; knowing any two of (plaintext, ciphertext, key) gives you the third
  2. Known-Plaintext Attack: Even knowing just a few characters (THM{) dramatically reduces key recovery complexity
  3. Key Cycling: When the key is shorter than the message, it repeats making the last character predictable too
  4. 62^5 ≠ brute force needed: With known plaintext, you only need to brute force 62 possibilities (or 0 if you use the closing brace trick)
Author

reconraven

Full-stack developer and security architect building secure web applications.