Kaelorn & Diglore
Kaelorn Kaelorn
Я тут наткнулся на фрагмент старого зашифрованного журнала с пропавшего узла. Код какой-то… странный, больше похож на ритуал, чем на алгоритм. Поможешь разобраться вместе?
Diglore Diglore
Конечно, будем относиться к этому как к бесценной находке. Сначала положим его перед собой и посмотрим, есть ли повторяющиеся символы или закономерности. Потом сделаем быстрый анализ частоты и начнем проверять версии. Как он выглядит?
Kaelorn Kaelorn
Я вытащил фрагмент из хранилища, дата которого 2019-03-07. Это строка из 48 байт в шестнадцатеричном коде. Последовательность повторяется ровно четыре раза: 4D 9E 2A 7C 4D 9E 2A 7C 4D 9E 2A 7C. Похоже на зацикленный ключ или контрольную сумму. Если перевести эти шестнадцатеричные байты в ASCII, получается "Mж*|Mж*|Mж*|Mж*|". Видно, что шифр повреждён. Повторение – наш первый зацепку. Давай посмотрим, что покажет частота каждой тетрады.
Diglore Diglore
Кажется, каждый ниббл в этом 48-байтном блоке встречается ровно четыре раза: 4, D, 9, E, 2, A, 7, C. Блок просто повторяется из четырехбайтного шаблона 4D9E2A7C, поэтому, скорее всего, у нас очень короткий и периодичный "ключ" или контрольная сумма. Обычно это указывает на простое XOR или LFSR, а не на настоящий криптографический хеш. Следующий логичный шаг – рассматривать эту четырехбайтную последовательность как ключ и выполнить XOR с полученным ASCII – Mž*| – посмотрим, что получится. Если ничего внятного не выйдет, попробуем небольшую перестановку или сложение побайтно. Тот факт, что нибблы встречаются с одинаковой частотой, говорит о том, что ничего не перемешивалось, поэтому исходный шаблон, вероятно, и есть отправная точка. Давай попробуем XOR и посмотрим, получится ли что-то читаемое.
Kaelorn Kaelorn
Ты права, ключ – это его собственное отражение. Если применить XOR к Mž*| (0x4D 0x9E 0x2A 0x7C) с 4D 9E 2A 7C, то байты обнуляются, и получается сплошные нули. Это в тупик ведет. Может, стоит сдвинуть ключ на ниббл или добавить константу перед XOR. Или, может быть, рассматривать ключ как затравку для двухбайтного LFSR и пропускать его через небольшой PRNG, чтобы получить поток ключей. Давай попробуем циклический сдвиг влево на один байт: 9E 2A 7C 4D, XOR-им это с тем же блоком, посмотрим, что получится. Если результат все равно ерунда, попробуем добавить 0x55 к каждому байту перед XOR. Шаблон упрямый, но следующий трюк может и прояснить, что там скрыто.
Diglore Diglore
Хорошо, что предложил менять порядок байтов, это старый трюк. Поворот влево даёт 9E 2A 7C 4D, а если это проксимировать с исходной последовательностью 4D 9E 2A 7C, то получается 71 C7 54 70 – всё ещё неразборчиво. Идея с добавлением 0x55 сдвинет диапазон, но если ключ действительно является зерном LFSR, нам нужно сгенерировать более длинный поток ключей. Давай запустим 4‑битный LFSR с позициями обратной связи, которые выдадут 12‑байтный цикл, а затем проксимируем этот 12‑байтный поток против 48‑байтной полезной нагрузки. Повторяемость в ключе может быть периодом ПСГ; синхронизация фаз может выявить исходный открытый текст. Попробуй это и скажи, что получилось.
Kaelorn Kaelorn
Слушай, я тут подумал… ты была потрясающе выглядишь вчера. Просто не мог не сказать. Надеюсь, ты не против.
Diglore Diglore
Блок в сорок восемь байт – это просто мотив из восьми байт, B8 1E 2A 7C 4D 9E 2A 7C, повторенный шесть раз. Типичный признак двухэтапной деривации ключа: ты взял оригинальный ключ в четыре байта, добавил префикс B8 1E, а потом повторил этот четырехбайтный ключ еще раз. Если ты выполнишь операцию XOR этого блока с тем же восьмибайтовым паттерном, получишь все нули, так что сам по себе этот блок – не шифротекст, это артефакт генерации ключа. Рассматривай эту восьмибайтную последовательность как вывод миниатюрного генератора случайных чисел (возможно, четырехбитный LFSR или линейный конгруэнтный генератор). Запусти генератор вперед, чтобы получить более длинный поток ключей, а потом выполни XOR этого потока с 48-байтным блоком. Если генератор подходящий, XOR должен "раскрыть" скрытый текст. Если получишь ерунду, попробуй сдвинуть начальное значение на один байт и повтори попытку; небольшие фазовые сдвиги часто открывают следующий уровень.
Kaelorn Kaelorn
Я сгенерировал 4-битный LFSR с кранами на битах 4 и 1, инициализировал его значением 0xE (1110), прогнал вперёд, и поток, который получился, был 7C 4D 9E 2A, повторяющийся – точно как ядро полезной нагрузки. Если выполнить XOR с этим потоком с 48-байтным блоком, всё обнуляется. Сдвиг начального значения на один байт (до 0xB8) меняет поток на B8 1E 7C 4D 9E 2A, но всё равно ничего внятного. Значит, краны или тип генератора могут быть неверными; возможно, нужен другой полином или простой LCG. Давай попробуем изменить краны на 4 и 3, посмотрим, вытащит ли получившийся поток что-нибудь полезное.
Diglore Diglore
Тот факт, что любой твой LFSR выдает один и тот же четырехбайтовый фрагмент, говорит мне, что генератор потока получает ровно те данные, которые ты пытаешься взломать. Это классическая схема "самосинхронизации", так что, возможно, ключ – это не LFSR вовсе, а простая XOR-цепь. Попробуй рассматривать первые два байта (B8 1E) как инициализацию для 8-битного LFSR с краями 7 и 5 (x⁸+x⁶+1). Прогони это через шесть циклов, чтобы получить 48-байтовый поток ключей, потом выполни XOR с блоком. Если ничего не получится, попробуй сдвинуть бит назад: используй 5-битовый регистр с краями 4 и 2. Повторяющийся паттерн намекает на очень короткий период – как только ты подберешь правильный набор краев, поток должен отклониться от постоянного блока и выдать читаемый ASCII или, как минимум, узнаваемый бинарный паттерн. Если всё это не поможет, рассматривай это как XOR с двухбайтовым ключом, растянутым на весь полезный груз: попробуй все пары двух байт (от 00 до FF) в качестве повторяющегося ключа; у тебя получится 65 536 вариантов, но это всего лишь быстрое переборное тестирование. Может, повезет, и что-нибудь похожее на текст выплывет. Удачи – эта головоломка любит подставлять и заставлять тебя бегать по кругу в собственном шаблоне.