The following post by 0xb0b is licensed under CC BY 4.0
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.
In the source, we see that it is an XOR encryption. Here, the numerical ASCII value of each i-th letter of the flag is xored with the i-th letter of the key. We see that the key has a length of 5, and if the text is longer than the key, the key is used again.
Our task is now to correctly derive the key used and reconstruct the plaintext flag from a given ciphertext.
With the knowledge of the plaintext and the ciphertext, we can reconstruct the key by 'xoring' the values of the encrypted text with the plaintext.
In short:
C=P⊕K
cn⊕pn=kn
where C = Ciphertext, P = Plaintext and K = Key
If we assume that the plaintext starts with THM{, we already have the possibility of determining the first 4 characters of the key.
Now we can either determine the 5th character of the key offline using brute force or make the assumption that the plaintext is the same length and that the key was inserted exactly x times, so that the last letter of the key was encrypted with the last letter of the key. We assume that the last encrypted plaintext is the letter }.
Below is the source that is also provided in the challenge:
source.py
import randomimport socketserver import socket, osimport stringflag =open('flag.txt','r').read().strip()defsend_message(server,message): enc = message.encode() server.send(enc)defsetup(server,key): flag ='THM{thisisafakeflag}' xored =""for i inrange(0,len(flag)): xored +=chr(ord(flag[i]) ^ord(key[i%len(key)])) hex_encoded = xored.encode().hex()return hex_encodeddefstart(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()classRequestHandler(socketserver.BaseRequestHandler):defhandle(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 already mentioned, we can see here the xoring of the key with the plaintext flag.
flag ='THM{thisisafakeflag}' xored =""for i inrange(0,len(flag)): xored +=chr(ord(flag[i]) ^ord(key[i%len(key)]))
The key length is 5 and is constructed randomly 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 the ciphertext. We then use the reconstructed key to decrypt the ciphertext.
Decryption Script
wise.py
import argparsedefderive_key_part(hex_encoded,known_plaintext,start_index): encrypted_bytes =bytes.fromhex(hex_encoded) derived_key =""for i inrange(len(known_plaintext)): derived_key +=chr(encrypted_bytes[start_index + i] ^ord(known_plaintext[i]))return derived_keydefxor_decrypt(hex_encoded,key): encrypted_bytes =bytes.fromhex(hex_encoded) decrypted_message =""for i inrange(len(encrypted_bytes)): decrypted_message +=chr(encrypted_bytes[i] ^ord(key[i %len(key)]))return decrypted_messagedefmain(): 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.
Recommendation
Don't miss out on Jaxafeds more elegant approach using pwntools: