Можно ли создать работоспособный исполняемый pe файл без секций text code или data

Обновлено: 04.07.2024

Все тексты о том, как создать компилятор, останавливаются после объяснения лексеров и синтаксических анализаторов. Они не объясняют, как создать машинный код. Я хочу понять сквозной процесс.

В настоящее время я понимаю, что форматы файлов Windows exe называются Portable Executable. Я читал о заголовках, которые у него есть, но пока не нашел ресурса, который бы это легко объяснил.

Моя следующая проблема: я не вижу ресурсов, которые объясняют, как машинный код хранится в файле. Это похоже на 32-битные инструкции фиксированной длины, хранящиеся одна за другой в разделе .text ?

Есть ли место, которое хотя бы объясняет, как создать exe-файл, который ничего не делает (у него есть инструкция No Op). Следующим моим шагом будет подключение к файлам dll для печати на консоли.

Хороший вопрос! У меня нет большого опыта в этом конкретном вопросе, но я бы начал вот так:

PE или ELF не создают чистый машинный код. Он также содержит некоторую информацию заголовка и т. Д. Подробнее: Запись пользовательских данных в исполняемые файлы в Windows и Linux

Я предполагаю, что вы ищете, как файл ELF / PE хранит машинный код, вы можете получить это из этого вопроса (используя objdump): Как извлечь только содержимое раздела ELF

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

Попробуйте какой-нибудь редактор ресурсов, например ResourceEditor, чтобы понять exe, или просто ildasm.

PS: В основном это решения Unix, но я уверен, что PE должен делать что-то принципиально похожее.

Я думаю, что лучший способ подойти к этому - сначала попытаться проанализировать, как работают существующие PE / ELF, в основном обратное проектирование. И для этого хорошей отправной точкой будет Unix-машина. А потом творите чудеса :)

Не тот же, но похожий вопрос здесь.

Обновление:

Я создал дамп объекта из образца кода c. Итак, я полагаю, это то, на что вы нацеливаетесь, верно? Вам нужно знать, генерируете ли вы этот файл (a.out)?

Взгляните на это изображение, время жизни кода C.

enter image description here

Источник Теперь, для ясности, вы хотите реализовать последний шаг, то есть преобразование объектного кода в исполняемый код?

Формат исполняемого файла зависит от ОС. Для Windows это PE32 (32 бит) или PE32 + (64 бит).

То, как будет выглядеть окончательный исполняемый файл, зависит от ABI (двоичного интерфейса приложения) ОС. ABI сообщает, как загрузчик ОС должен загружать исполняемый файл и как он должен перемещать его, будь то DLL или простой исполняемый файл и т. Д.

Каждый объектный файл (исполняемый файл, DLL или драйвер) содержит часть, называемую разделами. Здесь находится весь наш код, данные, таблицы переходов и т. Д.

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

Часть генерации чистого машинного кода полностью зависит от того, насколько оптимизирован ваш код. Но для фактического запуска кода на ПК вам необходимо создать файл со всеми заголовками и соответствующими данными (проверьте MSDN для точного формата PE32 +), а затем поместить весь исполняемый машинный код (который сгенерировал ваш компилятор) в один разделов (обычно код находится в разделе с именем .text). Если вы создали файл, соответствующий формату PE32 +, значит, вы успешно создали исполняемый файл в Windows.

Для Linux можно прочитать и запустить примеры из книги Джонатана Бартлетта «Программирование с нуля»:

Тогда, конечно, можно предпочесть взломать программы Windows. Но, возможно, первое дает лучший способ понять, что происходит на самом деле.

Некоторая информация о том, как уменьшить размер PE-файлов: Tiny PE.

Что касается формата инструкций, то, насколько я помню, набор инструкций x86 имеет переменную длину, включая 1-байтовые инструкции. RISC-процессоры, вероятно, будут иметь инструкции фиксированной длины.

Неудивительно, что лучшие сайты с информацией о написании файлов в формате PE посвящены созданию вирусов.

