Секции .text, .data и .rsrc в исполняемых файлах: как они устроены и зачем нужны

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

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

Что такое секция в исполняемом файле

Исполняемый файл формата PE (Portable Executable — стандартный формат для Windows) состоит из заголовков и набора секций. Заголовки описывают общую структуру: точку входа, требования к памяти, таблицу импорта. Секции — это непрерывные области файла, каждая из которых содержит данные одного типа.

Загрузчик Windows при запуске программы отображает (маппирует) секции из файла в виртуальное адресное пространство процесса. Для каждой секции задаются права доступа к страницам памяти:

  • execute — разрешение на исполнение кода процессором;
  • read — разрешение на чтение;
  • write — разрешение на запись.

Типовая раскладка выглядит так: .text получает права «чтение + исполнение», .data — «чтение + запись», .rsrc — только «чтение». Именно поэтому попытка записать данные в область кода вызывает исключение доступа, а попытка исполнить байты из секции данных срабатывает только если защита DEP (Data Execution Prevention) отключена или код специально обходит ограничение.

У каждой секции есть два ключевых адреса, которые новички часто путают:

  • Virtual Address (VA) — смещение секции в памяти процесса после загрузки;
  • Pointer to Raw Data — смещение секции внутри самого файла на диске.

Эти значения обычно не совпадают, потому что в файле секции выровнены по одному размеру (часто 512 байт), а в памяти — по другому (обычно 4096 байт, размер страницы). Инструменты анализа всегда показывают оба значения, и умение переводить файловое смещение в виртуальный адрес и обратно — базовый навык при работе с дизассемблером и hex-редактором.

Секция .text: где живёт код

В .text находится скомпилированный машинный код всех функций программы, включая код из подключённых статических библиотек. Это самая важная для реверс-инжиниринга секция: именно её читает дизассемблер, когда вы открываете файл в IDA, Ghidra или другом инструменте.

Что ещё попадает в .text

Кроме собственно инструкций процессора, в этой секции часто лежат:

  • константы времени компиляции, например литеральные строки, если компилятор решил разместить их рядом с кодом;
  • таблицы переходов для операторов switch с плотными диапазонами значений;
  • заглушки импорта (import thunks) — небольшие фрагменты, через которые программа обращается к функциям из DLL;
  • исключения и метаданные, необходимые раскрутке стека при обработке ошибок.

Название секции не является жёстким требованием: теоретически код можно положить в секцию с любым именем, лишь бы у неё стоял флаг исполнения. Но компиляторы Microsoft по умолчанию используют .text, поэтому имя стало фактическим стандартом. В Linux-мире аналог называется .text тоже, хотя формат ELF устроен иначе.

Почему .text защищена от записи

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

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

Секция .data: изменяемые данные программы

В .data размещаются глобальные и статические переменные, которые могут меняться во время работы. Сюда же попадают инициализированные данные, используемые до входа в main: например, объекты, конструкторы которых должны отработать при загрузке.

.data, .bss и .rdata: три разных типа данных

Строго говоря, «данные» в PE-файле распределены по нескольким секциям, и различать их важно:

Секция Содержимое Права в памяти Особенность
.data Инициализированные изменяемые переменные Чтение + запись Начальное значение хранится в файле
.bss Неинициализированные и обнулённые переменные Чтение + запись Может занимать мало места в файле, но много в памяти
.rdata Константы, строки, таблицы импорта Только чтение Изменение запрещено на уровне страниц памяти

Разница между .data и .bss экономит размер файла. Если вы объявили массив на десять мегабайт и не заполнили его, компилятору незачем писать десять мегабайт нулей в файл: достаточно указать в заголовке секции, сколько памяти выделить при загрузке. Загрузчик сам выдаст обнулённые страницы. Поэтому исполняемый файл может весить пару сотен килобайт, а процесс — потреблять десятки мегабайт.

Секция .rdata (read-only data) содержит то, что менять нельзя: строковые литералы, таблицы виртуальных функций C++, дескрипторы импорта. Попытка записи туда вызывает access violation — это частая причина падений при ошибках работы со строками в C++.

Практическое значение для анализа

При исследовании программы секция данных отвечает на вопрос «где хранится состояние». Конфигурационные флаги, счётчики, кэши, указатели на выделенную память — всё это здесь. В отладчике установка точки наблюдения (watchpoint) на адрес в .data позволяет узнать, какая функция изменяет конкретную переменную — приём, который часто оказывается быстрее чтения кода.

Секция .rsrc: ресурсы программы

.rsrc хранит ресурсы — данные, которые не являются ни кодом, ни рабочими переменными, но нужны программе для работы интерфейса и локализации. Формат ресурсов в PE стандартизирован: это дерево, корень которого разделяет ресурсы по типам, затем по именам или идентификаторам, затем по языкам.

Что обычно лежит в ресурсах

  • иконки и курсоры — в том числе та иконка, которую вы видите в проводнике;
  • версионный ресурс (VS_VERSION_INFO) — название продукта, версия, компания, описание файла;
  • таблицы строк — тексты интерфейса, сообщения об ошибках;
  • диалоговые окна и меню — описания элементов интерфейса в декларативном виде;
  • растровые изображения;
  • манифест — XML-описание требований к версии common controls, уровню прав UAC, совместимости;
  • произвольные бинарные данные — разработчики могут встроить в ресурсы шаблоны, шрифты, сертификаты, архивы.

