Формат PE
Portable Executable (PE) — это формат исполняемых файлов, объектных файлов, библиотек (DLL) и драйверов, используемый в операционных системах семейства Microsoft Windows. Он был разработан на основе спецификации COFF (Common Object File Format) и пришёл на смену формату NE (New Executable), применявшемуся в 16-разрядных версиях Windows. Формат PE является стандартным для загрузки и выполнения программ в 32-разрядных (PE32) и 64-разрядных (PE32+) версиях Windows, начиная с Windows NT 3.1 (1993 год). Основное назначение формата — предоставить операционной системе структурированную информацию о коде, данных, ресурсах и зависимостях исполняемого модуля, необходимую для его загрузки в память и последующего выполнения.
История
Формат PE был разработан корпорацией Microsoft как часть проекта по созданию новой операционной системы Windows NT. Инженеры, работавшие над Windows NT, стремились создать переносимую и модульную систему, способную работать на разных аппаратных архитектурах (x86, MIPS, Alpha). Для этого требовался универсальный формат исполняемых файлов, который не был бы привязан к конкретному процессору. В качестве основы был взят формат COFF, используемый в Unix-подобных системах, но он был значительно расширен и адаптирован под нужды Windows.
Первая версия формата PE была представлена в 1993 году с выходом Windows NT 3.1. Впоследствии формат неоднократно совершенствовался. В 64-разрядных версиях Windows (начиная с Windows XP 64-bit Edition и Windows Server 2003) была введена спецификация PE32+, которая отличается увеличенным размером некоторых полей заголовков (например, для адресов и размеров стали использоваться 8-байтовые значения вместо 4-байтовых). В последних версиях Windows (начиная с Windows 10) были добавлены расширения для поддержки новых технологий, таких как Control-flow Guard (CFG) и защита от эксплойтов (например, динамическая верификация кода).
Структура формата PE
Файл в формате PE представляет собой последовательность структур данных, которые описывают его содержимое и способ загрузки. Структура делится на несколько ключевых частей.
DOS-заголовок (MZ Header)
Каждый PE-файл начинается с DOS-заголовка, который имеет сигнатуру «MZ» (0x4D 0x5A) — инициалы Марка Збиковски, одного из первых разработчиков MS-DOS. Этот заголовок необходим для обратной совместимости: если PE-файл будет запущен в среде MS-DOS, он выведет сообщение «This program cannot be run in DOS mode» (или аналогичное). В конце DOS-заголовка находится поле e_lfanew, которое содержит смещение (в байтах) от начала файла до начала PE-заголовка.
PE-заголовок (NT Headers)
PE-заголовок начинается с сигнатуры «PE\0\0» (0x50 0x45 0x00 0x00). Он состоит из двух основных частей:
- IMAGE_FILE_HEADER — содержит базовую информацию о файле:
Machine— архитектура процессора (например, 0x014C для x86, 0x8664 для x64).NumberOfSections— количество секций в файле.SizeOfOptionalHeader— размер дополнительного заголовка.Characteristics— флаги, указывающие тип файла (исполняемый, DLL, системный и т.д.).- IMAGE_OPTIONAL_HEADER — содержит более детальную информацию, необходимую для загрузчика:
Magic— идентификатор версии (0x10B для PE32, 0x20B для PE32+).AddressOfEntryPoint— относительный виртуальный адрес (RVA) точки входа программы.ImageBase— предпочтительный базовый адрес загрузки образа в память.SectionAlignmentиFileAlignment— выравнивание секций в памяти и в файле.SizeOfImage— общий размер образа в памяти.Subsystem— тип подсистемы (например, GUI, консольное приложение, драйвер).
Таблица секций (Section Table)
После PE-заголовка следует таблица секций, которая представляет собой массив структур IMAGE_SECTION_HEADER. Каждая секция описывает блок данных файла (код, данные, ресурсы и т.д.). Структура содержит:
Name— имя секции (например,.text,.data,.rdata,.rsrc).VirtualSize— размер секции в памяти.VirtualAddress— RVA начала секции.SizeOfRawData— размер секции в файле.PointerToRawData— смещение начала секции в файле.Characteristics— флаги, определяющие права доступа (чтение, запись, выполнение).
Секции
Секции — это фактические блоки данных, на которые разбит файл. Типичные секции включают:
.text— исполняемый код..data— инициализированные глобальные и статические переменные..rdata— константные данные (например, строки, таблицы импорта и экспорта)..idata— таблица импорта (на практике часто объединяется с.rdata)..edata— таблица экспорта (для DLL)..rsrc— ресурсы (иконки, меню, диалоги, строки)..reloc— таблица перемещений (для перебазирования образа, если он не загружен по предпочтительному адресу)..pdata— таблица обработчиков исключений (для архитектуры x64).
Таблица импорта (Import Table)
Таблица импорта содержит информацию о функциях и библиотеках (DLL), которые использует программа. Она позволяет загрузчику Windows найти и загрузить необходимые библиотеки, а также связать адреса импортируемых функций. Структура таблицы импорта представляет собой массив структур IMAGE_IMPORT_DESCRIPTOR, каждая из которых описывает одну DLL. Внутри каждой структуры находится массив IMAGE_THUNK_DATA, который содержит либо имена импортируемых функций, либо их порядковые номера (ordinals).
Таблица экспорта (Export Table)
Таблица экспорта используется в DLL-файлах для предоставления функций другим программам. Она содержит массив имен экспортируемых функций, их порядковые номера и адреса (RVA). Таблица экспорта позволяет загрузчику динамически связывать вызовы функций из других модулей.
Таблица перемещений (Base Relocation Table)
Таблица перемещений необходима, если образ не может быть загружен по предпочтительному базовому адресу (ImageBase). В этом случае загрузчик корректирует все абсолютные адреса в коде и данных, используя записи из этой таблицы. Каждая запись указывает на смещение внутри секции, где находится адрес, требующий пересчёта.
Типы PE-файлов
Формат PE используется для различных типов исполняемых модулей, которые различаются значением поля Subsystem и флагами Characteristics:
- EXE — исполняемый файл. Обычно имеет подсистему
IMAGE_SUBSYSTEM_WINDOWS_GUI(графический интерфейс) илиIMAGE_SUBSYSTEM_WINDOWS_CUI(консольное приложение). - DLL — динамически подключаемая библиотека. Имеет флаг
IMAGE_FILE_DLLв полеCharacteristics. Содержит таблицу экспорта. - SYS — драйвер режима ядра Windows. Имеет подсистему
IMAGE_SUBSYSTEM_NATIVE. - OBJ — объектный файл (COFF-объект). Не содержит PE-заголовка в полном смысле, а использует только структуру COFF. Используется компиляторами и линковщиками.
- LIB — статическая библиотека (на основе COFF). Содержит набор объектных файлов.
Загрузка PE-файла
Процесс загрузки PE-файла в память выполняется загрузчиком Windows (часть ядра ntoskrnl.exe и библиотеки ntdll.dll). Основные этапы:
- Создание сессии и процесса — загрузчик получает путь к файлу и создаёт новый процесс.
- Отображение файла в память — файл отображается в виртуальное адресное пространство процесса с помощью системного вызова
NtCreateSectionиNtMapViewOfSection. - Обработка заголовков — загрузчик проверяет сигнатуры, определяет архитектуру, точку входа и размеры.
- Перемещение (relocation) — если образ не может быть загружен по предпочтительному адресу, загрузчик применяет таблицу перемещений.
- Загрузка зависимостей — загрузчик анализирует таблицу импорта, находит и загружает все необходимые DLL (рекурсивно), вызывая для каждой из них точку входа
DllMain. - Инициализация — после загрузки всех зависимостей загрузчик вызывает точку входа программы (функцию
mainилиWinMain).
Инструменты для работы с PE-форматом
Существует множество инструментов для анализа, редактирования и создания PE-файлов:
- dumpbin — утилита из состава Visual Studio, позволяющая просматривать заголовки, секции, таблицы импорта и экспорта.
- PEview — простая программа для просмотра структуры PE-файла.
- CFF Explorer — мощный инструмент для редактирования всех аспектов PE-формата.
- LordPE — утилита для модификации заголовков и секций.
- IDA Pro и Ghidra — дизассемблеры, которые глубоко анализируют PE-файлы.
- objdump (из состава GNU Binutils) — может анализировать COFF-объекты и PE-файлы.
Критика и недостатки
Формат PE, несмотря на свою распространённость, имеет ряд недостатков, которые часто критикуются специалистами по безопасности и разработчиками:
- Сложность и избыточность — формат содержит множество устаревших полей (например, DOS-заголовок), что усложняет его анализ.
- Уязвимость к модификации — PE-файлы легко поддаются редактированию, что используется для внедрения вредоносного кода (например, вставка кода в секцию
.textили добавление новой секции). - Отсутствие встроенной защиты — до появления Windows 10 и технологий, таких как Control-flow Guard, формат не предоставлял механизмов для проверки целостности кода.
- Проблемы с переносимостью — формат жёстко привязан к архитектуре Windows и не может быть напрямую использован в других операционных системах без эмуляции или трансляции (например, Wine).
Интересные факты
- Сигнатура «MZ» в DOS-заголовке является данью уважения Марку Збиковски, который написал загрузчик для MS-DOS.
- Поле
e_lfanew(смещение до PE-заголовка) может быть произвольным, что позволяет скрывать PE-заголовок внутри файла (техника, используемая некоторыми вирусами). - Формат PE поддерживает подпись цифровыми сертификатами (Authenticode), что позволяет проверять подлинность и целостность файла.
- Формат PE используется не только для Windows, но и в эмуляторах (например, Wine) и в некоторых загрузчиках операционных систем (например, ReactOS).
Источники
- Microsoft Corporation. Portable Executable and Common Object File Format Specification (Revision 8.3, 2023).
- Pietrek, M. Peering Inside the PE: A Tour of the Win32 Portable Executable File Format. Microsoft Systems Journal, 1994.
- Russinovich, M., Solomon, D. Windows Internals, Part 1 (7th Edition). Microsoft Press, 2017.
- Документация к инструменту
dumpbin(Visual Studio). - Описание формата PE на сайте Open Specification (Microsoft).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →