Шифрование строк и расшифровка при анализе

Строки выдают логику программы, поэтому их шифруют — учимся понимать, как и где аналитик восстанавливает их обратно.

Шифрование строк — приём, при котором текстовые данные хранятся в файле в преобразованном виде и расшифровываются в памяти прямо перед использованием, чтобы простой поиск по строкам ничего не выдал.

Строки — самая ценная зацепка при реверсе: сообщения об ошибках, форматы лицензий, URL, имена ключей, пути. Поэтому первый шаг защиты — спрятать их. В файле строка лежит как массив «мусорных» байт, а в коде рядом есть функция-декодер, которая восстанавливает оригинал перед тем, как его показать или использовать. Аналитику нужно найти эту функцию, понять алгоритм и ключ — и тогда все спрятанные строки раскрываются разом. Разбираем принцип на безопасном примере.

Зачем это знать защитнику

Для аналитика расшифровка строк часто — самый быстрый путь к пониманию программы: восстановив строки, вы сразу видите, что код делает (к каким адресам обращается, какие сообщения выводит). Для разработчика знание этой техники задаёт верные ожидания: спрятать строку ≠ защитить секрет, ведь ключ всё равно лежит рядом в том же файле. Тренируемся на своих образцах и учебных заданиях (CTF, juice-shop, DVWA, собственные сборки).

Почему именно XOR так распространён

Самый частый «школьный» способ спрятать строки — побайтовый XOR с ключом. Он популярен по трём причинам: тривиально реализуется, быстр и симметричен — одна и та же операция и шифрует, и расшифровывает. XOR обладает ключевым свойством: применённый дважды с тем же ключом, он возвращает исходное значение, потому что a XOR k XOR k = a. Это не криптографическая защита (ключ рядом, статистика утекает), но для сокрытия строк от поверхностного взгляда хватает. Покажем расшифровку спрятанной строки:

# спрятанные байты строки и однобайтовый ключ (учебный пример)
encoded = [0x2a, 0x27, 0x2e, 0x2e, 0x2d, 0x62, 0x35, 0x2d, 0x30, 0x2e, 0x26]
key = 0x42

decoded = "".join(chr(b ^ key) for b in encoded)
print("Расшифрованная строка:", decoded)

# симметрия: повторный XOR теми же байтами возвращает исходные данные
re_encoded = [ord(c) ^ key for c in decoded]
print("Совпало с исходными байтами:", re_encoded == encoded)

Вывод:

Расшифрованная строка: hello world
Совпало с исходными байтами: True

В файле было бы видно лишь набор байт 2a 27 2e 2e 2d ... — поиск по тексту «hello world» ничего не нашёл бы. Декодер же восстанавливает строку перед использованием. Заметьте: ключ 0x42 хранится прямо в программе — иначе она не смогла бы расшифровать строку сама. В этом и состоит фундаментальная слабость приёма для защиты секретов.

Как аналитик находит ключ и алгоритм

Аналитик не угадывает ключ наудачу — он идёт по следам декодера. Типичные приёмы:

1. Найти функцию-декодер

Спрятанная строка где-то используется, а значит, перед использованием вызывается декодер. Найдя обращения к подозрительному байтовому массиву, аналитик попадает в функцию, которая его обрабатывает: короткий цикл с операцией XOR (или сложением/вычитанием, перестановкой) и константой-ключом. Структура «цикл по байтам + одна арифметическая операция + константа» — характерная подпись строкового декодера.

2. Прочитать ключ из кода

Поскольку программа расшифровывает строки сама, ключ обязан быть ей доступен: как константа в коде, как отдельный массив байт, иногда как производное от чего-то (имени файла, версии). Аналитик читает ключ прямо из дизассемблера. Если ключ — байтовая последовательность, её тоже видно в данных.

3. Динамический способ: дать программе расшифровать самой

