/Разборы задач
31 октября 2026
Райтап на задачу «Ах#енный зум»
Разбор задачи «Ах#енный зум»: распознаём жёлтые точки и извлекаем закодированные данные.13
8

Crypto
Задача «Послание от Француза»
Заглянем в текстовый документ, что там написано:
Wmits, izxdiamo! Gsj tbnx ipfa ftpfvh xv Pimcb-Mwygkxqr-hco-Amdcim. Mi abmqtl futdzqiri, al Q htkflis bl ziicov mi. JroGIN{A0VX_U0ZD3B_XW3_S3V}Видно намёк на флаг, но его нереально разобрать. Поэтому смотрим, что в описании задачи нам дано на разгадку. Письмо от ФРАНЦУЗА, фотография ПИКСИ, Её имя — КЛЮЧ, не на русском. Главная подсказка здесь, что у нас есть шифр с ключом. Гуглим или «шифр с ключом», или «французский шифр с ключом» и получаем первым результатом — шифр Виженера.
Теперь, когда мы знаем, что это за шифр, воспользуемся дешифратором. Подойдёт для этого dcode, универсальный сайт с кучей других шифров. Вставляем текст в поле для дешифрации, указываем ключ PIXIE и дешифруем.
В итоге мы расшифровали послание и получили флаг!
Вау, этот шифр можно сбрутить.
Первый скрипт key-len.py
import string
from collections import Counter
def quick_key_length_analysis(ciphertext):
cipher_letters = ''.join(c.upper() for c in ciphertext if c.isalpha())
for key_length in range(1, 16):
subsequences = ['' for _ in range(key_length)]
for i, char in enumerate(cipher_letters):
subsequences[i % key_length] += char
ics = []
for subseq in subsequences:
if len(subseq) > 1:
n = len(subseq)
freq = Counter(subseq)
ic = sum(count * (count - 1) for count in freq.values()) / (n * (n - 1))
ics.append(ic)
if ics:
avg_ic = sum(ics) / len(ics)
bar = "█" * int(avg_ic * 150)
marker = " 🎯" if 0.060 < avg_ic < 0.070 else ""
print(f"Длина {key_length:2d}: {avg_ic:.4f} {bar}{marker}")
ciphertext = "Wmits, izxdiamo! Gsj tbnx ipfa ftpfvh xv Pimcb-Mwygkxqr-hco-Amdcim. Mi abmqtl futdzqiri, al Q htkflis bl ziicov mi. JroGIN{A0VX_U0ZD3B_XW3_S3V}"
quick_key_length_analysis(ciphertext)Сперва высчитываем длину ключа, используя метод индекса совпадений (Index of Coincidence, IC) — статистическую метрику, которая измеряет вероятность того, что две случайно выбранные буквы в тексте окажутся одинаковыми. Для каждой предполагаемой длины ключа (от 1 до 15) скрипт разбивает шифртекст на подпоследовательности (каждая i-я буква с шагом, равным длине ключа), затем вычисляет IC для каждой подпоследовательности.
Второй скрипт key-brut.py
import string
import itertools
import time
def vigenere_decrypt(ciphertext, key):
result = []
key = key.upper()
key_length = len(key)
key_as_int = [ord(i) - ord('A') for i in key]
key_index = 0
for char in ciphertext:
if char.isalpha():
base = ord('A') if char.isupper() else ord('a')
shift = key_as_int[key_index % key_length]
decrypted_code = (ord(char) - base - shift) % 26
decrypted = chr(decrypted_code + base)
result.append(decrypted)
key_index += 1
else:
# Неалфавитные символы добавляем без изменений
result.append(char)
return ''.join(result)
ciphertext = "Wmits, izxdiamo! Gsj tbnx ipfa ftpfvh xv Pimcb-Mwygkxqr-hco-Amdcim. Mi abmqtl futdzqiri, al Q htkflis bl ziicov mi. JroGIN{A0VX_U0ZD3B_XW3_S3V}"
known_plaintext = "BugCTF"
iteration = 0
start_time = time.time()
alphabet = string.ascii_uppercase
for key_tuple in itertools.product(alphabet, repeat=5):
iteration += 1
key = ''.join(key_tuple)
decrypted = vigenere_decrypt(ciphertext, key)
if known_plaintext in decrypted:
elapsed_time = time.time() - start_time
print(f"\nКлюч найден на итерации {iteration} за {elapsed_time:.2f} секунд")
print(f"🔑 Ключ: {key}")
print(f"📜 Расшифрованный текст: {decrypted}")
breakДалее просто перебираем все возможные варианты ключа и ищем в расшифрованном тексте вхождение известного куска текста — BugCTF.
Чтобы пробрутить ключ длиной в 5 символов, который состоит из алфавита 26 символов, надо проверить 26 в 5 степени вариантов — это чуть более 11 млн. Но это небольшое значение, скрипт может проверить все варианты за 2–4 минуты.