Поиск в VX Heavens по запросу "PE" дает целую кучу руководств по изменению файлов PE.

Iv'e использовал "Формат файлов Wotsit" в течение многих лет . вплоть до дней MS-Dos :-) и назад, когда это был просто набор текстовых файлов, которые можно было загрузить с большинства систем BBS под названием "The Энциклопедия игровых программистов "

ОБНОВЛЕНИЕ 2 - Я сохраняю редактирование, как было, когда я добавил обновление ошибочно, спасибо тем, кто хотел его отредактировать, но по уважительной причине я его отклоняю:

Пока домен wotsit не будет полностью удален из Интернета и даже DNS не ответит, самое время обернуть все в отдельные архивные ссылки, до тех пор это лучший способ сохранить ссылку.

Вопрос 4. Формат исполняемого РЕ файла ОС Windows . Приведите фрагмент программы, читающей из РЕ файла список импортируемых программой функций ОС.

Комментарии для "Вопрос 4. Формат исполняемого РЕ файла ОС Windows . Приведите фрагмент программы, читающей из РЕ файла список импортируемых программой функций ОС. "

Исполняемый файл на диске и модуль, получаемый после загрузки, очень похожи. Загрузчик попросту использует отображенные в память файлы Win32, чтобы загрузить соответствующие части РЕ-файла в адресное пространство программы. Так же просто загружается и DLL. После того как ЕХЕ или .DLL модуль загружены, Windows обращается с ними так же, как и с другими отображенными в память файлами.

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

Заголовок MS-DOS занимает первые 64 байта PE файла. Структура, представляющая содержание MS-DOS-заголовка следующая:

Основной заголовок РЕ-файла представляет структуру типа IMAGE_NT_НEADERS, определенную в файле WINNT.H. Структура IMAGE_NT_HEADERS в памяти – это то, что Windows использует в качестве своей базы данных модуля в памяти. Каждый загруженный ЕХЕ-файл или DLL представлены в Windows структурой IMAGE_NT_HEADERS. Эта структура состоит из двойного слова и двух подструктур, как показано ниже:

DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;

PE File Signature

Поле Signature (сигнатура – подпись), представленное как ASCII код, – это РЕ\0\0 (два нулевых байта после РЕ). Если поле e_lfanew в заголовке DOS указало вместо обозначения РЕ обозначение NE в этом месте, значит, вы работаете с файлом Win16 NE. Аналогично, если указано обозначение LE в поле Signature, то это файл VxD (VirtualDeviceDriver – драйвер виртуального устройства). Обозначение LX указывает на файл старой соперницы Windows 95 – OS/2.

За двойным словом – сигнатурой РЕ, в заголовке РЕ-файла следует структура типа IMAGE_FILE_HEADER. Поля этой структуры содержат только самую общую информацию о файле.
Далее приводятся поля IMAGE_FILE_HEADER:

typedef struct _IMAGE_FILE_HEADER
<
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
> IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER

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

Intel I386 0xl4C
Intel I860 0xl4D
MIPS R3000 0х162
MIPS R4000 0х166
DEC Alpha AXP 0х184
Power PC 0x1F0 (little endian)
Motorola 68000 0х268
PA RISC 0х290 (Precision Architecture)

NumberOfSections – количество секций в ЕХЕ- или OBJ-файле.

TimeDateStamp – время, когда файл был создан компоновщиком (или компилятором, если это OBJ-файл). В этом поле указано количество секунд, истекших с 16:00 31.12.1969

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

NumberOfSymbols – количество символов в COFF-таблицс символов.

SizeOfOplionalHeader – размер необязательного заголовка, который может следован, за этой структурой. В исполняемых файлах – это размер структуры IMAGE_OPTIONAL_HEADER, которая следует за этой структурой.

Characteristics – флаги, содержащие информацию о файле. Здесь описываются некоторые важные поля.
0х0001 – файл не содержит перемещений
0х0002 – файл представляет исполняемое отображение (т.е. это не OBJ- или LIB-файл)
0х2000 – файл является библиотекой динамической компоновки (DLL), а не программой

