Формат Portable Executable
Portable Executable (PE) — это формат исполняемых файлов, объектных файлов и библиотек динамической компоновки (DLL), используемый в операционных системах семейства Windows. Он был разработан компанией Microsoft для обеспечения переносимости (portability) кода между различными версиями Windows на разных аппаратных архитектурах. Формат PE является развитием формата COFF (Common Object File Format), используемого в Unix-подобных системах, и служит основой для загрузки и выполнения программ, а также для управления ресурсами и зависимостями.
История
Формат Portable Executable был введён в 1993 году с выходом Windows NT 3.1. До этого в Windows 3.x и MS-DOS использовались форматы NE (New Executable) и MZ (Mark Zbikowski). Основной причиной перехода на PE стала необходимость поддержки 32-битной архитектуры, а также унификации формата для разных процессоров (x86, MIPS, Alpha, PowerPC). Впоследствии формат был расширен для 64-битных систем (PE32+). В Windows 2000 и более поздних версиях PE стал стандартом для всех исполняемых файлов, включая драйверы (.sys).
Структура формата
Файл в формате PE состоит из нескольких последовательных блоков, каждый из которых имеет строго определённую структуру. Основные компоненты:
DOS-заголовок (MZ Header)
Каждый PE-файл начинается с DOS-заголовка, который обеспечивает обратную совместимость с MS-DOS. Первые два байта содержат сигнатуру «MZ» (0x4D 0x5A). В конце заголовка находится поле e_lfanew — смещение (в байтах) до начала PE-заголовка. Если файл запускается в DOS, выполняется короткая программа-заглушка (обычно выводит сообщение «This program cannot be run in DOS mode»).
PE-заголовок (PE Header)
PE-заголовок начинается с сигнатуры «PE\0\0» (4 байта) и состоит из двух частей:
- IMAGE_FILE_HEADER — содержит общую информацию: архитектуру процессора (Machine), количество секций, размер дополнительного заголовка, флаги характеристик (например, является ли файл DLL).
- IMAGE_OPTIONAL_HEADER — несмотря на название, является обязательным. Включает:
- Магическое число (0x10B для PE32, 0x20B для PE32+).
- Размер кода, инициализированных и неинициализированных данных.
- Адрес точки входа (Entry Point) — относительный виртуальный адрес (RVA), с которого начинается выполнение.
- Базовый адрес образа (Image Base) — предпочтительный адрес загрузки в память.
- Размер стека и кучи.
- Таблицы данных (Data Directories) — массив из 16 записей, указывающих на важные структуры: таблицу импорта, экспорта, ресурсов, отладочную информацию и т. д.
Таблица секций (Section Table)
Сразу после PE-заголовка следует таблица секций — массив структур IMAGE_SECTION_HEADER. Каждая запись описывает одну секцию: имя (до 8 символов, например .text, .data, .rdata), виртуальный размер, виртуальный адрес, размер в файле, смещение в файле, а также флаги (код, данные, возможность записи, выполнения и т. д.). Количество секций определяется в IMAGE_FILE_HEADER.
Секции
Секции содержат собственно данные файла. Типичные секции:
- .text — исполняемый код (машинные инструкции).
- .data — инициализированные глобальные и статические переменные.
- .rdata — константные данные (строки, таблицы импорта/экспорта).
- .bss — неинициализированные данные (занимает место только в памяти, в файле не хранится).
- .rsrc — ресурсы (иконки, меню, диалоги, строки).
- .reloc — таблица перемещений (используется, если файл не загружен по базовому адресу).
- .edata — таблица экспорта (для DLL).
- .idata — таблица импорта (указывает, какие функции из других DLL использует программа).
Таблицы импорта и экспорта
Таблица импорта (Import Table)
Таблица импорта (IMAGE_IMPORT_DESCRIPTOR) содержит список DLL, от которых зависит файл, и функций (или ordinals), которые из них загружаются. При загрузке программы загрузчик Windows (Windows Loader) находит эти DLL, загружает их в память и заполняет таблицу адресов импорта (IAT — Import Address Table) актуальными адресами функций. Это позволяет программе вызывать функции из системных библиотек (например, kernel32.dll, user32.dll).
Таблица экспорта (Export Table)
Таблица экспорта (IMAGE_EXPORT_DIRECTORY) присутствует в DLL и файлах, предоставляющих функции другим модулям. Она содержит имена функций, их порядковые номера (ordinals) и адреса (RVA). Экспорт может быть по имени, по порядковому номеру или по обоим способам.
Загрузка и выполнение
Процесс загрузки PE-файла в Windows включает несколько этапов:
- Создание процесса — система создаёт виртуальное адресное пространство.
- Отображение файла — загрузчик считывает заголовки и отображает секции в память по указанным виртуальным адресам, корректируя адреса (relocations) при необходимости.
- Обработка импорта — загрузчик загружает все зависимые DLL и заполняет IAT.
- Инициализация — выполняются конструкторы глобальных объектов (в C++), вызывается точка входа (обычно
WinMainилиmain). - Выполнение — управление передаётся на точку входа.
Разновидности
Формат PE используется для нескольких типов файлов, которые различаются по значению поля Characteristics в IMAGE_FILE_HEADER:
- EXE — исполняемый файл (обычно имеет расширение
.exe). Может быть консольным или оконным приложением. - DLL — динамически подключаемая библиотека (
.dll). Не может быть запущена самостоятельно, экспортирует функции. - SYS — драйвер режима ядра (
.sys). Загружается в адресное пространство ядра Windows. - OBJ — объектный файл (
.obj). Промежуточный результат компиляции, используется компоновщиком. - LIB — статическая библиотека (
.lib). Фактически является архивом объектных файлов в формате COFF.
64-битная версия (PE32+)
Для 64-битных систем (x86-64, IA-64) используется расширение PE32+, которое отличается от PE32:
- Магическое число 0x20B.
- Увеличенный размер полей для адресов (8 байт вместо 4) в
IMAGE_OPTIONAL_HEADER(например,ImageBase,SizeOfStackReserve). - Поддержка 64-битных инструкций и большего объёма памяти.
Инструменты для работы
Существует множество утилит для анализа и модификации PE-файлов:
- dumpbin (входит в Visual Studio) — отображает заголовки, секции, импорт, экспорт.
- peview — графический редактор структуры PE.
- CFF Explorer — мощный инструмент для просмотра и редактирования всех полей.
- Objdump (из GNU binutils) — поддерживает анализ PE через
-pи-d. - LordPE — утилита для редактирования PE-заголовков и секций.
Безопасность и атаки
Формат PE часто используется для сокрытия вредоносного кода. Злоумышленники применяют такие техники, как:
- Упаковка (packing) — сжатие или шифрование секций, которые распаковываются во время выполнения.
- Инъекция кода — внедрение вредоносного кода в легитимные PE-файлы (например, в секцию
.textили.rsrc). - Модификация таблицы импорта — подмена адресов функций для перехвата вызовов (API hooking).
- Reflective DLL injection — загрузка DLL без участия загрузчика Windows.
Для защиты от атак используются цифровые подписи (Authenticode), контроль целостности (например, проверка контрольной суммы), а также антивирусные программы, анализирующие структуру PE.
Критика
Несмотря на широкое распространение, формат PE имеет ряд недостатков:
- Сложность — большое количество полей и структур затрудняет ручной анализ.
- Ограничения — фиксированное количество секций (до 65535) и размер таблицы данных (16 записей) могут быть недостаточны для некоторых приложений.
- Зависимость от Windows — формат не поддерживается другими операционными системами без эмуляции (например, Wine).
- Уязвимости — возможность модификации без нарушения цифровой подписи (например, атаки на контрольную сумму).
Интересные факты
- Формат PE поддерживает «системные» DLL, такие как
ntdll.dll, которые загружаются в адресное пространство каждого процесса. - В Windows 10 и 11 появилась поддержка PE-файлов для ARM64.
- Некоторые вирусы (например, Win32.CIH) использовали особенности PE для повреждения системных файлов.
- Формат PE также используется в консольных играх Xbox (первого поколения) и в Windows CE.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →