Структура exe файла windows

Структура exe файла windows

Исследуем формат EXE-файла

Целью работы компилятора является получение EXE-файла. Поэтому, исследуем его структуру.

EXE-файлы появились ещё в DOS и потом они с небольшими изменениями перекочевали в Windows. Формат EXE-файла под Windows называется PE-файлом. Он организован в виде линейного потока данных.

Формат PE-файла

Заголовок MS-DOS
Программа-заглушка
Заголовок PE
Доп. заголовок PE
Массив DataDir
Заголовки сегментов
Тела сегментов
Остальные области данных

Заголовок MS-DOS не нов, он используется начиная с MS-DOS версии 2. Если вы пытаетесь запустить Windows-программу под DOS-ом, то программа-заглушка, которая размещена ниже, сообщит о невозможности этого сделать. Если бы заголовок MS-DOS и программа-заглушка не были бы включены в PE-файл, то скорее всего бы это бы привело к сбою.

Залоговок MS-DOS (размер 40H байт)

Адрес Тип Имя Описание
00h word Magic Магическая сигнатура DOS-файла — два символа «MZ», явно от MZ -club 🙂
02h word LastByteCount Количество байт на последней странице файла
04h word PageCount Количество страниц в файле
06h word RelocCount Количество релокейшенов
08h word HeaderSize Размер заголовка в параграфах
0Ah word MinAlloc Мин. выделение памяти в параграфах
0Ch word MaxAlloc Макс. выделение памяти в параграфах
0Eh word InitSS Начальное (относительное) значение регистра SS
10h word InitSP Начальное значение регистра SP
12h word CheckSum Контрольная сумма
14h word InitIP Начальное значение регистра IP
16h word InitCS Начальное (относительное) значение регистра CS
18h word RelocAddr Адрес на релокейшены и программу-заглушку
1Ah word OverlayCount Количество оверлеев
1Ch word Res1[4] Зарезервировано
24h word OEMIdentifier Для OEMInfo
26h word OEMInfo Информация о программе
28h word Res1[10] Зарезервировано
3Ch dword PEHeaderAddr Адрес в файле заголовка PE

Для Windows-программы заголовок MS-DOS не содержит релокейшины (пока даже и не знаю что это такое), то есть Relocations = 0, поэтому RelocAddr указывает сразу на программу-заглушку. Но нам важен заголовок PE, его адрес находиться в PEHeaderAddr.

Залоговок PE (размер 18H байт)

Адрес Тип Имя Описание
00h dword Magic Магическая сигнатура PE-файла 4550H или «PE», 0H, 0H
04h word CPUType Тип процессора
06h word SectionCount Количество сегментов
08h dword DateTime Дата/время создания/модификации линкером
0Сh dword SymbolTableAddr Адрес местонахождения таблицы символов
10h dword SymbolTableSize Размер таблицы символов
14h word OptionalHeaderSize Размер доп. заголовка PE
16h word Flags Предназначение программы

Сразу за основным заголовком идёт дополнительный заголовок PE.

Доп. залоговок PE (размер 18H — 77H байт)

