
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:
- Task 1: Download and review the provided Python source code
- 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:
- Generates a random 5-character key using string.ascii_letters + string.digits (62 possible characters per position)
- XORs a hardcoded placeholder flag (THM{thisisafakeflag}) with that key, cycling the key as needed
- Sends you the result as a hex-encoded string
- Prompts you for the encryption key
- 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
nc <target-ip> 1337You will get something like:

Step 3: The XOR Math
XOR decryption is simple:
plaintext XOR key = ciphertext
ciphertext XOR key = plaintext
ciphertext XOR plaintext = keySince 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

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

Key Learning Points
- XOR Properties: XOR is symmetric; knowing any two of (plaintext, ciphertext, key) gives you the third
- Known-Plaintext Attack: Even knowing just a few characters (THM{) dramatically reduces key recovery complexity
- Key Cycling: When the key is shorter than the message, it repeats making the last character predictable too
- 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)