Содержание статьи
Что такое PE-файл
Portable Executable (переносимый исполняемый файл) — это официальное название формата файлов .exe, .dll и .sys для Windows. Слово portable здесь означает переносимость формата между разными версиями Windows и разными архитектурами процессоров. Ну а слово executable говорит нам о том, что этот файл содержит в себе инструкции для процессора, которые он будет послушно исполнять.
Создание заготовки
Наша программа будет довольно простой: мы немного подергаем Windows за ее API и попросим вывести диалоговое окошко с заголовком «Wake up, Neo...» и надписью «The Matrix has you...».
Для наших дальнейших изысканий создадим пустой файл и начнем потихоньку заполнять его данными. Для этого нам потребуется Hex-редактор. Лично я предпочитаю Hiew, который даже в наше время не теряет актуальности. Это очень мощный инструмент, но мы воспользуемся только его встроенным компилятором ассемблера, остальное заполним руками.
Для создания пустого файла в Windows PowerShell есть встроенная утилита fsutil. Наша будущая программа будет иметь размер 2 Кбайт. Так туда поместится заголовок, секция программы, секция с данными и секция импорта. Конечно, они могли бы поместиться и в гораздо более скромный объем, но дальше ты поймешь, почему я выбрал именно такой размер.
fsutil file createnew Program.exe 2048
Структура PE-файла
Следующая картинка показывает структуру PE-файла. По сути, это краткий пересказ всей статьи. Спасибо автору Ange Albertini за столь наглядную иллюстрацию и Lyr1k — за ее перевод на русский язык.

Как видно по схеме, PE-файл состоит из заголовка и некоторого количества секций. В нашем случае их будет три.
Заголовок
В общих чертах заголовок — это специальная структура, которая содержит информацию, необходимую операционной системе для загрузки и выполнения программы. Давай взглянем на него подробнее.
Заголовок DOS
Заголовок DOS — рудимент, сохраненный со времен MS-DOS для обратной совместимости.
Смещение Размер Поле
[0x00] 2 e_magic;
[0x02] 2 e_cblp;
[0x04] 2 e_cp;
[0x06] 2 e_crlc;
[0x08] 2 e_cparhdr;
[0x0A] 2 e_minalloc;
[0x0C] 2 e_maxalloc;
[0x0E] 2 e_ss;
[0x10] 2 e_sp;
[0x12] 2 e_csum;
[0x14] 2 e_ip;
[0x16] 2 e_cs;
[0x18] 2 e_lfarlc;
[0x1A] 2 e_ovno;
[0x1C] 2 e_res[4];
[0x24] 2 e_oemid;
[0x26] 2 e_oeminfo;
[0x28] 2 e_res2[10];
[0x3C] 4 e_lfanew;
info
Размер в этой и следующих таблицах указан в байтах.
Сейчас необходимости запускать файлы в DOS нет, а значит, разбирать эту структуру полностью не будем. Нам здесь важны только два элемента — первый и последний (без них ничего работать не будет):
-
e_magic— всегда имеет значениеMZ(4d 5a); -
e_lfanew— содержит в себе смещение до PE-заголовка. В нашем случае смещение будет 0х40, то есть PE-заголовок будет сразу следовать за DOS-заголовком.
Запишем эти значения в нашу программу.

DOS-заглушка
Стоит также упомянуть DOS-заглушку. Компоновщик вставляет ее сразу за DOS-заголовком и перед PE-заголовком. Это небольшая программа для DOS, которая обычно выводит сообщение «This program cannot be run in DOS mode». Она также является рудиментом, и без нее можно спокойно обойтись, так что в нашей программе она присутствовать не будет.
Заголовок PE
Далее идет PE-заголовок, адрес которого мы записали в переменной e_lfanew. Он состоит из сигнатуры и COFF (Common Object File Format) заголовка файла.
Signature
Смещение Размер Поле
[0x00] 4 Signature
COFF header
Смещение Размер Поле
[0x00] 2 Machine
[0x02] 2 NumberOfSections
[0x04] 4 TimeDateStamp
[0x08] 4 PointerToSymbolTable
[0x0C] 4 NumberOfSymbols
[0x10] 2 SizeOfOptionalHeader
[0x12] 2 Characteristics
-
Signature — сигнатура PE-файла. Используется загрузчиком для проверки того, что файл действительно является PE. Всегда имеет значение
PE\(50 45 00 00).x00\ x00
Структура COFF-заголовка файла такова:
- Machine — архитектура процессора. Нужна для правильной расшифровки команд. Мы будем писать под архитектуру x86, соответственно, значение этого поля будет 0х14c. С полным списком поддерживаемых архитектур можно ознакомиться на сайте.
- NumberOfSections — количество секций. В нашем примере их будет три.
-
TimeDateStamp — время создания файла в формате UNIX timestamp. Возьмем 31.03.1999, дата выхода первой «Матрицы». Вписываем значение
0х3700f500. - PointerToSymbolTable — указатель на таблицу символов. Таблица символов формируется, например, для дебага, и в ней содержатся имена функций, переменных и меток. Мы такую таблицу формировать не будем, так что это поле оставим равным нулю.
- NumberOfSymbols — количество символов в таблице символов. Также оставляем нуль.
- SizeOfOptionalHeader — размер опционального заголовка, который следует за нашим PE-заголовком. Для нашей 32-битной версии он будет равен 224 байт (0xE0), для 64-битной — 240 байт (0xF0). Этот размер можно немного уменьшить за счет уменьшения количества директорий данных, которые находятся в конце опционального заголовка, но делать это не имеет смысла. Несколько свободных байтов в заголовке нам ничего не дадут, но, если мы решим дополнить программу еще одной директорией данных, нам придется переписывать таблицу секций, поскольку она уже будет не на своем месте.
-
Characteristics — это слово содержит атрибуты нашего файла. Здесь нам понадобится флаг
IMAGE_FILE_EXECUTABLE_IMAGE(0x0002), который означает, что файл исполняемый. Также в этом слове может быть один из флагов, напримерIMAGE_FILE_SYSTEM, если файл системный (.sys), илиIMAGE_FILE_DLL, если это DLL. Посмотреть полный список доступных атрибутов можно в документации.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