Адрес Тип Имя Описание
18h word Magic Всегда 10Bh
1Ah byte MajorLinkVer Версия линкера, создавшего данный файл
1Bh byte MinorLinkVer
1Ch dword CodeSize Размер исполнительного кода
20h dword InitDataSize Размер инициализированных данных
24h dword UnInitDataSize Размер неинициализированных данных
28h dword EntryPointAddr Адрес, относительно ImageBase, по которому передаётся управление при запуске программы или адрес инициализации/завершения библиотеки
2Ch dword CodeBase Относительное смещение сегмента кода
30 dword DataBase Относительное смещение сегмента неинициализированных данных
34h dword ImageBase Предподчтительный адрес для загрузки исполнимого файла (по умолчанию 400000H)
38h dword SectionAlign Выравнивание программных секций (по умолчанию 1000H)
3Ch dword FileAlign Минимальная гранулярность сегментов, то есть размер сегментов должен быть кратен FileAlign, должен быть равен значению степени 2 между 200H и 10000H (по умолчанию 200H)
40h word MajorOSVer Старший номер версии OS, необходимый для запуска программы
42h word MinorOSVer Младший номер версии OS
44h word MajorImageVer Пользовательский старший номер версии, задается пользователем при линковке программы и им же и используется
46h word MinorImageVer Пользовательский младший номер версии, задается пользователем при линковке программы и им же и используется
48h word MajorSubSysVer Старший номер версии Win32
4Ah word MinorSubSysVer Младший номер версии Win32
4Ch dword Res1
50h dword ImageSize Виртуальный размер в байтах всего загружаемого образа, вместе с заголовками, кратен ObjectAlign
54h dword HeaderSize Общий размер всех заголовков: MS-DOS, PE, доп PE и всех сегментов
58h dword CheckSum Контрольная сумма (не используется и равна 0)
5Ch word SubSystem Подсистема, необходимая для запуска данного файла //(0 — неизвестная подсистема, 1 — не требует подсистему, 2 — Windows GUI, 3 — Windows консоль. )
5Eh word DllFlags Специальные флаги при загрузке, начиная с NT 3.5 не используются
60h dword StackReserveSize Память, требуемая для стека приложения, память резервируется, но выделяется только StackCommitSize байтов, следующая страница является охранной. Когда приложение достигает этой страницы, то страница становится доступной, а следующая страница — охранной, и так до достижения нижней границы, после чего Windows убивает программу с сообщением о конце стека
64h dword StackCommitSize Объем памяти, отводимый в стеке немедленно после загрузки
68h dword HeapReserveSize Максимальный возможный размер локального хипа
6Ch dword HeapComitSize Отводимый при загрузке хип
70h dword LoaderFlags Данный параметр устарел
74h dword DataDirSize Указывает размер массива DataDir, расположенный ниже (по умолчанию 10h)

Далее идёт массив DataDir, 8-байтные элементы которого состоят из двух 4-х байтных: адрес и размер.

Массив DataDir (размер 78H — F8H байт)

Адрес Тип Имя Описание
78h qword ExportDir Каталог экспортируемых объектов
80h qword ImportDir Каталог импортируемых объектов
88h qword ResourceDir Каталог ресурсов
90h qword ExceptionDir Каталог исключений
98h qword SecurityDir Каталог безопастности
A0h qword BaseRelocDir Каталог переадресаций
A8h qword DebugDir Отладочный каталог
B0h qword CopyrightDir Каталог описаний
B8h qword CpuSpecDir Каталог значений, специфичных для процессора
C0h qword TLSDir Каталог TLS (Thread local storage — локальная память потоков)
C8h qword ConfigDir Каталог конфигураций загрузки
D0h qword ResDir11
D8h qword ResDir12
E0h qword ResDir13
E8h qword ResDir14
F0h qword ResDir15

Элемент массива DataDir (размер 8 байт)

Адрес Тип Имя Описание
00h dword Addr Адрес каталога
04h dword Size Размер каталога

Далее идёт подряд несколько сегментов, количество указано в SectionCount.

Заголовок сегмента (размер 2Ch байт)

Адрес Тип Имя Описание
00h char[8] SectName Имя секции, если имя Практически любая программа под Windows работает с такими её DLL-ками: kernel32.dll, user32.dll, gdi32.dll и т.д.. Поэтому, EXE-шник должен уметь импортировать функции данных библиотек, то есть работать с каталогом импорта ImportDir. Каталог импорта сразу же начинается с таблицы импорта ImportDirTable, которая описывает остальную информацию об импорте. Такая таблица состоит из элементов ImportDirTableItem, указывающих, как минимум, на каждую импортируемую библиотеку. Последний элемент, указывающий на конец таблицы, заполнен нулями.

Элемент таблицы каталога импортируемых объектов ImportDirTableItem (размер 14h байт)

Адрес Тип Имя Описание
00h dword FuncNameList Список имён импортируемых функций
04h dword Res1
08h dword Res2
0Ch dword LibName Имя библиотеки
10h dword FuncAddrList Список адресов импортируемых функций

Параметр LibName указывает на имя библиотеки, которое должно заканчиваться нулём. FuncNameList указывает на список адресов (0-ой адрес — конец списка), по которым находится сначала Hint — (укороченный идентификатор точки входа), а затем имя функции, заканчивающееся нулём. Параметр FuncAddrList указывает на точно такой же список адресов, находящийся (по моим наблюдениям) перед ImportDirTable.

Формат EXE-файла здесь описан не полностью. Остальное будет описано позже. Однако, этого уже достаточно для создания компилятора.

Для глубокого изучения EXE-шника, написана специальная программа «EXE-исследователь». Последнюю версию данной программы можно скачать на страничке Download