PE File Optional Header

Третьим компонентом заголовка РЕ-файла является структура типа IMAGE_OPTIONAL_HEADER. Для РЕ-файлов эта часть является обязательной. Наиболее важными полями являются поля ImageBase и Subsystem.

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

Subsystem – тип подсистемы, которую данный исполняемый файл использует для своего пользовательского интерфейса. WINNT.H определяет следующие значения:
NATIVE = 1 – подсистема не требуется (например, для драйвера устройства)
WINDOWS_GUI = 2 – запускается в подсистеме Windows GUI
WINDOWS_GUI = 3 – запускается в подсистеме Windows character (терминальное приложение)
OS2_GUI = 5 – запускается в подсистеме OS/2 (только приложения OS/2 IJC)
POSIX_GUI = 7 – запускается в подсистеме Posix

Сразу после заголовка РЕ-файла в памяти следует массив из 1MAGE_SECT10N_HEADER. Эта таблица. содержит информацию о каждой секции отображения. Количество элементов этого массива задается в заголовке РЕ-файла (поле IMAGE_NT_HEADER.FileHeader.NumberOfSections). Секции в отображении упорядочены по их стартовому адресу, а не в алфавитном порядке.
Каждый IMAGE_SECTION_HEADER представляет собой полную базу данных об одной секции файла ЕХЕ или OBJ \ и имеет следующий формат.

Misc – это поле имеет различные назначения в зависимости от того, встречается ли оно в ЕХЕ- или OBJ-файле. В ЕХЕ-файле оно содержит виртуальный размер секции программного кода или данных. В случае OBJ-файлов это поле указывает физический адрес секции.

VirtualAddress – в случае ЕХЕ-файлов это поле содержит RVA, куда загрузчик должен отобразить секцию. Средства Microsoft устанавливают по умолчанию RVA первой секции равным 0х101. Для объектных файлов это поле устанавливается в 0.

SizeOfRawData – в ЕХЕ-файлах это поле содержит размер секции, выровненный па ближайшую верхнюю границу размера файла.
PointerToRawData – файловое смещение участка, где находятся исходные данные для секции. Если пользователь сам отображает в мять РЕ- или COFF-файл (вместо того, чтобы доверить загрузку операционной системе), это поле важнее, чем в VirtualAddress.

PointerToRelocations – в объектных файлах это файловое смещение информации о поправках, которая следует за исходными данными для данной секции. В ЕХЕ-файлах это поле устанавливается в 0.

PointerToLinenumhers – файловое смещение таблицы номеров строк. Таблица номеров строк ставит в соответствие номера строк исходного файла адресам, по которым можно найти код, сгенерированный для данной строки. Обычно только секции с программным кодом (например, .text или CODE) имеют номера строк. В ЕХЕ-файлах номера строк собраны в конце файла после исходных данных для секций. В объектных файлах таблица номеров строк для секции следует за исходными данными секции и таблицей перемещений для этой секции.

NumberOfRelocations – количество перемещений в таблице поправок для данной секции (используется только в объектных файлах).
NumberOfLinenumbers – количество номеров строк в таблице номеров строк для данной секции.
Characteristics – набор флагов, которые указывают на атрибуты секции (программа/данные, предназначен для чтения, предназначен для записи и т.н.).

Часто встречающиеся секции

Секция .text (или CODE , если PE-файл создан Borland C++)

В этой секции собран весь программный код общего назначения, генерируемый компилятором или ассемблером. Компоновщик объединяет все секции .text из различных объектных файлов в одну большую секцию .text в ЕХЕ-файле.

Секция .data (или DATA , если PE-файл создан Borland C++)

Инициализированные данные попадают в секцию .data. Инициализированные данные состоят из тех глобальных и статических переменных, которые были проинициализированы во время компиляции. Они также включают строковые литералы (например, строку "Hello World" в программе C/C++). Компоновщик объединяет все секции .data из разных объектных и LIB-файлов в одну секцию .data в ЕХЕ-файле. Локальные переменные расположены в стеке цепочки и не занимают места в секциях .data и .bss.

