Шифрование строк и расшифровка при анализе
Строки выдают логику программы, поэтому их шифруют — учимся понимать, как и где аналитик восстанавливает их обратно.
Шифрование строк — приём, при котором текстовые данные хранятся в файле в преобразованном виде и расшифровываются в памяти прямо перед использованием, чтобы простой поиск по строкам ничего не выдал.
Строки — самая ценная зацепка при реверсе: сообщения об ошибках, форматы лицензий, 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 тем же ключом возвращает исходные данные).
- Аналитик находит функцию-декодер, читает ключ из кода или снимает уже расшифрованную строку из памяти динамически.
- Ключ всегда доступен самой программе, поэтому строковое шифрование не защищает секреты — это лишь барьер от поверхностного анализа.
- Настоящую защиту секретов дают серверная логика и проверенная криптография с ключами вне клиента, а не сокрытие строк.