Самый надёжный приём — не повторять алгоритм вручную, а позволить программе выполнить свой декодер и подсмотреть результат. В отладчике останавливаются после вызова декодера и читают уже расшифрованную строку из памяти. Это работает с любым, даже хитрым алгоритмом, потому что не нужно его воспроизводить — достаточно поймать готовый результат. Идея та же, что с распаковкой: рано или поздно настоящие данные появляются в памяти.

Когда шифрование строк сложнее XOR

Встречаются более серьёзные варианты: многобайтовые ключи, разные ключи на строку, стандартные алгоритмы (AES) с ключом, спрятанным в коде, или строки, собираемые из кусочков. Принцип анализа не меняется: найти, где данные превращаются в читаемый вид, и либо восстановить алгоритм с ключом, либо снять результат из памяти динамически. Усложнение лишь повышает стоимость, как и в случае обфускации и упаковки.

Как это работает под капотом

Ключевое наблюдение то же, что во всём разделе: чтобы строку использовать (вывести, сравнить, передать в системный вызов), программа обязана иметь её в открытом виде в памяти хотя бы на мгновение. Значит, существует момент и место, где открытая строка доступна. Статически аналитик восстанавливает алгоритм и ключ из кода; динамически — просто читает расшифрованное из памяти в нужный момент. Оба пути ведут к одному: спрятанная строка перестаёт быть секретом.

Как защититься

Выводы для разработчика и аналитика:

  • Не прячьте секреты в строках клиента. Ключи API, пароли, токены не спасает никакое строковое шифрование — ключ расшифровки лежит рядом. Секреты держат на сервере или в защищённом хранилище ОС, а не в коде, попадающем к пользователю.
  • Строковое шифрование — это анти-сигнатура и анти-«поверхностный взгляд», не защита. Оно мешает быстрому grep по строкам и сигнатурам, но не серьёзному анализу.
  • Для аналитика (Blue Team): расшифровка строк часто раскрывает назначение образца за минуты — ищите функцию-декодер и снимайте результат из памяти в изолированной ВМ.
  • XOR — не криптография. Никогда не используйте XOR с зашитым ключом как настоящее шифрование данных; для реальной защиты — проверенные алгоритмы и правильное управление ключами вне клиента.

Анализ и расшифровка строк законны на своих программах, учебных образцах и CTF. Несанкционированный доступ к чужим данным и системам наказуем (ст. 272–273 УК РФ); работайте только с тем, что вам разрешено.

Итоги

  • Строки шифруют, чтобы скрыть логику программы от поиска по тексту и сигнатур; декодер восстанавливает их в памяти перед использованием.
  • XOR с ключом популярен из-за простоты, скорости и симметрии (двойной XOR тем же ключом возвращает исходные данные).
  • Аналитик находит функцию-декодер, читает ключ из кода или снимает уже расшифрованную строку из памяти динамически.
  • Ключ всегда доступен самой программе, поэтому строковое шифрование не защищает секреты — это лишь барьер от поверхностного анализа.
  • Настоящую защиту секретов дают серверная логика и проверенная криптография с ключами вне клиента, а не сокрытие строк.
Проверьте себя
1. Почему XOR так часто используют для сокрытия строк в программах?
AПотому что это стойкое шифрование
BПотому что он простой, быстрый и симметричный — одна операция и шифрует, и расшифровывает
CПотому что он не требует ключа
DПотому что его невозможно обнаружить
2. Какой динамический приём надёжнее всего раскрывает спрятанную строку при любом алгоритме?
AУгадать ключ перебором
BДать программе выполнить свой декодер и прочитать уже расшифрованную строку из памяти
CУдалить функцию-декодер
DПовысить энтропию файла
3. Почему строковое шифрование не защищает секрет, зашитый в клиентский код?
AПотому что строки нельзя зашифровать
BПотому что ключ расшифровки доступен самой программе и лежит рядом в том же файле
CПотому что XOR меняет поведение программы
DПотому что аналитик всегда знает пароль