W1seGuy
Analysis Link to heading
In this challenge, we’re tackling a cryptographic problem. We’ve been given the source code and an open port 1337
on the target machine.
The source code shows that the challenge uses XOR encryption. Each character of the flag is XORed with a character from a repeating key. The key is five characters long.
Our goal is to figure out the key and reconstruct the plaintext flag from the given ciphertext.
We can start by making an assumption: if the plaintext begins with THM{
, we can determine the first four characters of the key. For the fifth character, we can either brute-force it or assume that the plaintext ends with }
, matching the pattern that the key repeats.
Here’s the source code provided in the challenge:
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()
From this, we can see the XOR operation being applied to the flag with the key:
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0, len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i % len(key)]))
The key is generated randomly from ASCII letters and digits:
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
To reconstruct the key, we can use the following script. It uses the known starting and ending plaintext to derive the key and then decrypts the ciphertext:
import argparse
def derive_key_part(hex_encoded, known_plaintext, start_index):
encrypted_bytes = bytes.fromhex(hex_encoded)
derived_key = ""
for i in range(len(known_plaintext)):
derived_key += chr(encrypted_bytes[start_index + i] ^ ord(known_plaintext[i]))
return derived_key
def xor_decrypt(hex_encoded, key):
encrypted_bytes = bytes.fromhex(hex_encoded)
decrypted_message = ""
for i in range(len(encrypted_bytes)):
decrypted_message += chr(encrypted_bytes[i] ^ ord(key[i % len(key)]))
return decrypted_message
def main():
parser = argparse.ArgumentParser(description='W1seGuy XOR Decryption')
parser.add_argument('hex_encoded', type=str, help='Hex encoded string to decrypt')
args = parser.parse_args()
hex_encoded = args.hex_encoded
key_length = 5
known_start_plaintext = 'THM{'
known_end_plaintext = '}'
derived_key_start = derive_key_part(hex_encoded, known_start_plaintext, 0)
print("Derived start of the key:", derived_key_start)
derived_key_end = derive_key_part(hex_encoded, known_end_plaintext, len(hex_encoded) // 2 - 1)
print("Derived end of the key:", derived_key_end)
derived_key = (derived_key_start + derived_key_end)[0:key_length]
print("Derived key:", derived_key)
decrypted_message = xor_decrypt(hex_encoded, derived_key)
print("Decrypted message:", decrypted_message)
if __name__ == '__main__':
main()
With this script, we derive the key, decrypt the first flag, and then use the key to submit it to the server.
Second Flag Link to heading
After submitting the derived key, we receive the second flag.