В секции .bss хранятся неинициализированные статические и глобальные переменные. Компоновщик объединяет все сек¬ции .bss из разных объектных и LIB-файлов в одну секцию .bss в ЕХЕ-файле.

Еще одна секция для инициализированных данных, используемая библиотеками поддержки выполнения программы Microsoft C/C++. Данные из этой секции используются для таких це¬лей, как вызов конструкторов статических классов C++ перед вызовом main или WinMain.

Секция .rsrc содержит ресурсы модуля.

Секция .idata (или таблица импорта) содержит информацию о функциях (и данных), которые модуль импортирует из других DLL. Таблица импорта начинается с массива, состоящего из IMAGE_IMPORT_DESCRIPTOR. Каждый элемент (IMAGE_IMPORT_DESCRIPTOR) соответствует одной из DLL, с кото¬рой неявно связан данный РЕ-файл. Количество элементов в массиве нигде не учитывается. Вместо этого последняя структу¬ра массива IMAGE_IMPORT_DESCRIPTOR имеет поля, содержащие NULL.
Структура IMAGE_IMPORT_DESCRIPTOR имеет следующий формат

typedef struct _IMAGE_IMPORT_DESCRIPTOR <
union <
DWORD Characteristics;
DWORD OriginalFirstThunk;
>;
DWORD TimeDateStamp;

DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
> IMAGE_IMPORT_DESCRIPTOR

Characteristics/OriginalFirstThunk – в этом поле содержится смещение (RVA) массива двойных слов. Каждое из этих двойных слов в действительности является объединением IMAGE_THUNK_DATA. Каждое двойное слово IMAGE_THUNK_DATA соответствует одной функции, импортируемой данным ЕХЕ-файлом или DLL.

TimeDateStamp – отметка о времени и дате, указывающая, когда был создан данный файл.
ForwarderChain – это поле имеет отношение к передаче, когда одна DLL передает ссылку на какую-то свою функцию другой DLL.
Name – это RVA строки символов ASCII, оканчивающейся нулем и содержащей имена импортируемых DLL.

FirstThunk – RVA-смещение массива двойных слов IMAGE_THUNK_DATA. В большинстве случаев двойное слово рассматривает¬ся как указатель на структуру IMAGE_IMPORT_BY_NAME. Это структура выглядит следующим образом:

typedef struct _IMAGE_IMPORT_BY_NAME <
WORD Hint;
BYTE Name[1];
> IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

Hint – номер экспорта у функции импорта.
Name – Строка ASCIIZ с именем импортируемой функции.

Фрагмент программы читающей из РЕ файла список импортируемых программой функций ОС.

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

На рис. 2.3 изображена схема PE-файла. Слева показана структура файла на диске, а справа - его образ в памяти. Мы видим, что PE-файл начинается с заголовков, за которыми располагаются несколько секций.


Рис. 2.3. PE-файл на диске и в оперативной памяти

В секциях размещаются код и данные исполняемого файла, а также служебная информация, необходимая загрузчику (например, секция ". reloc " на схеме содержит таблицу релокаций). Секции в оперативной памяти должны быть выровнены по границам страниц, поэтому загрузчик отображает каждую секцию, начиная с новой страницы адресного пространства процесса. Это приводит к тому, что в памяти секции, как правило, располагаются менее компактно, чем в файле (и это отражено на схеме).

Так как расположение элементов PE-файла в памяти и на диске отличаются, для их локализации приходится вводить два понятия: относительный виртуальный адрес элемента в памяти (Relative Virtual Address - RVA ) и смещение элемента в файле (file offset).

RVA некоторого элемента PE-файла - это разность виртуального адреса данного элемента и базового адреса, по которому PE-файл загружен в память. Например, если файл загружен по адресу 0x400000, и некоторый элемент в нем располагается по адресу 0x402000, то RVA этого элемента равен (0x402000 - 0x400000) = 0x2000.

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