Структура exe файла windows

.EXE (сокр. англ. executable — исполнимый) — расширение исполняемых файлов, применяемое в операционных системах DOS, Windows, Symbian OS, OS/2 и в некоторых других, соответствующее ряду форматов. Кроме объектного кода может содержать различные метаданные (ресурсы, цифровая подпись [1] ).

Содержание

Форматы .EXE [ править | править код ]

  • MZ — 16-битный формат, основной формат файлов .EXE в DOS.
  • EXE-файлы для Windows и OS/2 используют другие форматы для основной части программы, но всё равно начинаются с заглушки в формате MZ, которая, как правило, при попытке запустить файл в DOS выводит сообщение This program cannot be run in DOS mode. («Эту программу невозможно запустить в режиме DOS») и завершает выполнение, хотя теоретически может запускать некий произвольный код, работоспособный в DOS.
  • NE — 16-битный формат, использовался в Windows 3.x[2] , OS/2 и MS-DOS.
  • LE — смешанный 16- и 32-битный формат, ранее использовался в OS/2 и Windows (VxD).
  • LX — 32-битный формат, используется в OS/2.
  • PE — 32- и 64-битный формат, используется в современных версиях Windows, начиная с Windows NT и Windows 95.

    Структура файлов [ править | править код ]

    Файл EXE, создаваемый компоновщиком, состоит из двух частей:

    • управляющая информация для загрузчика;
    • загрузочный модуль.

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

    Стандартная часть заголовка имеет следующий формат [3] :

    00-01 4D5A — сигнатура файла .EXE; 02-03 Длина образа задачи по модулю 512 (то есть число полезных байт в последнем блоке). Компоновщики версий до 1.10 помещали в это поле 04; если оно имеет такое значение, его рекомендуется игнорировать); 04-05 Длина файла в блоках; 06-07 Число элементов таблицы настройки адресов; 08-09 Длина заголовка в 16-байтных параграфах. Используется для выяснения начала тела загрузочного модуля; 0A-0B Минимальный объём памяти, которую нужно выделить после конца образа задачи (в 16-байтных параграфах); 0C-0D Максимальный объём памяти, которую нужно выделить после конца образа задачи (в 16-байтных параграфах); 0E-0F Сегментный адрес начала стекового сегмента относительно начала образа задачи; 10-11 Значение SP при входе в задачу; 12-13 Контрольная сумма — ноль минус результат сложения без переноса всех слов файла; 14-15 Значение IP (счетчика команд) при входе в задачу; 16-17 Сегментный адрес начала кодового сегмента относительно начала образа задачи; 18-19 Адрес первого элемента таблицы настройки адресов относительно начала файла; 1A-1B Номер сегмента перекрытий (0 для корневого сегмента программы).

    Далее следует таблица настройки адресов. Таблица состоит из элементов, число которых записано в байтах 06-07. Элемент таблицы настройки состоит из двух полей: 2-байтного смещения и 2-байтного сегмента, и указывает слова в загрузочном модуле, содержащее адрес, который должен быть настроен на место памяти, в которое загружается задача. Настройка производится следующим образом:

    1. В области памяти после резидентной части выполняющей загрузку программы строится префикс программного сегмента (PSP);
    2. Стандартная часть заголовка считывается в память;
    3. Определяется длина тела загрузочного модуля (разность длины файла 04-07 и длины заголовка 08-09 плюс число байт в последнем блоке 02-03). В зависимости от признака, указывающего загружать задачу в конец памяти или в начало, определяется сегментный адрес для загрузки. Этот сегмент называется начальным сегментом;
    4. Загрузочный модуль считывается в начальный сегмент;
    5. Таблица настройки порциями считывается в рабочую память;
    6. Для каждого элемента таблицы настройки к полю сегмента прибавляется сегментный адрес начального сегмента. В результате элемент таблицы указывает на слово в памяти, к которому прибавляется сегментный адрес начального сегмента;
    7. Когда таблица настройки адресов обработана, в регистры SS и SP записываются значения, указанные в заголовке, а к SS прибавляется сегментный адрес начального сегмента. В ES и DS записывается сегментный адрес начала PSP. Управление передается по адресу, указанному в заголовке (байты 14-17).

    Структура программного сегмента [ править | править код ]

    При обращении к нерезидентной команде или вызове программы операцией Exec, DOS определяет минимальный адрес, начиная с которого может быть загружена соответствующая программа. Эта область называется программным сегментом.

    По смещению 0000 в программном сегменте DOS формирует префикс программного сегмента (PSP). Сама программа загружается по смещению 0100.

    Программа завершается переходом по адресу 0000 в программном сегменте, выполнив INT 20, выполнив INT 21 с AH=0 или AH=4C, или обратившись к подпрограмме по адресу 0050 в программном сегмент с AH=0 или AH=4C.

    Примечание: при завершении иначе, чем операцией 4C, программа должна предварительно заслать в CS адрес начала своего программного сегмента.

    Все четыре способа возвращают управление в резидентную часть COMMAND.COM (при этом операция 4C передает код завершения). Все четыре способа приводят к продолжению выполнения программы, обратившейся к операции Exec (4B). При этом вектора прерываний 22, 23 и 24 (завершение, Ctrl-Break, фатальная ошибка обмена) восстанавливаются из Префикса Программного сегмента возобновляемой задачи. Затем управление передается по адресу завершения. Если программа возвращается в COMMAND.COM, то управление передается в нерезидентную часть. Если это происходит во время выполнения командного файла, оно продолжается, иначе COMMAND выдает на терминал приглашение и ждет ввода следующей команды.

    Когда загруженная программа получает управление, имеют место следующие условия:

    Переносимый исполняемый файл
    Расширение .exe , .dll , .ocx , .sys , .scr , .drv , .cpl или .efi
    MIME-тип application/vnd.microsoft.portable-executable [1] и application/efi [2]
    Тип формата двоичный, исполняемый, объектный, динамическая библиотека

    Portable Executable (PE, «переносимый исполняемый») — формат исполняемых файлов, объектного кода и динамических библиотек, используемый в 32- и 64-разрядных версиях операционной системы Microsoft Windows. Формат PE представляет собой структуру данных, содержащую всю информацию, необходимую PE-загрузчику для отображения файла в память. Исполняемый код включает в себя ссылки для связывания динамически загружаемых библиотек, таблицы экспорта и импорта API-функций, данные для управления ресурсами и данные локальной памяти потока (TLS). В операционных системах семейства Windows NT формат PE используется для EXE, DLL, SYS (драйверов устройств) и других типов исполняемых файлов.

    PE представляет собой модифицированную версию COFF формата файла для Unix. PE/COFF — альтернативный термин при разработке Windows.

    На операционных системах семейства Windows NT формат PE в настоящее время поддерживает следующие архитектуры наборов команд: IA-32, IA-64 и x86-64 (AMD64/Intel64). До Windows 2000 Windows NT (таким образом, и PE) поддерживал MIPS, Alpha и PowerPC. Поскольку PE используется на Windows CE, он продолжает поддерживать несколько разновидностей MIPS, ARM (включая Thumb) и SuperH.

    Основные «конкуренты» PE — ELF (используемый в Linux и большинстве других версий Unix) и Mach-O (используемый в Mac OS X).

    Содержание

    Краткая история [ править | править код ]

    С появлением операционной системы Windows NT 3.1 Microsoft перешла на формат PE. Все более поздние версии Windows, включая Windows 95/98/ME, поддерживают этот формат. Формат сохранил ограниченную поддержку существующего (MZ) для преодоления разрыва между системами, основанными на DOS, и системами NT. Например, заголовки PE/COFF всё ещё включают исполняемую программу MS-DOS, которая по умолчанию является заглушкой, выводящей на экран простое сообщение «This program cannot be run in DOS mode» — «Эта программа не может быть выполнена в режиме DOS» (или подобное). PE продолжает служить изменяющейся платформе Windows. Некоторые расширения включают формат PE.NET (см. ниже), 64-разрядную версию под названием PE32+ (иногда PE+) и спецификацию для Windows CE.

    Технические детали [ править | править код ]

    Сигнатура [ править | править код ]

    Первые 2 байта PE-файла содержат сигнатуру 0x4D 0x5A — «MZ» (как наследник MZ-формата). Далее – двойное слово по смещению 0x3C содержит адрес PE-заголовка. Последний начинается с сигнатуры 0x50 0x45 — «PE».

    Структура [ править | править код ]

    Файл PE состоит из нескольких заголовков и секций, которые указывают динамическому компоновщику, как отображать файл в память. Исполняемый образ состоит из нескольких различных областей (секций), каждая из которых требует различных прав доступа к памяти; таким образом, начало каждой секции должно быть выровнено по границе страницы. Например, обычно секция .text, которая содержит код программы, отображена как исполняемая/доступная только для чтения, а секция .data, содержащая глобальные переменные, отображена как неисполняемая/доступная для чтения и записи. Однако, чтобы не тратить впустую пространство на жёстком диске, различные секции на нём на границу страницы не выровнены. Часть работы динамического компоновщика состоит в том, чтобы отобразить каждую секцию в память отдельно и присвоить корректные права доступа получившимся областям согласно указаниям, содержащимся в заголовках.

    Таблица импорта [ править | править код ]

    Одна из известных секций — таблица адресов импорта (IAT — Import Address Table), которая используется в качестве таблицы поиска, когда приложение вызывает функцию из другого модуля. Это может быть сделано и в форме импорта по порядковому номеру функции (ordinal), и импорта по её имени. Поскольку скомпилированной программе неизвестно расположение библиотек, от которых она зависит, то требуется производить косвенный переход всякий раз, когда происходит вызов API-функции. Когда динамический компоновщик загружает модули и объединяет их, он записывает действительные адреса в область IAT так, чтобы они указали на ячейки памяти соответствующих библиотечных функций. Хотя это добавляет дополнительный переход внутри модуля, приводящий к потере производительности, это предоставляет ключевое преимущество: количество страниц памяти, которые должны быть скопированы загрузчиком при записи, минимизировано, что приводит к экономии памяти и дискового времени ввода-вывода. Если компилятору будет известно заранее, что вызов будет межмодульным (через атрибут dllimport), то он сможет произвести более оптимизированный код, который просто приводит к коду операции косвенного вызова.

    Таблица экспорта [ править | править код ]

    Таблица адресов экспорта (EAT — Export Address Table) нужна для того, чтобы один модуль (обычно это динамически загружаемая библиотека) мог указать другим модулям, какие функции они могут из него импортировать, и по каким адресам последние расположены.

    Таблица перемещений [ править | править код ]

    Файлы PE не содержат позиционно-независимого кода. Вместо этого они скомпилированы для предпочтительного базового адреса, и все адреса, генерируемые компилятором/компоновщиком, заранее фиксированы. Если PE-файл не может быть загружен по своему предпочтительному адресу (потому что он уже занят чем-то ещё), операционная система будет перебазировать его. Это включает в себя перевычисление каждого абсолютного адреса и изменение кода для того, чтобы использовать новые значения. Загрузчик делает это, сравнивая предпочтительный и фактический адреса загрузки, и вычисляя значение разности. Тогда для получения нового адреса ячейки памяти эта разность складывается с предпочтительным адресом. Базовые адреса перемещений хранятся в списке и при необходимости добавляются к существующей ячейке памяти. Полученный код является теперь отдельным по отношению к процессу и не является больше разделяемым, так что при таком способе теряются многие из преимуществ экономии памяти динамически загружаемых библиотек. Такой способ также значительно замедляет загрузку модуля. По этой причине следует избегать перебазирования везде, где это возможно; например, библиотеки, поставляемые Microsoft, имеют предварительно вычисленные неперекрывающиеся базовые адреса. В случае отсутствия необходимости перебазировании PE-файлы имеют преимущество очень эффективного кода, но при наличии перебазирования издержки в использовании памяти могут быть значительными. Это отличает формат PE от ELF, который использует полностью позиционно-независимый код и глобальную таблицу смещений, которая жертвует временем выполнения в пользу расходования памяти.

    .NET, метаданные и PE-формат [ править | править код ]

    Платформа .NET корпорации Microsoft расширила формат PE с помощью функций, которые поддерживают общеязыковую среду исполнения (Common Language Runtime — CLR). Среди дополнений — заголовок CLR и секция данных CLR. После загрузки двоичного файла загрузчик ОС приводит к выполнению CLR через ссылку в таблице импорта PE/COFF. Затем CLR загружает заголовок CLR и секции данных.

    Секция данных CLR содержит два важных сегмента: сегмент метаданных и сегмент кода промежуточного языка (IL):

    • Метаданные содержат информацию, относящуюся к сборке, включая манифест сборки. Манифест подробно описывает сборку, включая уникальный идентификатор (с помощью хеша, номера версии и т. д.), данные об экспортируемых компонентах, расширенную информацию о типе (поддерживаемую общей системой типов (Common Type System — CTS)), внешние ссылки и список файлов в сборке. Среда CLR широко использует метаданные.
    • Код промежуточного языка (Intermediate Language — IL) — абстрактный, независимый от языка код, который удовлетворяет требованиям общего промежуточного языка (Common Intermediate Language — CIL) .NET CLR. Термин «промежуточный» относится к природе кода IL, обладающего межъязыковой и кроссплатформенной совместимостью. Этот промежуточный язык, подобный байт-кодуJava, позволяет платформам и языкам поддерживать общую среду .NET CLR. IL поддерживает объектно-ориентированное программирование (полиморфизм, наследование, абстрактные типы и т. д.), исключения, события и различные структуры данных.

    Использование в других операционных системах [ править | править код ]

    Формат PE также используется ReactOS, поскольку ReactOS предназначена для того, чтобы быть двоично совместимой с Windows на уровне кода. Кроме того, он исторически использовался многими другими операционными системами, включая SkyOS и BeOS R3. Однако и SkyOS, и BeOS в конечном счёте перешли на формат ELF.

    Поскольку платформа разработки Mono намеревается быть двоично совместимой с Microsoft .NET, она использует тот же формат PE, что и в реализации Microsoft.

    На платформе x86 в Unix-подобных операционных системах некоторые двоичные файлы Windows (в формате PE) могут быть исполнены с помощью Wine. HX DOS Extender также использует формат PE для собственных 32-разрядных двоичных файлов DOS, кроме того, может в некоторой степени выполнить существующие двоичные файлы Windows в DOS, действуя, таким образом, как Wine для DOS.

    Mac OS X 10.5 имеет возможность загружать и интерпретировать PE-файлы, однако они не являются двоично совместимыми с Windows.

    PingVinich

    Технарь

    Доброго времени суток, форумчане. Сегодня, мы немного поговорим, рассмотрим и изучим под микроскопом структуру исполняемых файлов Windows ( Portable EXEcutable , или просто PE ), а в следующих статьях изучим технику модифицирования (инфицирования) PE-файлов, для того, чтобы исполнять свой собственный код после запуска чужого исполняемого файла (кстати, эта техника используется многими вирусами, для собственного «паразитического» распространения) и другие «хаки» с использованием знаний о структуре PE.

    Portable Executabe файл (PE-файл) – это отдельный исполняемый модуль с расширением .exe (или .dll), получаемый в процессе сборки (компиляции и линковкии). В него включены код, ресурсы (иконки и другие данные), библиотеки, данные программы и т.д..

    Давайте проведём аналогию между квартирой и PE-файлом. У каждой квартиры есть свой этаж, своя дверь, прихожая, гостинная, кладовка, свои комнаты, также у каждой квартиры есть своя схема планировки. Вся информация о квартире и сама эта квартира хранится в PE-файле. Взглянем на структуру исполняемого файла, а потом разберём основные части.

    Как мы можем увидить, исполняемый файл состоит из двух основных частей:

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

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

    Давайте изучим по-порядку какие есть заголовки и что в них указано. Заголовками PE-файла являются следующие заголовки в указанном порядке:

    1. DOS заголовок
    2. Заглушка DOS
    3. PE заголовок
    4. Таблица секций

    Начнём наше приключение с изучения DOS заголовка.

    Как я и сказал, заголовки хранят необходимую информацию для загрузки PE-файла. Поэтому данный заголовок является обязательным для загрузки PE-файла, хоть и не несёт в себе большой смысловой информации.

    Заголовок состоит из полей, как список состоит из пунктов свойств. Каждый пункт хранит в себе какое-либо значение. Естественно, в файле всё это представленно в байтовом представлении. Не все поля нужны для загрузки (запуска) PE-файла. Поэтому комментировать и рассматривать мы будем только поля, необходимые для загрузки файла в память.

    Вот его структура на языке C/C++.

    Нас интересуют только первое ( e_magic ) и последнее поле ( e_lfanew ) этого заголовка. Они является самыми важными и влияют непосредственно на загрузку PE-файла.

      e_magic
      Двухбайтовое поле e_magic хранит в себе специальную сигнатуру. Эта сигнатура нужна, чтобы указать что это действительно исполняемый файл. Вот она – «MZ». Каждый PE-файл обязан начинаться с неё. Если это не так, файл просто не запустится.

    e_lfanew
    Четырёхбайтовое поле e_magic хранит в себе смещение до заголовка PE. То есть хранит, количество байтов, которое нужно отсчитать с начала файла, для того, чтобы попасть прямо к PE-заголовку, т.е. проще говоря, адрес PE-заголовка относительно начала файла. Только этот адрес хранится в обратном порядке. Например, на изображении ниже 08 01 00 00 – это 00 00 01 08 (0x108) наоборот. Почему наоборот? Не будем углубляться, но скажу, что компьютеру так легче работать с данными.

    Я выделил самым большим красный прямоугольником область DOS-заголовка. Здесь мы можем увидеть байты в шестнадцатеричном представлении.

    Вы можете поинтересоваться, для чего же другие поля? Дело в том, что система Windows построена на базе старой системы MS DOS (была до Windows). И на самом деле, это заголовок для DOS программы. Так вот, эти поля хранятся на случай, если кто-то попытается запустить PE-файл в DOS . В наше время никто практически ей не пользуется, но они остаются, так как если поменяют формат PE-файла, то перестанут работать программы с нынешним стандартом.

    Дальше у нас по списку PE-заголовок, который на самом деле, состоит из трёх частей: сигнатуры, файлового подзаголовка и дополнительного подзаголовка.

    Вот его структура на языке C/C++:

    Теперь давайте разберём каждое поле по-порядку.

      Signature
      Это четырёхбайтовое поле содержит сигнатуру, а именно значение 50 45 00 00 (или «PEx00x00»). Эта сигнатура указывает на то, что перед нами действительно PE-файл (Ага, ещё одна проверка).

    FileHeader
    Это обязательный подзаголовок PE-заголовка. Он хранит в себе базовые характеристики исполняемого файла.

    На C/C++ структура данного заголовка выглядит так:

    Разберём и этот подзаголовок по порядку.

    • Machine
      Данное двухбайтовое поле содержит информацию о характеристике процессора, на котором может быть выполнена данная программа. Вот 3 основных значения, которые может принять этот заголовок:
      1. IMAGE_FILE_MACHINE_I386 ( 0x014c ) – означает, что программа может выполняться на x32
      2. IMAGE_FILE_MACHINE_IA64 ( 0x0200 ) – означает, что программа может выполняться на процессорах Intel Itanium (Intel x64).
      3. IMAGE_FILE_MACHINE_AMD64 ( 0x8664 ) – означает, что программа может выполняться на процессорах AMD64 (x64).

    NumberOfSections
    Двухбайтовоеполе NumberOfSections содержит в себе число секций (комнат) в PE-файле.

    SizeOfOptionalHeader
    Двухбайтовое поле содержащее размер дополнительного заголовка, который идёт сразу за файловым заголовком.

    Characteristics
    Даное двухбайтовое поле содержит характеристики PE-файла. Например, является ли это exe-файлом, или dll. Также, тут описано, является ли данная программа x64-битной или x86-битной.

    Перейдём к следующему подзаголовку PE-заголовка.

    OptionalHeader
    Это ещё один обязательный подзаголовок PE-файла. В нём хранится необходимая информация для загрузки PE-файла. Он имеет всего два формата PE32+ (для 64-битных программ) и PE32 (для 32-битных).

    Структура дополнительного заголовка представлена следующий C/C++ кодом.

    И на этот раз рассмотрим только основные поля, необходимые для загрузки PE-файла в память.

    • Magic
      Это двухбайтовое поле отвечает за битность программы (x32/x64). Оно может принимать следующие значения:

    1. IMAGE_NT_OPTIONAL_HDR32_MAGIC ( 0x10b ) – означает, что это x32 (x86) исполняемый образ.
    2. IMAGE_NT_OPTIONAL_HDR64_MAGIC ( 0x20b ) – означает, что это x64 исполняемый образ.
    3. IMAGE_ROM_OPTIONAL_HDR_MAGIC ( 0x107 ) – означает, что это ROM образ.

    AddressOfEntryPoint
    Четырёхбайтовое поле AddressOfEntryPoint содержит адрес начала кода, т.е. указатель на дверь в команту кода.
    Техника инфекции, которую мы изучим в будующем, основана на изменении этого значения.

    ImageBase
    Это четырёхбайтовое поле содержит предпочтительный адрес загрузки программы в память. В следующей статье мы разберём для чего он нужен.

    SectionAlignment
    Это четырёхбайтовое поле содержит относительный виртуальный адрес (относительно ImageBase, т. е. сколько байтов нужно отсчитать с адреса загрузки программы, чтобы попасть к началу секций) начала секций в виртуальной памяти.

    FileAlignment
    Это четырёхбайтовое поле содержит смещение относительной файла (сколько байтов нужно отсчитать с начала файла) начала секций в исполняемом файле.

    MajorSubsystemVersion и MinorSubsytemVersion
    В этих двухбайтовых полях содержится необходимая версия Windows.

    SizeOfImage
    Это четырёхбайтовое поле содержит размер (в байтах) загруженного исполняемого файла в памяти.

    SizeOfHeaders
    Четырёхбайтовое поле SizeOfHeaders содержит размер (в байтах) заголовков файла в памяти.

    Subsystem
    Двухбайтовое поле содержащее тип подсистемы (GUI, CLI, Driver, . ).

    NumberOfRvaAndSizes
    Данное четырёхбайтовое поле содержит число каталогов в массиве каталогов. По умолчанию равна 16.

    DataDirectory
    Это поле – на самом деле массив, которая содержит информацию о каталогах. Их число определено в поле NumberOfRvaAndSizes (по умолчанию (и почти всегда) 16) дополнительного заголовка. Каждый элемент информации о каталоге хранит относительный виртуальный адрес (относительно ImageBase, т. е. сколько байтов нужно отсчитать с адреса загрузки программы, чтобы попасть к началу секций) и размер какого-либо каталога (которые являются и секциями), которая определяется по её позиции в массиве.

    Вот структура каталога на языке C/C++:

    А вот идентификаторы (порядковый номер в DataDirectory):

    Перейдём к последнему заголовку. Этот заголовок – таблица, которая содержит различную информацию о секциях . Мы уже знаем, что их количество определено в файловом заголовке в поле NumberOfSections. Проще говоря, это массив с NumberOfSections элементов. Этот массив содержит элементы типа IMAGE_SECTION_HEADER.

    Давайте подробно рассмотрим основные поля.

      Name
      Это поле, размером в 8 байт, содержит имя секции, в ASCII кодировке.

    VirtualSize
    Это четырёхбайтовое поле содержит размер (в байтах) секции (той самой комнаты) в виртуальной памяти.

    VirtualAddress
    А это четырёхбайтовое поле уже содержит относительный адрес секции в виртуальной памяти.

    SizeOfRawData
    Данное четырёхбайтовое поле содержит размер секции в файле.

    PointerToRawData
    А указатель на эти самые данные, содержаться в этом четырёхбайтовом поле.

    Characteristics
    Это четырёхбайтовое поле содержит атрибуты секции. Например, права чтения, записи и исполнения (Read Write Execute) (RWE).

    По сути, в таблице секций просто зафиксирована информация о секциях.

    Вот и всё, мы закончили изучать заголовки. Теперь мы приступаем к изучению секций. По сути, секции являются простыми последовательными блоками данных. Они следуют друг за другом и у них нет определенного формата, так как их характеристики описаны в таблице секций. А вот формат данных, в этих секциях, зависят от типа информации, которая в них хранится. Секции, как я уже сказал, можно представить в виде комнат. Также, их можно представить и как в виде коробок с информацией. Размер каждой секции зафиксирован в таблице секций, поэтому секции должны быть определённого размера, а для этого их дополняют NULL-байтами (00). Вот и всё, что касается секций.

    Также, небольшая шпаргалка, для того, чтобы понимать какое назначение носит имя определенного заголовка секции в таблице:

    • .text : Код
    • .data : Инициализированные данные
    • .bss : Неинициализированные данные
    • .rdata : Константные (рид-онли) данные
    • .edata : Дескрипторы экспорта
    • .idata : Дескрипторы импорта
    • .reloc : Таблица релокации
    • .rsrc : Ресурсы
    • .tls : __declspec(thread) данные

    Также, секциями являются и различные каталоги.

    На этом всё. Спасибо за внимание. Если у Вас есть какие-либо вопросы или вы обнаружите неточности в статье, прошу отписаться в комментариях. Буду рад ответить на все ваши вопросы.

    Читайте также:  Как удалить все файлы приложения windows 10
    Оцените статью