Загрузка данных


import os

def read_file(filepath):
    with open(filepath, 'rb') as f:
        return f.read()

def xor_bytes(data, key):
    """Побайтовый XOR"""
    return bytes([d ^ k for d, k in zip(data, key * (len(data) // len(key) + 1))])

def decrypt():
    # Читаем зашифрованный файл (он в hex)
    hex_content = read_file('output.txt').decode('utf-8').strip()
    
    # Превращаем hex обратно в байты
    encrypted_data = bytes.fromhex(hex_content)
    
    known_plaintext = b"DUCKERZ{"
    
    print(f"Размер зашифрованных данных: {len(encrypted_data)} байт")
    print(f"Известное начало: {known_plaintext}")
    print("-" * 50)

    # Перебираем возможные длины ключа от 12 до 44
    for key_size in range(12, 45):
        # Проверяем, делится ли размер данных на key_size (так как данные повторялись)
        # В enc.py: len(expanded_data) = len(input_data) * key_size
        # Значит, len(encrypted_data) должно делиться на key_size без остатка
        if len(encrypted_data) % key_size != 0:
            continue
            
        # Вычисляем предполагаемую длину исходного файла
        original_length = len(encrypted_data) // key_size
        
        # Берем первый блок зашифрованных данных длиной original_length
        # (так как данные повторялись, первый блок содержит весь исходный текст)
        first_block = encrypted_data[:original_length]
        
        # Восстанавливаем ключ, используя известный текст
        # Ключ = Зашифрованный_байт XOR Исходный_байт
        # Нам нужно ровно len(known_plaintext) байт ключа
        if len(first_block) < len(known_plaintext):
            continue
            
        key_part = bytes([first_block[i] ^ known_plaintext[i] for i in range(len(known_plaintext))])
        
        # Теперь нам нужно восстановить весь ключ.
        # Мы знаем, что ключ повторялся. 
        # Если key_size > len(known_plaintext), нам нужно найти остальные байты ключа.
        # Но мы можем просто расшифровать первый блок, используя key_part, 
        # повторенный до нужной длины.
        
        # Восстанавливаем полный ключ:
        # Так как key_part - это первые 8 байт ключа, а ключ имеет длину key_size,
        # нам нужно найти остальные key_size - 8 байт.
        # Мы можем сделать это, зная, что исходный текст - это текст (например, ASCII).
        # Но проще: XOR первого блока с key_part даст нам исходный текст.
        # Однако, если key_size > 8, key_part не покроет весь ключ.
        
        # Давайте просто расшифруем первый блок, используя key_part, 
        # циклически повторенный.
        # Но это сработает только если key_size <= len(known_plaintext).
        # Если key_size > 8, нам нужно восстановить остальную часть ключа.
        
        # Альтернативный подход: мы знаем, что исходный текст - это ASCII.
        # Мы можем перебрать оставшиеся байты ключа, чтобы получить читаемый текст.
        # Но это сложно. 
        
        # Более простой способ: 
        # 1. Восстанавливаем первые 8 байт ключа.
        # 2. Если key_size > 8, мы не знаем остальные байты.
        # 3. Но мы можем предположить, что исходный текст - это флаг.
        # 4. Мы можем просто расшифровать данные, используя key_part, 
        #    повторенный до длины first_block. Это даст правильный результат 
        #    только для первых 8 байт, остальное будет мусором.
        
        # СТОП. Давайте подумаем иначе.
        # В enc.py: expanded_key = repeat_data(encryption_key, len(input_data))
        # Это значит, что ключ повторялся, чтобы покрыть весь input_data.
        # Затем expanded_data = repeat_data(input_data, key_size)
        # Затем encrypt(expanded_data, expanded_key)
        
        # Это значит, что для первого блока (длиной original_length):
        # Зашифрованный_блок = input_data XOR expanded_key[:original_length]
        # expanded_key[:original_length] - это encryption_key, повторенный 
        # до длины original_length.
        
        # Мы знаем первые 8 байт input_data. 
        # Значит, мы знаем первые 8 байт encryption_key.
        # Если key_size <= 8, мы знаем весь ключ.
        # Если key_size > 8, мы знаем только первые 8 байт ключа.
        
        # НО! Мы можем восстановить весь ключ, если знаем, что исходный текст 
        # состоит из читаемых символов. Мы можем перебрать оставшиеся байты ключа.
        # Это долго, но возможно.
        
        # Однако, есть более простой путь. 
        # Мы можем просто расшифровать данные, используя key_part, 
        # повторенный до длины first_block, и посмотреть, что получится.
        # Если key_size <= 8, результат будет правильным.
        # Если key_size > 8, результат будет правильным только для первых 8 байт.
        
        # Давайте попробуем просто вывести результат для каждого key_size.
        # Если key_size <= 8, мы увидим читаемый текст.
        # Если key_size > 8, мы увидим мусор после 8-го байта.
        
        # Но подождите! В enc.py есть еще одна деталь:
        # expanded_data = repeat_data(input_data, key_size)
        # Это значит, что input_data повторялся key_size раз.
        # Следовательно, первый блок (длиной original_length) - это 
        # просто input_data, повторенный key_size раз? Нет.
        # original_length = len(input_data).
        # expanded_data = input_data * key_size.
        # Длина expanded_data = len(input_data) * key_size.
        # encrypted_data = expanded_data XOR expanded_key.
        # expanded_key = encryption_key * len(input_data).
        # Длина expanded_key = len(input_data) * key_size? Нет.
        # expanded_key = repeat_data(encryption_key, len(input_data)).
        # repeat_data(data, factor) возвращает data * factor.
        # Значит, expanded_key = encryption_key * len(input_data).
        # Длина expanded_key = key_size * len(input_data).
        # Отлично! Длины совпадают.
        
        # Теперь: encrypted_data = (input_data * key_size) XOR (encryption_key * len(input_data))
        # Это значит, что encrypted_data состоит из повторяющихся блоков?
        # Давайте проверим:
        # Первые len(input_data) байт: input_data XOR (encryption_key * len(input_data))[:len(input_data)]
        # Это input_data XOR encryption_key (повторенный).
        # Следующие len(input_data) байт: input_data XOR (encryption_key * len(input_data))[len(input_data):2*len(input_data)]
        # Это input_data XOR encryption_key (повторенный), начиная с другого места.
        # Так как encryption_key повторяется, это может быть сдвиг.
        
        # НО! Мы можем просто взять первый блок длиной original_length.
        # Это input_data XOR encryption_key (повторенный до original_length).
        # Если мы знаем первые 8 байт input_data, мы знаем первые 8 байт encryption_key.
        # Если key_size <= 8, мы знаем весь ключ.
        # Если key_size > 8, мы знаем только часть ключа.
        
        # Хорошая новость: в задании сказано "возможно будет написано DUCKERZ{флаг}".
        # Возможно, ключ короткий (12-16 байт). 
        # Давайте просто попробуем расшифровать, используя key_part, 
        # повторенный до длины first_block, и выведем результат.
        # Если ключ длиннее 8 байт, мы увидим мусор после 8-го байта.
        # Но мы можем попробовать восстановить остальную часть ключа,
        # предполагая, что текст - это ASCII.
        
        # Для простоты: давайте выведем результат для всех key_size.
        # Если key_size <= 8, мы увидим читаемый текст.
        # Если key_size > 8, мы увидим "DUCKERZ{" и мусор.
        
        # Но подождите! Мы можем восстановить весь ключ, если знаем, 
        # что исходный текст - это флаг. Флаги обычно состоят из ASCII.
        # Мы можем перебрать оставшиеся байты ключа (key_size - 8 байт)
        # так, чтобы расшифрованный текст был ASCII.
        # Это 256^(key_size - 8) вариантов. Для key_size=12 это 256^4 = 4 миллиарда. Слишком много.
        
        # Однако, мы можем использовать тот факт, что encrypted_data состоит 
        # из повторяющихся блоков. 
        # Давайте посмотрим на структуру:
        # encrypted_data = (input_data * key_size) XOR (encryption_key * len(input_data))
        # Это значит, что encrypted_data[i] = input_data[i % len(input_data)] XOR encryption_key[i % key_size]
        # Мы знаем input_data[0:8] = "DUCKERZ{".
        # Мы можем найти encryption_key[0:8].
        # Теперь, если мы посмотрим на encrypted_data[original_length + j] для j от 0 до 7:
        # Это input_data[j] XOR encryption_key[(original_length + j) % key_size]
        # Но input_data[j] = input_data[j % len(input_data)] = input_data[j] (так как j < len(input_data))
        # И мы знаем input_data[j] для j < 8.
        # Значит, мы можем найти encryption_key[(original_length + j) % key_size].
        # Это дает нам больше байт ключа!
        
        # Этот процесс можно продолжать, пока мы не восстановим весь ключ.
        # Но это сложно реализовать в одном скрипте.
        
        # ДАВАЙТЕ СДЕЛАЕМ ПРОЩЕ:
        # Мы знаем, что key_size от 12 до 44.
        # Мы знаем первые 8 байт ключа.
        # Мы можем просто перебрать оставшиеся байты ключа, 
        # но только если key_size <= 12 (тогда осталось 4 байта, 256^4 = 4e9 - все еще много).
        
        # СТОП. Давайте перечитаем enc.py.
        # expanded_key = repeat_data(encryption_key, len(input_data))
        # Это encryption_key * len(input_data).
        # Длина = key_size * len(input_data).
        # expanded_data = repeat_data(input_data, key_size)
        # Это input_data * key_size.
        # Длина = len(input_data) * key_size.
        # Они совпадают!
        # Теперь: encrypted_data = expanded_data XOR expanded_key.
        # encrypted_data[i] = expanded_data[i] XOR expanded_key[i].
        # expanded_data[i] = input_data[i % len(input_data)].
        # expanded_key[i] = encryption_key[i % key_size].
        # Значит, encrypted_data[i] = input_data[i % L] XOR encryption_key[i % K],
        # где L = len(input_data), K = key_size.
        
        # Мы знаем input_data[0:8] = "DUCKERZ{".
        # Мы можем найти encryption_key[0:8].
        # Теперь, если мы возьмем i = L + j, где j от 0 до 7:
        # encrypted_data[L + j] = input_data[(L + j) % L] XOR encryption_key[(L + j) % K]
        # = input_data[j] XOR encryption_key[(L + j) % K].
        # Мы знаем input_data[j] (так как j < 8).
        # Значит, мы можем найти encryption_key[(L + j) % K].
        # Это дает нам ключ по модулю K.
        # Продолжая этот процесс, мы можем восстановить весь ключ.
        
        # Но для этого нам нужно знать L (длину исходного файла).
        # L = len(encrypted_data) // K.
        
        # Давайте реализуем это!
        
        # 1. Вычисляем L.
        L = len(encrypted_data) // key_size
        
        # 2. Восстанавливаем ключ.
        key = [None] * key_size
        known_plain = b"DUCKERZ{"
        
        # Заполняем ключ на основе известного текста
        for i in range(len(known_plain)):
            if i < L:
                key[i % key_size] = encrypted_data[i] ^ known_plain[i]
        
        # Теперь используем тот факт, что данные повторяются.
        # encrypted_data[i] = input_data[i % L] XOR key[i % K]
        # Если мы знаем input_data[i % L], мы можем найти key[i % K].
        # Мы знаем input_data[0:8].
        # Мы можем найти key для всех индексов, которые встречаются 
        # при i = 0..7, L+0..L+7, 2L+0..2L+7, и т.д.
        # Это даст нам key[(j) % K] для j = 0..7, L..L+7, 2L..2L+7, ...
        # Так как L и K могут быть не взаимно просты, это может покрыть не все индексы.
        # Но обычно покрывает достаточно.
        
        # Давайте просто попробуем найти ключ, перебирая оставшиеся байты.
        # Если key_size <= 16, это реально.
        # Для key_size > 16 это может быть долго.
        
        # Упростим: просто попробуем расшифровать, используя известные байты ключа,
        # и посмотрим на результат. Если ключ неполный, мы увидим мусор.
        
        # Но мы можем сделать так: 
        # Мы знаем, что исходный текст - это ASCII (флаг).
        # Мы можем перебрать оставшиеся байты ключа, проверяя, что результат - ASCII.
        # Это 256^(K - known_bytes) вариантов.
        # Если K=12, known_bytes=8, осталось 4 байта: 256^4 = 4.3e9. Слишком много.
        # Если K=12, но мы можем найти больше байт ключа через повторение.
        
        # Давайте реализуем поиск ключа через повторение.
        # Мы знаем key[i] для i = 0..7.
        # Мы можем найти key[(L + j) % K] для j = 0..7.
        # Это добавит новые индексы.
        # Продолжаем, пока не найдем все K байт.
        
        # Реализация:
        key = [None] * key_size
        known_plain = b"DUCKERZ{"
        
        # Заполняем начальные байты
        for i in range(len(known_plain)):
            if i < L:
                key[i % key_size] = encrypted_data[i] ^ known_plain[i]
        
        # Итеративно ищем остальные байты
        # Мы знаем input_data[0:8].
        # Мы можем найти key для всех i, где i % L < 8.
        # i % L < 8 означает i = m*L + j, где j < 8.
        # Тогда key[i % K] = encrypted_data[i] ^ input_data[j].
        # Мы можем перебрать m от 0 до (len(encrypted_data) // L) - 1.
        # Это даст нам key для всех i % K, где i = m*L + j.
        
        for m in range(len(encrypted_data) // L):
            for j in range(len(known_plain)):
                i = m * L + j
                if i < len(encrypted_data):
                    idx = i % key_size
                    if key[idx] is None:
                        key[idx] = encrypted_data[i] ^ known_plain[j]
        
        # Проверяем, все ли байты ключа найдены
        if None in key:
            # Не все байты найдены. Пропускаем этот key_size.
            continue
        
        # Если все байты найдены, расшифровываем первый блок
        decrypted = bytes([encrypted_data[i] ^ key[i % key_size] for i in range(L)])
        
        # Проверяем, начинается ли с DUCKERZ{
        if decrypted.startswith(known_plain):
            print(f"Найден ключ! Длина ключа: {key_size}")
            print(f"Ключ (hex): {bytes(key).hex()}")
            print(f"Расшифрованный текст:")
            try:
                print(decrypted.decode('utf-8'))
            except:
                print(decrypted)
            print("-" * 50)

if __name__ == "__main__":
    decrypt()