Загрузчик формирует образ PE-файла в памяти таким образом, что соблюдается следующее правило: пусть ox и oy - смещения каких-то элементов в файле, а rx и ry - RVA этих элементов, тогда если ox < oy , то rx < ry .

Секции

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

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

Каждая секция имеет имя. Оно не используется загрузчиком и предназначено главным образом для удобства человека. Разные компиляторы и компоновщики дают секциям различные имена. Например, компоновщик от Microsoft размещает код в секции ".text", константы - в секции ".rdata", таблицы импорта и экспорта - в секциях ".idata" и ".edata", таблицу релокаций - в секции ". reloc ", ресурсы - в секции ".rsrc". В то же время компоновщик фирмы Borland использует имена "CODE" для секций, содержащих код, и "DATA" для секций с данными.

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

Выбор базового адреса образа PE-файла в памяти

Давайте обсудим, каким образом загрузчик определяет базовый адрес, по которому нужно загрузить PE-файл. Для exe-файлов это тривиальная задача: в заголовках файла присутствует поле ImageBase , содержащее значение базового адреса. Так как для выполнения exe-файла создается отдельный процесс со своим виртуальным адресным пространством, то обычно не возникает никаких проблем с тем чтобы отобразить файл в это адресное пространство по заданному адресу. Как правило, все exe-файлы содержат в поле ImageBase значение 0x400000.

Импорт функций

В PE-файле существует специальная секция ".idata", описывающая функции, который этот файл импортирует из динамических библиотек. Описание импортируемых функций в секции ".idata" приводит к тому, что библиотеки загружаются загрузчиком операционной системы еще до запуска программы. В принципе, необязательно описывать каждую импортируемую функцию в этой секции, так как динамические библиотеки можно загружать с помощью функции LoadLibrary из Win32 API прямо во время выполнения программы.

В процессе загрузки программы осуществляется связывание (binding) функций, импортируемых из динамических библиотек. Связывание подразумевает загрузку соответствующих динамических библиотек и составление таблицы адресов импорта (Import Address Table - IAT). Адрес каждой импортируемой функции заносится в эту таблицу и в дальнейшем используется для вызова данной функции.

Экспорт функций

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

Сразу после заголовка PE в файле располагается массив заголовков секций. Его размер задается полем NumberOfSections заголовка файла. Каждый заголовок секции состоит из 0x28 байт и имеет следующую структуру:

Смещение (hex) Размер Тип Название Описание
00 8 CHAR[8] Name Название секции.
08 4 DWORD Misc.VirtualSize Размер секции в памяти. Если это значение больше SizeOfRawData, то секция дополняется в памяти нулевыми байтами.
0C 4 DWORD VirtualAddress RVA секции в памяти.
10 4 DWORD SizeOfRawData Размер секции в файле. Всегда кратен FileAlignment из необязательного заголовка. Если секция содержит только неинициализированные данные, то это поле равно нулю.
14 4 DWORD PointerToRawData Смещение в файле до начала данных секций. Всегда кратно FileAlignment из необязательного заголовка. Если секция содержит только неинициализированные данные, то это поле равно нулю.
18 4 DWORD PointerToRelocations В исполняемых файлах это поле всегда равно нулю.
4 DWORD PointerToLinenumbers В исполняемых файлах это поле всегда равно нулю.
20 2 WORD NumberOfRelocations В исполняемых файлах это поле всегда равно нулю.
22 2 WORD NumberOfLinenumbers В исполняемых файлах это поле всегда равно нулю.
24 4 DWORD Characteristics Атрибуты секции.

Прокомментируем некоторые поля.

Название секции

