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("\nWelcome. You can encrypt or decrypt some text or get the flag.\nThe encryption key changes every time you get a flag.\n".encode())
while True:
conn.sendall("1 - Encrypt\n2 - Decrypt\n3 - Get flag\n0 - 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("\nWelcome. You can encrypt or decrypt some text or get the flag.\nThe encryption key changes every time you get a flag.\n".encode())
while True:
conn.sendall("1 - Encrypt\n2 - Decrypt\n3 - Get flag\n0 - 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()