Ресурсы редактируются отдельно от кода. Специальные утилиты позволяют заменить иконку или перевести строки, не пересобирая программу. Именно поэтому локализация Windows-приложений исторически строилась на ресурсах: переводчики работают с ними, не имея доступа к исходному коду.

Ресурсы и анализ подозрительных файлов

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

  • аномально большой объём .rsrc относительно остального файла;
  • ресурс с высоким энтропией (выглядит как случайные байты) — вероятный упакованный или зашифрованный контент;
  • несоответствие версионного ресурса реальному поведению: подделка имени и версии — распространённый приём маскировки;
  • встроенный исполняемый образ, распознаваемый по сигнатуре MZ в начале ресурса.

Легитимные установщики и программы с самообновлением тоже хранят большие бинарные блобы в ресурсах, так что большой .rsrc сам по себе не приговор — это повод посмотреть содержимое внимательнее.

Как секции связаны между собой

Хотя секции разделены, работа программы постоянно их связывает. Типичный цикл выглядит так: процессор исполняет инструкции из .text, инструкция обращается к глобальной переменной в .data, затем вызывает функцию из системной DLL через таблицу импорта в .rdata, а результат выводится в диалоговое окно, описание которого взято из .rsrc.

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

Другие секции, о которых стоит знать

Помимо трёх основных, в реальных файлах встречаются:

  • .edata / .idata — таблицы экспорта и импорта: списки функций, которые программа предоставляет другим модулям и берёт из DLL;
  • .reloc — таблица перемещений, позволяющая загрузчику скорректировать абсолютные адреса, если модуль не удалось загрузить по предпочтительному адресу;
  • .pdata / .xdata — информация для раскрутки стека и обработки исключений на 64-битных платформах;
  • .tls — данные локального хранилища потока;
  • .debug — отладочная информация, если она не вынесена в отдельные PDB-файлы.

Отдельного упоминания заслуживают нестандартные имена секций, которые добавляют упаковщики и протекторы. Если вместо привычного набора вы видите странные названия с высоким энтропией содержимого и единственной секцией с флагом исполнения, скорее всего, файл упакован: настоящий код появится в памяти только после распаковки при запуске.

Как посмотреть секции самостоятельно

Для базового осмотра достаточно бесплатных инструментов:

  1. Откройте файл в утилите просмотра PE-заголовков (например, встроенный просмотр свойств в проводнике покажет только версию, нужен специализированный инструмент).
  2. Найдите список секций: для каждой будут показаны виртуальный размер, виртуальный адрес, размер в файле, смещение в файле и характеристики (флаги чтения/записи/исполнения).
  3. Сравните ожидаемую картину с реальной: обычное приложение имеет небольшой набор стандартных секций, разумные размеры и типовые права доступа.
  4. Для глубокого анализа откройте файл в дизассемблере — он автоматически разметит .text как код, .data как данные, а ресурсы покажет отдельным деревом.

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

Типичные заблуждения

  • «Имя секции определяет её свойства». Нет, свойства определяются флагами в заголовке. Имя — соглашение, и злоумышленники могут назвать вредоносную секцию как угодно.
  • «Все строки программы лежат в .rsrc». Строки интерфейса, оформленные как ресурсы, действительно там. Но строки, зашитые в код (логирование, URL, ключи), чаще оказываются в .rdata или прямо в .text.
  • «Изменение байта в .data безопасно». Изменение начального значения переменной меняет поведение программы, иногда критично. Безопасных изменений в чужом исполняемом файле без понимания структуры не бывает.
  • «Большая секция ресурсов означает вирус». Установщики, игры и офисные пакеты законно хранят в ресурсах гигабайты данных. Оценивать нужно содержимое, а не размер.

Практические сценарии: когда эти знания пригождаются

Отладка падения. Если в отчёте об ошибке указан адрес, определив секцию, к которой он относится, вы сразу сужаете поиск: адрес в .text — проблема в коде (например, вызов по неверному указателю на функцию), в .data или .rdata — обращение к повреждённым данным или запись в константу.

Оценка подозрительного файла. Нестандартные имена секций, исполняемые права у секции данных, высокая энтропия, отсутствие ресурсов у «солидной» программы — совокупность таких признаков даёт быструю первичную оценку до полноценного анализа.

Локализация и кастомизация. Замена строк и иконок через редактор ресурсов позволяет адаптировать программу без исходников — легитимно для своих приложений и с юридическими оговорками для чужих.

Оптимизация размера. Понимание, что неинициализированные массивы не увеличивают файл, а константы в .rdata дедуплицируются компилятором, помогает осознанно управлять структурой данных в проекте.

Что делать дальше

Если вы хотите закрепить материал на практике, порядок такой: возьмите любой exe-файл на своей машине, откройте его в просмотрщике PE-структур и найдите список секций. Сопоставьте имена, размеры и флаги с тем, что описано выше. Затем откройте тот же файл в бесплатном дизассемблере и найдите в дереве проекта секцию ресурсов — увидите знакомую иконку и строки версии. На этом примере связь «файл на диске → секции → память процесса» становится наглядной за полчаса.

Главный ориентир при любом анализе: назначение секции определяется её флагами и содержимым, а не именем. Стандартные имена .text, .data и .rsrc — удобная отправная точка, но окончательный вывод делайте по характеристикам, структуре и поведению файла при запуске.

Материал носит информационный характер. Исследование исполняемых файлов, полученных из ненадёжных источников, выполняйте только в изолированной среде, а решения, связанные с безопасностью системы или юридическими ограничениями на анализ чужого ПО, принимайте с учётом применимого законодательства и консультации профильного специалиста.

PEFile.ru