Название секции содержит от 0 до 8 ASCII-символов. Вместо константы 8 можно использовать определение IMAGE_SIZEOF_SHORT_NAME. Если длина названия меньше 8 символов, то оно дополняется нулевыми байтами. Если оно состоит ровно из 8 символов, то завершающего нулевого байта нет. Важно отметить, что название секции, вообще говоря, никак не соотносится с ее содержимым. Каждый компилятор использует свое собственное соглашение о именовании секций, поэтому полагаться на название секции при ее анализе нельзя. Единственно надежным способом определить, что содержит данная секция, является анализ ее атрибутов и содержащихся в ней каталогов данных.

Атрибуты секции

32-битовое поле Characteristics содержит набор флагов, описывающих содержимое данной секции. Секции исполняемого файла могут иметь следующие атрибуты:

Название Значение Описание
IMAGE_SCN_CNT_CODE 0x00000020 Секция содержит исполняемый код.
IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 Секция содержит инициализированные данные.
IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 Секция содержит неинициализированные данные.
IMAGE_SCN_MEM_DISCARDABLE 0x02000000 Секция может быть удалена из памяти после создания процесса.
IMAGE_SCN_MEM_NOT_CACHED 0x04000000 Секция не может кэшироваться.
IMAGE_SCN_MEM_NOT_PAGED 0x08000000 Секция не может выгружаться в файл подкачки.
IMAGE_SCN_MEM_SHARED 0x10000000 Все копии данного файла могут иметь один общий экземпляр этой секции. По-видимому, используется только для секций данных динамических библиотек.
IMAGE_SCN_MEM_EXECUTE 0x20000000 Секцию можно исполнять как программный код.
IMAGE_SCN_MEM_READ 0x40000000 Секцию можно читать.
IMAGE_SCN_MEM_WRITE 0x80000000 В секцию можно писать.

4.2. Содержимое секций

Сами секции располагаются в файле после всех заголовков секций. Каждая секция выравнена на границу FileAlignment из необязательного заголовка.

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

4.3. Доступ к секциям

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

где pHeader – указатель на заголовок PE, возвращаемый функцией GetHeader.

Для перебора всех заголовков секций удобно использовать такой цикл:

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

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

В начале файла располагается DOS-MZзаголовок. Он определен следующим образом:

Все что нас интересует здесь - это только одно значение - e_lfanew. Это двойное слово является RVAи указывает на структуру IMAGE_NT_HEADERS. Размер DOS-MZ заголовка составляет 80 байт.

Файловый заголовок находиться в PE-файле сразу же после сигнатуры IMAGE_NT_SIGNATURE. В файле WINNT.Hона определена как 00004550H. Файловый заголовок содержит наиболее общую информацию о данном файле. В файле WINNT.H файловый заголовок определен следующим образом:

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

WORDMachine;

Два байта содержащие платформу, для которой создавался данный PE-файл. Возможные значения приведены ниже.

ОС Windows поддерживает только две архитектуры и все они - процессоров Intel– IA-32, IA-64. Исходя из этого, только два значения считаются корректными в PE-файле IMAGE_FILE_MACHINE_IA64 и IMAGE_FILE_MACHINE_I386. Если Вы подставите чего-либо другое, загрузчик откажется загружать данный файл. Да и то для 32х разрядных операционных систем (т.е. работающих с 32х разрядными процессорами) – значение единственное - IMAGE_FILE_MACHINE_I386. Очень интересно еще и то, что в официальной спецификации о некоторых значениях просто умалчивается, просто умалчивается и все!

WORD NumberOfSections;

Количество секций в PE-файле. Значение должно быть верным. Фактически означает число элементов в таблице секций.

DWORD TimeDateStamp;

Информация о времени, когда был собран данный PE-файл. Это значение равно количеству секунд прошедших с 1 января 1970 года до времени создания файла. В стандартной библиотеке Си есть замечательная функция gmtime, которая переводит время из секунд в удобочитаемый вид. Она берет указатель на DWORD – количество секунд и заполняет структуру tm, определенную в time.h. Эта структура выглядит следующим образом:

Чтобы узнать какой дате это число соответствует, используйте следующую функцию

Читайте также: