W1seGuy - Write-up - TryHackMe

Information

Room#

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

W1seGuy

Write-up

Overview#

Install tools used in this WU on BlackArch Linux:

sudo pacman -S ruby

Server'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:

  1. Cyclic key: The encryption uses key[i%len(key)], meaning the key repeats cyclically throughout the flag encryption.
  2. 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.digits means a 62 characters set.
  • k=5 means 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:

  1. connect to the TCP socket,
  2. retrieve the hexadecimal encoded encrypted flag,
  3. computed the 4 first char of the key,
  4. brute force the 5th char of the key,
  5. 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)
Share