Как в EXE попадают строки интерфейса: от исходного кода до исполняемого файла

Откройте любой exe-файл в hex-редакторе — и среди машинных кодов вы увидите узнаваемые куски текста: названия кнопок, сообщения об ошибках, пути к библиотекам. Эти строки не появляются в файле случайно: каждая из них попадает туда одним из нескольких строго определённых способов ещё на этапе сборки программы. Понимание этих механизмов полезно с двух сторон: разработчику — чтобы правильно организовать локализацию и не раздувать бинарник, а аналитику или энтузиасту — чтобы понимать, что именно он видит при просмотре строк утилитами вроде strings.

Главный ориентир такой: строки попадают в 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 на практике

Если задача — посмотреть, какие тексты содержит исполняемый файл, порядок действий обычно такой:

  1. Определите формат и архитектуру. Убедитесь, что перед вами PE-файл (EXE/DLL), а не упакованный самораспаковывающийся архив — во втором случае сначала понадобится распаковка.
  2. Прогоните файл через извлекатель строк с поддержкой обеих кодировок: ASCII/UTF-8 и UTF-16LE. Без второго режима значительная часть Windows-интерфейсов останется невидимой.
  3. Откройте ресурсы специализированным инструментом — просмотрщиком или редактором ресурсов. Он покажет дерево: диалоги, меню, таблицы строк, версию — в человекочитаемом виде, чего сырой дамп не даёт.
  4. Проверьте соседние файлы. Если приложение собрано на фреймворке с внешней локализацией, основные тексты будут в каталогах lang, locales, translations рядом с исполняемым файлом.
  5. Учтите упаковку и шифрование. Компрессоры исполняемых файлов сжимают все секции, поэтому до распаковки осмысленных строк в файле может не быть вовсе — вместо текстов виден мусор.

Частые заблуждения и ошибки

  • «Нет строк в дампе — значит, их нет в программе». Тексты могут лежать в ресурсах, внешних файлах, сжатом виде или генерироваться в рантайме из шаблонов и числовых идентификаторов.
  • «Нашёл строку — нашёл место её использования». Адрес строки в данных и код, который её выводит, связаны через ссылки, и вручную эту связь приходится восстанавливать отдельно.
  • «Правка байтов в EXE — безопасный способ перевести программу». Замена строки на более длинную сдвигает адреса и ломает файл; замена на более короткую требует аккуратного заполнения остатка нулями с сохранением терминатора. Работоспособность после такого патча никто не гарантирует, а лицензионное соглашение программы может это запрещать.
  • «Все строки — интерфейс». Как показано выше, значительная часть — служебные данные загрузчика, импорта и метаданных сборки.

Что это значит для разработчика

Если вы пишете программу и хотите, чтобы с её строками было удобно работать дальше, практические выводы такие:

  • Не оставляйте пользовательские тексты литералами в коде, если планируете локализацию: перенести их потом в ресурсы или файлы перевода — трудоёмкий рефакторинг.
  • Явно выбирайте кодировку литералов на уровне настроек сборки, а не полагайтесь на умолчания компилятора: смешение ANSI и Unicode в одном проекте даёт трудноуловимые баги с кракозябрами.
  • Помните, что строки в бинарнике доступны любому, кто откроет файл: не храните в них секреты, ключи и пароли — это не защита, а иллюзия защиты.
  • Для многоязычных версий решите заранее: мультиязычные ресурсы в одном файле, отдельные языковые DLL или внешние файлы перевода. У каждого варианта своя цена поддержки.

Ответы на частые вопросы

Можно ли изменить текст в готовой программе?

Технически — да, если строки лежат в ресурсах: редактор ресурсов позволяет заменить текст и сохранить файл. Правка литералов в секциях данных возможна только при совпадении длины или с осторожным сдвигом структуры. Юридическая сторона зависит от лицензии программы: модификация может быть запрещена, а подписанный файл после правки перестанет проходить проверку цифровой подписи.

Почему утилита показывает бессмысленный набор символов вместо русских надписей?

Почти всегда дело в кодировке: текст записан в UTF-16 или в однобайтовой кодировке, которую инструмент не интерпретирует. Включите поиск UTF-16LE-строк или откройте файл в редакторе с выбором кодировки.

Где искать строки, если в самом EXE их мало?

Проверьте ресурсы основного файла, затем соседние DLL и файлы локализации в каталогах программы. Современные приложения нередко хранят весь интерфейс вне исполняемого файла.

Влияют ли строки на размер файла?

Да, но умеренно: тысячи коротких строк — это десятки килобайт. Заметный вклад дают длинные сообщения, встроенные шаблоны и дублирование текстов, когда компилятор по каким-то причинам не смог объединить одинаковые литералы.

Практический итог

Строки попадают в EXE двумя путями: компилятор размещает литералы в секциях данных, а система ресурсов складывает типизированные тексты в секцию .rsrc. Кодировка определяет, как строки выглядят при извлечении, а выбранный подход к локализации — где вообще искать переводы: в самом файле или рядом с ним. Если вам нужно найти тексты интерфейса в конкретной программе, начните с просмотра ресурсов и извлечения строк в обеих кодировках, затем осмотрите соседние файлы — этот порядок закрывает подавляющее большинство случаев. Если же вы проектируете собственное приложение, выносите пользовательские тексты из кода на этапе проектирования: позже это обойдётся значительно дороже.

PEFile.ru