Откройте любой exe-файл в hex-редакторе — и среди машинных кодов вы увидите узнаваемые куски текста: названия кнопок, сообщения об ошибках, пути к библиотекам. Эти строки не появляются в файле случайно: каждая из них попадает туда одним из нескольких строго определённых способов ещё на этапе сборки программы. Понимание этих механизмов полезно с двух сторон: разработчику — чтобы правильно организовать локализацию и не раздувать бинарник, а аналитику или энтузиасту — чтобы понимать, что именно он видит при просмотре строк утилитами вроде strings.
Главный ориентир такой: строки попадают в EXE либо как константы, зашитые компилятором прямо в секции данных, либо как ресурсы — отдельные блоки внутри файла, которые программа загружает по запросу. Всё остальное — кодировки, таблицы локализации, упаковка — лишь вариации этих двух базовых путей.
- Два основных источника строк в исполняемом файле
- Строковые литералы в коде
- Ресурсы
- Что ещё попадает в EXE помимо интерфейсных надписей
- Кодировки: почему одни строки читаются, а другие нет
- ANSI и однобайтовые кодировки
- UTF-16 и широкие символы
- UTF-8
- Как устроена секция ресурсов
- Локализация: три подхода к хранению переводов
- Как извлечь строки из EXE на практике
- Частые заблуждения и ошибки
- Что это значит для разработчика
- Ответы на частые вопросы
- Можно ли изменить текст в готовой программе?
- Почему утилита показывает бессмысленный набор символов вместо русских надписей?
- Где искать строки, если в самом EXE их мало?
- Влияют ли строки на размер файла?
- Практический итог
Два основных источника строк в исполняемом файле
Строковые литералы в коде
Самый прямой путь. Когда программист пишет в исходном коде что-то вроде ShowMessage(«Файл не найден»), компилятор помещает последовательность байтов этой строки в секцию данных (обычно .rdata или .data в PE-файлах), а в код вставляет ссылку на её адрес. Строка живёт в файле «как есть» — в той кодировке, которую выбрал компилятор для литералов.
Важная деталь: одинаковые литералы компиляторы обычно схлопывают в одну копию. Если сообщение «Готово» встречается в коде двадцать раз, в бинарнике чаще всего будет одна физическая строка, на которую указывают двадцать ссылок. Поэтому количество видимых строк в дампе почти всегда меньше, чем количество мест их использования в коде.
Ресурсы
Второй путь — секция ресурсов (.rsrc). Это специально организованное хранилище внутри PE-файла, куда на этапе сборки попадает то, что линковщик добавил из ресурсных файлов: диалоговые окна, меню, таблицы строк, иконки, версии, манифесты. Ресурсы отличаются от литералов тем, что они структурированы и типизированы: у каждой записи есть тип, идентификатор и язык. Программа обращается к ним не по адресу в памяти, а через API-функции загрузки ресурсов, например FindResource и LoadResource в Windows.
Именно ресурсы — исторически основной механизм локализации интерфейса в Windows-программах: таблица строк (string table) хранит пары «идентификатор — текст», и переводчик может заменить тексты, не трогая сам код.
Что ещё попадает в EXE помимо интерфейсных надписей
Когда вы запускаете извлечение строк, вы увидите гораздо больше, чем тексты кнопок:
- Имена импортируемых функций и DLL — например, KERNEL32.DLL, CreateFileW. Они нужны загрузчику ОС для связывания программы с системными библиотеками.
- Пути и имена файлов — ссылки на конфигурации, библиотеки плагинов, шаблоны.
- Сообщения об ошибках и отладочные строки — часто остаются даже в релизной сборке.
- Метаданные компилятора и среды — версии инструментов, иногда пути к папкам проекта на машине разработчика.
- Служебные данные форматов — XML-манифест, подписи, встроенные архивы инсталляторов.
Поэтому «строки в EXE» и «тексты интерфейса» — не одно и то же. Интерфейсные надписи — лишь заметная часть общего пула.
Кодировки: почему одни строки читаются, а другие нет
То, как строка выглядит в дампе, напрямую зависит от кодировки, в которой её записал компилятор.
ANSI и однобайтовые кодировки
Классические C-строки заканчиваются нулевым байтом, и каждый символ занимает один байт. Латиница читается без проблем, но русский текст в такой строке записан в одной из однобайтовых кодировок (в Windows исторически это CP1251). В hex-редакторе такие строки выглядят как читаемый текст только если просмотрщик интерпретирует байты в правильной кодировке. Утилита strings по умолчанию ищет ASCII-последовательности, поэтому кириллицу в CP1251 она может показать частично или исказить.
UTF-16 и широкие символы
Windows API исторически построен вокруг UTF-16: каждый символ занимает два байта, а между ними лежат нулевые. Отсюда характерная картина в дампе: «З\0а\0г\0р\0у\0з\0к\0а\0…». Многие инструменты умеют распознавать такие последовательности (обычно опция поиска UTF-16LE-строк), но если её не включить, русские надписи просто не будут найдены. Это самая частая причина, почему новичок «не видит» очевидный текст интерфейса в бинарнике.
UTF-8
Современные кроссплатформенные проекты всё чаще хранят литералы в UTF-8: русские буквы занимают два байта каждый, латиница — один. Такие строки читаются в большинстве редакторов корректно, но старые утилиты могут резать их посередине многобайтового символа.
Как устроена секция ресурсов
PE-ресурсы организованы как дерево из трёх уровней: тип → имя/идентификатор → язык. Например, запись типа RT_STRING (таблица строк) с идентификатором 5 и языком 0x0419 (русский) — это конкретный блок текстов для русской локали. Такая структура позволяет упаковать в один файл несколько языковых версий интерфейса одновременно: программа при запуске выбирает блок, соответствующий языку системы.
Таблицы строк имеют техническую особенность: они хранятся блоками по шестнадцать строк, и идентификаторы внутри блока вычисляются по формуле. Из-за этого при ручном разборе ресурсов легко ошибиться с нумерацией — практичнее использовать готовые редакторы ресурсов, которые показывают дерево целиком.
Локализация: три подхода к хранению переводов
Способ, которым переводы попадают в программу, определяет и то, где их искать, и то, можно ли их заменить без пересборки.
| Подход | Где живут строки | Замена без пересборки | Типичное применение |
|---|---|---|---|
| Литералы в коде | Секции .rdata/.data вперемешку с прочими данными | Практически нет — только патч байтов | Малые утилиты, прототипы, внутренние инструменты |
| Ресурсы PE | Секция .rsrc, структурированное дерево | Да — правка ресурсов штатными редакторами | Классические Windows-приложения |
| Внешние файлы локализации | Отдельные файлы рядом с EXE (INI, JSON, PO/MO, DLL-сателлиты) | Да — правка файла перевода | Кроссплатформенные и современные приложения |
Третий подход стоит пояснить. Многие фреймворки сознательно выносят тексты наружу: Qt использует файлы перевода, загружаемые в рантайме; ряд приложений держат языковые пакеты в отдельных DLL, где вся логика та же, что и с ресурсами основного файла, но каждый язык — отдельный модуль. В таких программах сам EXE может содержать минимум строк, и поиск по нему ничего интересного не даст — нужно смотреть соседние файлы.
Как извлечь строки из EXE на практике
Если задача — посмотреть, какие тексты содержит исполняемый файл, порядок действий обычно такой:
- Определите формат и архитектуру. Убедитесь, что перед вами PE-файл (EXE/DLL), а не упакованный самораспаковывающийся архив — во втором случае сначала понадобится распаковка.
- Прогоните файл через извлекатель строк с поддержкой обеих кодировок: ASCII/UTF-8 и UTF-16LE. Без второго режима значительная часть Windows-интерфейсов останется невидимой.
- Откройте ресурсы специализированным инструментом — просмотрщиком или редактором ресурсов. Он покажет дерево: диалоги, меню, таблицы строк, версию — в человекочитаемом виде, чего сырой дамп не даёт.
- Проверьте соседние файлы. Если приложение собрано на фреймворке с внешней локализацией, основные тексты будут в каталогах lang, locales, translations рядом с исполняемым файлом.
- Учтите упаковку и шифрование. Компрессоры исполняемых файлов сжимают все секции, поэтому до распаковки осмысленных строк в файле может не быть вовсе — вместо текстов виден мусор.
Частые заблуждения и ошибки
- «Нет строк в дампе — значит, их нет в программе». Тексты могут лежать в ресурсах, внешних файлах, сжатом виде или генерироваться в рантайме из шаблонов и числовых идентификаторов.
- «Нашёл строку — нашёл место её использования». Адрес строки в данных и код, который её выводит, связаны через ссылки, и вручную эту связь приходится восстанавливать отдельно.
- «Правка байтов в EXE — безопасный способ перевести программу». Замена строки на более длинную сдвигает адреса и ломает файл; замена на более короткую требует аккуратного заполнения остатка нулями с сохранением терминатора. Работоспособность после такого патча никто не гарантирует, а лицензионное соглашение программы может это запрещать.
- «Все строки — интерфейс». Как показано выше, значительная часть — служебные данные загрузчика, импорта и метаданных сборки.
Что это значит для разработчика
Если вы пишете программу и хотите, чтобы с её строками было удобно работать дальше, практические выводы такие:
- Не оставляйте пользовательские тексты литералами в коде, если планируете локализацию: перенести их потом в ресурсы или файлы перевода — трудоёмкий рефакторинг.
- Явно выбирайте кодировку литералов на уровне настроек сборки, а не полагайтесь на умолчания компилятора: смешение ANSI и Unicode в одном проекте даёт трудноуловимые баги с кракозябрами.
- Помните, что строки в бинарнике доступны любому, кто откроет файл: не храните в них секреты, ключи и пароли — это не защита, а иллюзия защиты.
- Для многоязычных версий решите заранее: мультиязычные ресурсы в одном файле, отдельные языковые DLL или внешние файлы перевода. У каждого варианта своя цена поддержки.
Ответы на частые вопросы
Можно ли изменить текст в готовой программе?
Технически — да, если строки лежат в ресурсах: редактор ресурсов позволяет заменить текст и сохранить файл. Правка литералов в секциях данных возможна только при совпадении длины или с осторожным сдвигом структуры. Юридическая сторона зависит от лицензии программы: модификация может быть запрещена, а подписанный файл после правки перестанет проходить проверку цифровой подписи.
Почему утилита показывает бессмысленный набор символов вместо русских надписей?
Почти всегда дело в кодировке: текст записан в UTF-16 или в однобайтовой кодировке, которую инструмент не интерпретирует. Включите поиск UTF-16LE-строк или откройте файл в редакторе с выбором кодировки.
Где искать строки, если в самом EXE их мало?
Проверьте ресурсы основного файла, затем соседние DLL и файлы локализации в каталогах программы. Современные приложения нередко хранят весь интерфейс вне исполняемого файла.
Влияют ли строки на размер файла?
Да, но умеренно: тысячи коротких строк — это десятки килобайт. Заметный вклад дают длинные сообщения, встроенные шаблоны и дублирование текстов, когда компилятор по каким-то причинам не смог объединить одинаковые литералы.
Практический итог
Строки попадают в EXE двумя путями: компилятор размещает литералы в секциях данных, а система ресурсов складывает типизированные тексты в секцию .rsrc. Кодировка определяет, как строки выглядят при извлечении, а выбранный подход к локализации — где вообще искать переводы: в самом файле или рядом с ним. Если вам нужно найти тексты интерфейса в конкретной программе, начните с просмотра ресурсов и извлечения строк в обеих кодировках, затем осмотрите соседние файлы — этот порядок закрывает подавляющее большинство случаев. Если же вы проектируете собственное приложение, выносите пользовательские тексты из кода на этапе проектирования: позже это обойдётся значительно дороже.
