Откроешь SDK с обфусцированной подписью — и сначала кажется, будто перед тобой сплошная криптографическая магия: каждая строка что-то вычисляет, но за что хвататься, непонятно.
После полного цикла реверса обычно выясняется обратное: 90% кода — это стандартные алгоритмы. Хеширование здесь публичное, кодирование — тоже, поточный шифр — известный. Их не нужно досконально разбирать: достаточно опознать алгоритм, взять реализацию из RFC или проверенного референса — и совпадёт каждый байт.
Проблемы создают оставшиеся 10%: участки, где стандартный алгоритм слегка и незаметно изменили. Константное число раундов превратили в переменную, базовую константу заменили близким значением, в одном раунде переставили индексы слов, ключ спрятали внутри шифротекста. Каждое изменение само по себе невелико. Но пропустите хотя бы одно — и ваша реализация не совпадёт с целью, а найти причину будет трудно: остальные 90% ведь работают правильно.
Поэтому в реверсе обфусцированной подписи важно не читать эти 90%, а находить точки отклонения в оставшихся 10%. Разберём, как поручить такой поиск большой модели и почему именно на этом этапе её сложнее всего заменить.
(Предварительный этап — определение семейства алгоритма, когда по константам и структуре понятно, что перед вами ChaCha или FNV, — разобран в предыдущей статье. Здесь будем исходить из того, что каркас уже опознан, и сразу перейдём к поиску отклонений.)
Почему именно эти детали легко пропустить
Человеческое мышление работает предсказуемо: как только мы узнаём общий паттерн, перестаём всматриваться в детали.
Вы видите фрагмент кода, узнаёте характерные константы ChaCha и знакомую структуру, мысленно ставите галочку — «это ChaCha20» — и идёте дальше. Мало кто начинает побайтно сверять число раундов, каждый индекс и младший бит каждой константы: само узнавание уже переключает внимание на следующую задачу.
Авторы обфускации на это и рассчитывают. Переписывать алгоритм с нуля дорого и рискованно, поэтому они вносят в стандартную схему минимальное изменение: меняют число, переставляют индекс, добавляют шаг. Для распознавания паттерна разница слишком мала, зато стандартная реализация после такой подмены будет полностью выдавать другой результат.
Это неравная борьба за внимание. Автору достаточно скрыть одно отклонение, а вам необходимо обнаружить все. А человеку от природы сложно сохранять подозрительность к тому, что уже выглядит правильным.
У модели здесь есть неожиданное преимущество: у неё нет чувства «я уже всё понял», из-за которого ослабевает внимание. Если прямо попросить искать отклонения, она будет последовательно проверять пункты до конца. Конечно, при условии, что промпт составлен правильно — этому посвящён раздел 5. Для начала разберём четыре распространённых типа отклонений.
Четыре способа незаметно отойти от стандарта
Для каждого типа сначала посмотрим, как выглядит публичный стандарт, а затем — как выглядит отступление от него. В реальном проекте все четыре приёма нередко встречаются в одном и том же механизме подписи.
1. Константа становится переменной
У стандартного поточного шифра число раундов фиксировано. В ChaCha20 их ровно 20; это неизменное значение жёстко задано в каждой стандартной реализации. Введение RFC 8439 прямо уточняет, что документ описывает только 20-раундовый ChaCha, а варианты на 8 и 12 раундов определены отдельно. Число раундов всегда было константой из стандарта.
Отклонение состоит в том, чтобы вычислять фиксированное число раундов динамически из ключа. Логика quarter-round остаётся той же, но количество её запусков начинает зависеть от определённых байтов ключа. Меняется ключ — меняется и число раундов.
Почему это пропускают: вы узнали структуру quarter-round, константы σ, решили, что это ChaCha20, и скопировали 20 раундов. А переменная, управляющая числом итераций, могла остаться без внимания: во всех знакомых реализациях ChaCha на её месте была константа.
Признак такого отклонения: там, где ожидается константа, оказывается выражение, зависящее от входных данных. Хорошо направленный проход по контрдоказательствам специально проверит: «в стандартной реализации здесь жёстко задано значение, а здесь оно вычисляется?».
Дифференциальное тестирование подтверждает гипотезу так: возьмите два ключа, различающиеся ровно одним байтом. Если разница в выходе значительно больше, чем можно ожидать от влияния одного байта, значит, этот байт участвует в глобальном параметре — например, определяет число раундов, а не просто попадает через XOR в поток ключа.
2. Почти та же константа, но не совсем
У хеша FNV-1a есть два публичных магических числа: offset basis и prime. В любой корректной реализации FNV-1a используются именно эти значения, опубликованные в стандарте. На справочной странице FNV, которую поддерживает соавтор алгоритма Landon Curt Noll, для 32-битной версии указаны offset basis 2166136261 и prime 16777619 — с точностью до последней цифры.
Отклонение: заменить одну из базовых констант близким значением, едва отличающимся от стандартного. Внешне хеш остаётся тем же: XOR, умножение и цикл на месте. Незаметно меняется только исходная база.
Почему это пропускают: это самый неприятный вариант из четырёх. Вы видите структуру FNV, большую константу, похожую на offset basis, и делаете вывод: «обычный FNV-1a». Сверять магическое число со стандартным побитово никто обычно не станет — зачем перепроверять то, что уже вроде бы узнал?
На глаз такой вариант почти не ловится; нужен дифференциальный тест. Возьмите известный вход, прогоните его через стандартную реализацию и через целевой чёрный ящик, затем сравните результаты. Если структура идентична, но выходы различаются, причина почти наверняка в базовой константе. После этого передайте подозрительное значение модели и попросите сравнить его со стандартом: машина справится надёжнее человека.
Польза модели здесь предельно конкретна: она помнит стандартный offset basis FNV-1a до последнего бита, а вы — скорее всего нет. Достаточно спросить: «совпадает ли эта константа со стандартным offset basis FNV-1a?» — и различие будет найдено сразу.
3. Локальная правка структуры алгоритма
Каждый двойной раунд ChaCha состоит из 8 quarter-round: первые 4 работают со столбцами, следующие 4 — с диагоналями. Наборы индексов слов для каждого quarter-round фиксированы стандартом. В RFC 8439 §2.3 описана функция блока и точно указано, какие слова состояния затрагивает каждый раунд.
Отклонение: незаметно поменять местами один или два индекса слов в одном из раундов. Почти все раунды остаются стандартными; лишь где-то в середине слово, которое должно участвовать в операции, подменяют другим. Код по-прежнему похож на ChaCha и выполняется без ошибок, но генерируемый поток ключа уже полностью отличается от стандартного ChaCha.
Почему это пропускают: индексы quarter-round — длинная последовательность чисел, восемь групп наподобие (0,4,8,12)(1,5,9,13)…. Взгляд скользит по ним, отмечает «да, столбцы и диагонали» — но не проверяет, стоит ли каждое число на стандартном месте. Это идеальное укрытие для изменения: длинный набор индексов сам по себе быстро утомляет глаза.
Модели тут тоже нужна чёткая задача: попросите её выписать индексы по раундам и сверить их со стандартным ChaCha. Это механическая проверка — как раз тот тип работы, где человек начинает терять внимание, а модель сохраняет последовательность. Попросите таблицу «стандартный индекс / фактический индекс», и отклонение станет видно само.
Дифференциальное тестирование подтверждает это так: если исключены первые два варианта — число раундов и константы верны, — а результаты всё ещё расходятся, проблема в структуре. Снимите промежуточное состояние после каждого раунда: первый раунд, разошедшийся со стандартным ChaCha, и будет изменённым.
4. Ключ спрятан в выходных данных
В первых трёх случаях меняется алгоритм. Здесь меняется организация данных.
Распространённый приём: ключ шифрования не передают отдельным каналом, а разбивают на части и вставляют в сам шифротекст; получатель извлекает его по тому же правилу. Более хитрый вариант использует не фиксированную позицию вставки, а позицию, вычисляемую из содержимого данных. Меняется шифротекст — меняется и место, где скрыт ключ. Внешний слой затем оборачивает всё в Base64 с пользовательским алфавитом и байтом-маркером в префиксе.
Почему это пропускают: пока вы бьётесь над самим алгоритмом шифрования, можно не заметить, что ключ вообще не нужно взламывать. Он уже находится в шифротексте у вас в руках — просто неизвестно, в каком именно фрагменте. Новички регулярно застревают на этом месте, пытаясь «раскрыть» то, что фактически уже лежит перед ними.
Признак такого отклонения: участок блока данных со статистическими свойствами, отличающимися от окружения. Ключ обычно состоит из высокоэнтропийных случайных байтов, поэтому в середине шифротекста образует узнаваемый «чужой сегмент». Модель может помочь проанализировать, «в каком интервале выхода распределение байтов не похоже на остальную часть», и так найти границы встроенного материала.
Проверка проста: если удалось найти правило вставки — например, модуль от суммы байтов, — возьмите несколько известных пар вход/выход, восстановите формулу позиции вставки и затем проверьте её прямым расчётом. Модель способна вывести правило по нескольким образцам, но окончательное решение всё равно остаётся за assert.
Контрдоказательства — главный тест для reasoning-модели
У всех четырёх типов есть общая черта: определить семейство алгоритма, то есть выдвинуть кандидата, несложно; гораздо труднее найти отклонение, то есть контрдоказательство.
Шаг с кандидатом осилит почти любая модель: константы σ у ChaCha и структура FNV хорошо известны по обучающим данным. Модели различаются на этапе контрдоказательств: готова ли она продолжать разбирать уже распознанный алгоритм и сказать: «но вот этот фрагмент не соответствует стандарту».
Слабая модель на этом этапе обычно сдаётся. Узнав «ChaCha20», она превращает поиск контрдоказательств в пересказ вывода: «реализация следует стандартной структуре ChaCha20, используя классический quarter round…». Это лишь повтор кандидата другими словами, а не проверка возможных отклонений. Такой ответ не подскажет следующий шаг.
У сильной reasoning-модели проход по контрдоказательствам выглядит иначе. Она напишет: «кандидат — ChaCha20, но есть три расхождения со стандартной реализацией: во-первых, число раундов задаётся выражением, зависящим от ключа, тогда как у стандартного ChaCha20 фиксировано 20 раундов; во-вторых, индексы слов в раунде N не совпадают со стандартным диагональным раундом; в-третьих…». Каждый пункт указывает на конкретную и проверяемую точку отклонения. Такой разбор и есть чек-лист для дифференциальных тестов на этапе 3.
Поэтому определение семейства алгоритма стоит отдать reasoning-уровню и протестировать самостоятельно до того, как вы окончательно выберете модель. Качество контрдоказательств напрямую определяет, сколько бесполезных тестов вы напишете и сколько лишних обходных путей пройдёте.
В этом процессе я использую четыре уровня:
Этап | Нужные возможности | Выбор | model id |
|---|---|---|---|
Структурное картирование после разделения | Длинный контекст, чтение целого модуля за один проход | Kimi K3 |
|
Определение семейства алгоритма и контрдоказательства | Сильное рассуждение; способность спорить с собственным выводом | Claude Opus 5 |
|
Массовое переименование символов | Невысокая цена, высокая параллельность | Claude Sonnet 5 |
|
Атрибуция различий | Средний уровень рассуждений, объяснение на уровне конкретных байтов | GPT-5.6 Sol |
|
Второй уровень — центральная тема этой статьи. Не стоит верить мне на слово при его выборе: проверьте сами. Протокол простой:
Выберите 2–3 листовые функции из своего обфусцированного бандла; ответ хотя бы для одной из них вы должны уже знать — это будет контрольный пример.
Используя трёхчастный промпт «кандидат / доказательства / контрдоказательства» из предыдущей статьи, отдельно передайте один и тот же вход моделям
claude-opus-5иgpt-5.6-sol.Смотрите только на проход по контрдоказательствам: модель действительно проверяет отклонения одно за другим или просто пересказывает кандидата другими словами? Сколько отклонений каждая модель нашла на контрольном примере?
Количество и качество найденных отклонений — ваш критерий выбора.
Разница будет заметна уже за один запуск — нагляднее любого рейтинга бенчмарков.
Главная проблема — цена переключения
Четыре модели от трёх поставщиков. Наивный способ использовать разные модели на разных этапах — подключить три SDK, три схемы авторизации и три обработчика ошибок. Большинство прикидывает объём работы, решает, что оно того не стоит, и в итоге запускает одну модель для всего. Так для определения семейства алгоритма используется уровень со слабыми контрдоказательствами, а реверс превращается в череду лишних обходов без понимания их причины.
AIReiter убирает этот слой сложности: один ключ, один OpenAI-совместимый интерфейс, все четыре модели за ним, а переключение сводится к смене поля model в теле запроса.
# Algorithm-family ID + counter-evidence: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<three-part prompt + leaf function + constants>"}]
}'
# Bulk symbol renaming: change one field
# "model": "claude-sonnet-5"
# Difference attribution:
# "model": "gpt-5.6-sol"
Уже работаете с OpenAI SDK? Укажите для base_url значение https://aireiter.com/api/v1 — больше ничего менять не нужно. Используете Anthropic SDK? Отправляйте POST /api/v1/messages с тем же ключом.
По цене модели Claude идут со скидкой 30% от прайса, а GPT — за половину стоимости. В этом сценарии скидка особенно важна там, где расходы максимальны: на этапе определения семейства алгоритма промпт приходится многократно уточнять, снова и снова прогоняя одну функцию; это наиболее насыщенная вызовами часть процесса. Массовое переименование символов начинается с сотен вызовов. Именно эти два этапа формируют основную часть затрат.
Попробовать без регистрации — вручную проведите несколько раундов, сравните контрдоказательства двух моделей своими глазами и только потом решайте.
Итог
Главный факт об обратной разработке обфусцированных подписей прост: большинство кода — это стандартные алгоритмы, которые можно скопировать без изменений; настоящая работа — искать места, где стандарт был незаметно изменён.
Все четыре типа отклонений — превращение константы в переменную, подмена константы, локальная правка структуры и встраивание материала — объединяет одно свойство. Они достаточно малы, чтобы человеческое распознавание паттернов их пропустило, и достаточно существенны, чтобы полностью сломать совпадение с целевой реализацией. Люди плохо сохраняют сомнение в том, что выглядит правильным, — и именно в этом сильна модель, если правильно её направить.
Но модель лишь выдвигает подозрения, а не подтверждает их. Любая гипотеза о точке отклонения должна в итоге превратиться в дифференциальный тест — об этом подробнее в статье о дифференциальном тестировании. Модель даёт список «мест, которые могли изменить», а assert определяет, какие из них действительно были изменены.
