W1seGuy Write Up | TryHackMe
You can review the source code by clicking on the Download Task Files button at the top of this task to download the required file.
Room Link https://tryhackme.com/r/room/w1seguy
You can review the source code by clicking on the Download Task Files button at the top of this task to download the required file.
Analysis
The challenge is a cryptographic task that needs to be solved. We are provided with the source and the open port 1337
of the machine that sets the task.
nc Machine_IP 1337
This XOR encoded text has flag 1: 252f3928374006183d33341f001233055317382430090660261d2b0d3b1203130d6332031f3b213a
What is the encryption key?
In the given source, we see that XOR encryption is used. Here, the numerical ASCII value of each i-th letter of the flag is XORed with the i-th letter of the key. The key has a length of 5 characters, and if the text is longer than the key, the key is repeated.
Our task is to correctly derive the key used and reconstruct the plaintext flag from a given ciphertext.
With knowledge of the plaintext and the ciphertext, we can reconstruct the key by XORing the values of the encrypted text with the plaintext.
In summary: C=P⊕KC = P \oplus KC=P⊕K cn⊕pn=knc_n \oplus p_n = k_ncn⊕pn=kn
where CCC is the ciphertext, PPP is the plaintext, and KKK is the key.
If we assume that the plaintext starts with “THM{“, we can determine the first 4 characters of the key.
To find the 5th character of the key, we can either use brute force offline or assume that the plaintext is the same length as the key repeated exactly x times, so that the last letter of the key was encrypted with the last letter of the plaintext. We assume the last encrypted plaintext character is “}”.
Below is 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()
As previously mentioned, we can observe the XORing of the key with the plaintext flag here.
flag = 'THM{thisisafakeflag}'
xored = ""
for i in range(0,len(flag)):
xored += chr(ord(flag[i]) ^ ord(key[i%len(key)]))
The key length is 5 and is randomly constructed from a set of digits and ASCII letters.
res = ''.join(random.choices(string.ascii_letters + string.digits, k=5))
key = str(res)
We use the following script to implement our previous findings. We reconstruct the key using the first four letters and the last letter of the flag by XORing with the ciphertext. We then use the reconstructed key to decrypt the ciphertext.
Decryption Script
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')
#parser.add_argument('key_length', type=int, help='Length of the encryption key')
args = parser.parse_args()
hex_encoded = args.hex_encoded
key_length = 5
# Example usage
known_start_plaintext = 'THM{'
known_end_plaintext = '}'
# Derive the first part of the key using the known starting plaintext
derived_key_start = derive_key_part(hex_encoded, known_start_plaintext, 0)
print("Derived start of the key:", derived_key_start)
# Derive the last character of the key using the known ending plaintext
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)
# Since the key length is key_length, the derived key will repeat
derived_key = (derived_key_start + derived_key_end)[0:key_length]
print("Derived key:", derived_key)
# Decrypt the full message using the derived key
decrypted_message = xor_decrypt(hex_encoded, derived_key)
print("Decrypted message:", decrypted_message)
if __name__ == '__main__':
main()
First Flag
We get the key and the first flag, and we can now hand this key over to the server.
Second Flag
After passing the derived key, we receive the second flag.