Information
Room#
- Name: W1seGuy
- Profile: tryhackme.com
- Difficulty: Easy
- Description: A w1se guy 0nce said, the answer is usually as plain as day.

Write-up
Overview#
Install tools used in this WU on BlackArch Linux:
sudo pacman -S rubyServer's source code analysis#
We're given the following python server source code:
import random
import socketserver
import socket, os
import string
flag = open('flag.txt','r').read().strip()
def send_message(server, message):
enc = message.encode()
server.send(enc)
def setup(server, key):
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0,len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i%len(key)]))
hex_encoded = xored.encode().hex()
return hex_encoded
def start(server):
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
hex_encoded = setup(server, key)
send_message(server, "This XOR encoded text has flag 1: " + hex_encoded + "\n")
send_message(server,"What is the encryption key? ")
key_answer = server.recv(4096).decode().strip()
try:
if key_answer == key:
send_message(server, "Congrats! That is the correct key! Here is flag 2: " + flag + "\n")
server.close()
else:
send_message(server, 'Close but no cigar' + "\n")
server.close()
except:
send_message(server, "Something went wrong. Please try again. :)\n")
server.close()
class RequestHandler(socketserver.BaseRequestHandler):
def handle(self):
start(self.request)
if __name__ == '__main__':
socketserver.ThreadingTCPServer.allow_reuse_address = True
server = socketserver.ThreadingTCPServer(('0.0.0.0', 1337), RequestHandler)
server.serve_forever()The server implements a simple XOR cipher flag delivery over a TCP socket.
# [...]
def setup(server, key):
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0,len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i%len(key)]))
hex_encoded = xored.encode().hex()
return hex_encoded
def start(server):
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
# [...]What can be noted is:
- Cyclic key: The encryption uses
key[i%len(key)], meaning the key repeats cyclically throughout the flag encryption. - Known clear text: As we get the encrypted flag, and we know the 4 first characters of the clear text flag (
THM{), we can compute the 4 first characters (out of 5) of the key (key ^ flag = encrypted_flag), which massively reduce the number of combination to brute force.
string.ascii_letters + string.digitsmeans a 62 characters set.k=5means the key is 5 characters long.- So the entropy of the key is 30 bits (log₂(62^5)), which is about 916 millions (62^5) of combinations.
- Because we need to brute force only 1 character, it is down to 6 bits (log₂(62^1)) of entropy which is only 62 combinations.
- So instead of hours or days of brute force, it will be instantaneous.
Solution script#
So here is a Ruby script to:
- connect to the TCP socket,
- retrieve the hexadecimal encoded encrypted flag,
- computed the 4 first char of the key,
- brute force the 5th char of the key,
- retrieve the flag.
require 'socket'
HOST = '10.129.183.99'
PORT = 1337
def xor_decode(hex_encoded, key)
# Convert hex string to bytes and XOR with key (cyclically)
encrypted_bytes = [hex_encoded].pack('H*')
decrypted = ''
encrypted_bytes.each_char.with_index { |char, i| decrypted += (char.ord ^ key[i % key.length].ord).chr }
decrypted
end
def retrieve_key(hex_encoded, known_plaintext = 'THM{')
# Extract partial key from known plaintext
encrypted_bytes = [hex_encoded].pack('H*')
key_chars = Array.new(5)
known_plaintext.each_char.with_index { |char, i| key_chars[i % 5] = (char.ord ^ encrypted_bytes[i].ord).chr }
# Brute force missing character
missing_index = key_chars.find_index(nil)
return key_chars.join, xor_decode(hex_encoded, key_chars.join) if missing_index.nil?
charset = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
charset.each do |missing_char|
test_key = key_chars.dup
test_key[missing_index] = missing_char
full_key = test_key.join
decrypted = xor_decode(hex_encoded, full_key)
# Only check if it starts with THM{ and ends with }
return full_key, decrypted if decrypted.start_with?('THM{') && decrypted.end_with?('}')
end
nil
end
def solve_chall
socket = TCPSocket.new(HOST, PORT)
puts "[*] Connected to #{HOST}:#{PORT}"
response = socket.gets
puts "[*] Response received: #{response.strip}"
if response =~ /flag 1: ([a-f0-9]+)/
hex_encoded = Regexp.last_match(1)
puts "[*] XOR-encoded text: #{hex_encoded}"
key, decrypted = retrieve_key(hex_encoded)
if key && decrypted
puts "[+] Key found: #{key}"
puts "[+] Decrypted text: #{decrypted}"
socket.puts(key)
puts "[+] Final response:\n#{socket.gets.strip}"
else
puts '[-] Key not found'
end
else
puts '[-] Unexpected response format'
end
socket.close
rescue Errno::ECONNREFUSED
puts '[-] Connection refused. Check if server is running.'
rescue StandardError => e
puts "[-] Error: #{e.message}"
end
solve_chall$ ruby solve.rb
[*] Connected to 10.129.183.99:1337
[*] Response received: This XOR encoded text has flag 1: 37787e150a52515f000e2648472f0e1704500519225e415d1b0f7c4a062f11444a5e0f11487c1c07
[*] XOR-encoded text: 37787e150a52515f000e2648472f0e1704500519225e415d1b0f7c4a062f11444a5e0f11487c1c07
[+] Key found: edited_key
[+] Decrypted text: THM{edited_flag_1}
[+] Final response:
What is the encryption key? Congrats! That is the correct key! Here is flag 2: THM{edited_flag_2?}Conclusion#
By leveraging the known plaintext attack principle, we transformed an infeasible brute-force problem into an instant solution. This demonstrates the importance of:
- Analyzing cryptographic implementations
- Identifying information leakage (predictable flag format)