Нужно подключиться к socket.cryptohack.org 13377 и получить флаг. Дан исходный код 13377.py и шаблон для подключения pwntools_example.py.
socket.cryptohack.org 13377
Solution
Так-с. Нужно разбираться в задаче. Дан исходный код приложения в 13377.py. Этот код запущен на сервере. Для подключения есть адрес socket.cryptohack.org 13377. Попробую подключиться через netcat:
nc socket.cryptohack.org 13377
А вот ответы от сервера:

Посмотрю исходник кода:
#!/usr/bin/env python3
from Crypto.Util.number import bytes_to_long, long_to_bytes
from utils import listener # this is cryptohack's server-side module and not part of python
import base64
import codecs
import random
FLAG = "crypto{????????????????????}"
ENCODINGS = [
"base64",
"hex",
"rot13",
"bigint",
"utf-8",
]
with open('/usr/share/dict/words') as f:
WORDS = [line.strip().replace("'", "") for line in f.readlines()]
class Challenge():
def __init__(self):
self.no_prompt = True # Immediately send data from the server without waiting for user input
self.challenge_words = ""
self.stage = 0
def create_level(self):
self.stage += 1
self.challenge_words = "_".join(random.choices(WORDS, k=3))
encoding = random.choice(ENCODINGS)
if encoding == "base64":
encoded = base64.b64encode(self.challenge_words.encode()).decode() # wow so encode
elif encoding == "hex":
encoded = self.challenge_words.encode().hex()
elif encoding == "rot13":
encoded = codecs.encode(self.challenge_words, 'rot_13')
elif encoding == "bigint":
encoded = hex(bytes_to_long(self.challenge_words.encode()))
elif encoding == "utf-8":
encoded = [ord(b) for b in self.challenge_words]
return {"type": encoding, "encoded": encoded}
#
# This challenge function is called on your input, which must be JSON
# encoded
#
def challenge(self, your_input):
if self.stage == 0:
return self.create_level()
elif self.stage == 100:
self.exit = True
return {"flag": FLAG}
if self.challenge_words == your_input["decoded"]:
return self.create_level()
return {"error": "Decoding fail"}
import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener
listener.start_server(port=13377)
В списке ENCODINGS можно увидеть используемыe кодировки. В методе create_level класса Challenge можно увидеть создание уровня. По строке self.challenge_words = "_".join(random.choices(WORDS, k=3)) можно понять, что слова подбираются рандомно, кодировка — тоже — encoding = random.choice(ENCODINGS).
Можно пройти это руками… GL HF.
Либо же написать автоматизацию. Сайт CryptoChallenge предлагает сделать это с помощью pwntools. Вполне себе хорошее решение. Но предварительно разберемся со всеми алгоритмами кодирования. Про base64, hex, bigint, utf-8(ascii) я уже рассказывал. Посмотрим на rot13.
ROT-13
ROT-13 — это простейший метод шифрования, относящийся к семейству шифров Цезаря. Он работает путем сдвига каждой буквы латинского алфавита на 13 позиций:

Оригинальная строка — ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz, строка для перестановок — NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm. Напишу простую реализацию на Python:
orig = 'abcdefghijklmnopqrstuvwxyz'
def encode(s: str, shift: int = 13) -> str:
encoded_str = []
shift = shift % 26
for c in s:
if 'a' <= c <= 'z':
pos = ord(c) - ord('a')
pos += shift
pos %= 26
c = chr(ord('a') + pos)
elif 'A' <= c <= 'Z':
pos = ord(c) - ord('A')
pos += shift
pos %= 26
c = chr(ord('A') + pos)
encoded_str.append(c)
return ''.join(encoded_str)
def decode(s: str, shift: int = 13) -> str:
decoded_str = []
shift = shift % 26
for c in s:
if 'a' <= c <= 'z':
pos = ord(c) - ord('a')
pos -= shift
pos %= 26
c = chr(ord('a') + pos)
elif 'A' <= c <= 'Z':
pos = ord(c) - ord('A')
pos -= shift
pos %= 26
c = chr(ord('A') + pos)
decoded_str.append(c)
return ''.join(decoded_str)
if __name__ == '__main__':
s = 'Coffee Cube'
for i in range(-26, 26):
encoded_s = encode(s, i)
decoded_s = decode(encoded_s, i)
print(i, encoded_s, decoded_s)
С алгоритмом мы разобрались. Теперь можно с чистой совестью использовать готовые решения)
pwntools
Это удобная Python-библиотека, предназначенная для эксплуатации уязвимостей, CTF-задач и автоматизации взаимодействия с бинарями.
Тула действительно удобная. Поэтому начнем с ней знакомство. Шаблон для работы с сервером уже есть. Разберу его:
from pwn import * # pip install pwntools
import json
r = remote('socket.cryptohack.org', 13377, level = 'debug') # Указывается удаленный сервер для взаимодействия
def json_recv():
line = r.recvline() # Получение строки до символа `\0` или `\n`
return json.loads(line.decode())
def json_send(hsh):
request = json.dumps(hsh).encode()
r.sendline(request) # Отправить строку
received = json_recv()
print("Received type: ")
print(received["type"])
print("Received encoded value: ")
print(received["encoded"])
to_send = {
"decoded": "changeme"
}
json_send(to_send)
json_recv()
Запущу шаблон:

Видно, что код уже корректно парсит приходящие данные. Напишу автоматизацию. Можно использовать реализации из предыдущих разборов. Я буду использовать и то, и другое.
from pwn import * # pip install pwntools
import json
import base64
r = remote('socket.cryptohack.org', 13377, level = 'info')
def json_recv():
line = r.recvline()
return json.loads(line.decode())
def json_send(hsh):
request = json.dumps(hsh).encode()
r.sendline(request)
def decode_utf(s: bytes):
return ''.join([chr(c) for c in s])
def decode_hex(s: bytes):
return bytes.fromhex(s).decode()
def decode_base64(s: str):
s = base64.b64decode(s).decode()
return s
def decode_bigint(s: str):
ans = []
for i in range(2, len(s), 2):
ans.append(chr(int(s[i:i+2], 16)))
return ''.join(ans)
def decode_rot(s: str, shift: int = 13) -> str:
orig = 'abcdefghijklmnopqrstuvwxyz'
decoded_str = []
shift = shift % 26
for c in s:
if 'a' <= c <= 'z':
pos = ord(c) - ord('a')
pos -= shift
pos %= 26
c = chr(ord('a') + pos)
elif 'A' <= c <= 'Z':
pos = ord(c) - ord('A')
pos -= shift
pos %= 26
c = chr(ord('A') + pos)
decoded_str.append(c)
return ''.join(decoded_str)
ENCODINGS = {
"base64": decode_base64,
"hex": decode_hex,
"rot13": decode_rot,
"bigint": decode_bigint,
"utf-8": decode_utf,
}
for _ in range(100):
received = json_recv()
encode_type = received["type"]
s = received["encoded"]
print("Received type:", encode_type)
print("Received encoded value:", s)
f = ENCODINGS[encode_type]
decoded = f(s)
print(decoded)
to_send = {
"decoded": decoded
}
json_send(to_send)
print('\nFlag is:', json_recv()['flag'])
Запустил скрипт.

Чтобы убрать кучу отладочных сообщений, можно включить другой режим вот тут:
r = remote('socket.cryptohack.org', 13377, level = 'info')
Flag is: crypto{3nc0d3_d3c0d3_3nc0d3}