Twisted Text
Задание
В убежище нашли старый терминал, который до сих пор работает. По слухам, он использовался для шифрования секретных данных. У тебя есть возможность подключиться к нему, изучить его алгоритмы и узнать, что за тайны в нем скрываются.
Решение
Подключение для выполнения данного задания происходит по Netcat.
Для выполнения задания был дан файл с исходным кодом сервиса:
import os
import socket
import random
from threading import Thread
alphabet = "abcdefghijklmnopqrstuvwxyz1234567890-_=+!;:?*,."
def generate_key ():
key_length = random.randint( 5 , 30 )
key = '' .join(random.choice(alphabet) for _ in range (key_length))
return key
def encrypt (text, key):
encrypted_text = []
key_length = len (key)
key_index = 0
for char in text:
if char in alphabet:
char_text_index = alphabet.index(char)
key_char = key[key_index % key_length]
key_index_in_alphabet = alphabet.index(key_char)
encrypted_index = (char_text_index + key_index_in_alphabet) % len (alphabet)
encrypted_text.append(alphabet[encrypted_index])
key_index += 1
else :
encrypted_text.append(char)
return '' .join(encrypted_text)
def decrypt (encrypted_text, key):
decrypted_text = []
key_length = len (key)
key_index = 0
for char in encrypted_text:
if char in alphabet:
encrypted_index = alphabet.index(char)
key_char = key[key_index % key_length]
key_index_in_alphabet = alphabet.index(key_char)
decrypted_index = (encrypted_index - key_index_in_alphabet) % len (alphabet)
decrypted_text.append(alphabet[decrypted_index])
key_index += 1
else :
decrypted_text.append(char)
return '' .join(decrypted_text)
def menu (conn, key, flag):
conn.sendall( " \n Welcome. You can encrypt or decrypt some text or get the flag. \n The encryption key changes every time you get a flag. \n " .encode())
while True :
conn.sendall( "1 - Encrypt \n 2 - Decrypt \n 3 - Get flag \n 0 - Exit \n " .encode())
choice = str (conn.recv( 1024 ).decode().strip())
if choice == "1" :
conn.sendall( "Enter text: \n " .encode())
text = str (conn.recv( 4096 ).decode().strip())
encrypted_text = encrypt(text.lower(), key)
conn.sendall( f "Encrypted text: { encrypted_text }\n\n " .encode())
elif choice == "2" :
conn.sendall( "Enter ciphertext: \n " .encode())
ciphertext = str (conn.recv( 4096 ).decode().strip())
decrypted_text = decrypt(ciphertext.lower(), key)
conn.sendall( f "Decrypted text: { decrypted_text }\n\n " .encode())
elif choice == "3" :
encrypted_flag = encrypt(flag, key)
conn.sendall( f "Your flag: { encrypted_flag }\n\n " .encode())
key = generate_key()
elif choice == "0" :
break
else :
conn.sendall( "Incorrect input! \n\n " .encode())
def handle_client (conn, addr):
print ( f "New connection from { addr } " )
flag = f "ptech2024 {{{ os.getenv( 'flag' ) }}}\n "
try :
key = generate_key()
menu(conn, key, flag)
except Exception as e:
print ( f "Error in session with { addr } : { e } " )
finally :
conn.close()
def server_get_connections ():
server_socket = socket.socket(socket. AF_INET , socket. SOCK_STREAM )
server_socket.bind(( '0.0.0.0' , 6789 ))
server_socket.listen( 10 )
print ( "Server started. Listening on port 6789...." )
while True :
conn, addr = server_socket.accept()
client_thread = Thread( target = handle_client, args = (conn, addr))
client_thread.start()
if __name__ == "__main__" :
server_get_connections()
После подключения пользователю доступно меню с основным функционалом сервиса:
Welcome. You can encrypt or decrypt some text or get the flag.
The encryption key changes every time you get a flag.
1 - Encrypt
2 - Decrypt
3 - Get flag
0 - Exit
В данном сервисе пользователь может зашифровать и расшифровать текст, а также получить флаг. Однако флаг выдается пользователю в зашифрованном виде:
Your flag: 7eh7*27,-{3lq!fm_znuwqg+s,7bm=or3.t_dmpq=pq4p,z,jp}
Разберем подробнее функцию шифрования. Как можно заметить, в данном задании использован шифр подстановки. Для каждого символа в шифруемом тексте происходит сдвиг индекса в алфавите путем сложения с индексом символа ключа. На основании этого, можно сделать вывод о том, что это шифр Виженера .
def encrypt (text, key):
encrypted_text = []
key_length = len (key)
key_index = 0
for char in text:
if char in alphabet:
char_text_index = alphabet.index(char)
key_char = key[key_index % key_length]
key_index_in_alphabet = alphabet.index(key_char)
encrypted_index = (char_text_index + key_index_in_alphabet) % len (alphabet)
encrypted_text.append(alphabet[encrypted_index])
key_index += 1
else :
encrypted_text.append(char)
return '' .join(encrypted_text)
Продолжим анализ кода. Шифрование текста, введенного пользователем, и флага происходит с использованием одного ключа. Однако после получения флага ключ шифрования изменяется, поэтому не получится просто расшифровать флаг с помощью соответствующей функции сервиса.
def menu (conn, key, flag):
conn.sendall( " \n Welcome. You can encrypt or decrypt some text or get the flag. \n The encryption key changes every time you get a flag. \n " .encode())
while True :
conn.sendall( "1 - Encrypt \n 2 - Decrypt \n 3 - Get flag \n 0 - Exit \n " .encode())
choice = str (conn.recv( 1024 ).decode().strip())
if choice == "1" :
conn.sendall( "Enter text: \n " .encode())
text = str (conn.recv( 4096 ).decode().strip())
encrypted_text = encrypt(text.lower(), key)
conn.sendall( f "Encrypted text: { encrypted_text }\n\n " .encode())
elif choice == "2" :
conn.sendall( "Enter ciphertext: \n " .encode())
ciphertext = str (conn.recv( 4096 ).decode().strip())
decrypted_text = decrypt(ciphertext.lower(), key)
conn.sendall( f "Decrypted text: { decrypted_text }\n\n " .encode())
elif choice == "3" :
encrypted_flag = encrypt(flag, key)
conn.sendall( f "Your flag: { encrypted_flag }\n\n " .encode())
key = generate_key()
Однако зашифровав текст вида aaaaaaaaa{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} (длина должна совпадать с длиной флага), можно вычислить смешения индексов для каждого символа флага. Далее можно просто вычесть смешения символов и получить искомый флаг.
Ниже приведен код для решения данного задания:
alphabet = "abcdefghijklmnopqrstuvwxyz1234567890-_=+!;:?*,."
def calculate_ciphertext_offsets (cipher_text, text):
ciphertext_offsets = []
for i in range ( len (cipher_text)):
if cipher_text[i] in alphabet:
offset = alphabet.index(cipher_text[i]) - alphabet.index(text[i])
else :
offset = 0
ciphertext_offsets.append(offset)
return ciphertext_offsets
def decrypt_flag (encrypted_flag, offsets):
flag = []
for i in range ( len (offsets)):
if encrypted_flag[i] in alphabet:
flag.append(alphabet[alphabet.index(encrypted_flag[i]) - offsets[i]])
else :
flag.append(encrypted_flag[i])
return '' .join(flag)
def main ():
text = input ( "Enter text: \n " )
cipher_text = input ( "Enter ciphertext: \n " )
encrypted_flag = input ( "Enter encrypted_flag \n " )
offsets = calculate_ciphertext_offsets(cipher_text, text)
flag = decrypt_flag(encrypted_flag, offsets)
print ( f "Flag: { flag } " )
main()