PE - это собственный 32-битный формат файла Windows, а PE + - собственный 64-битный формат файла Windows. Каждый 32-битный исполняемый файл Windows (кроме VxD и 16-битных DLL) использует формат файла PE.

32-разрядные библиотеки DLL, файлы COM, элементы управления OCX, апплеты панели управления (файлы .CPL) Серверы Scren и исполняемый файл .NET являются форматом PE. Даже драйверы режима ядра NT используют формат файла PE.

Тем не менее, зачем нам об этом знать? Есть несколько причин. Например, добавление кода в исполняемый файл, изменение исходной точки входа программы на компрессор, распаковка исполняемого файла вручную и тому подобное. В настоящее время большинство вредоносных программ упаковано, чтобы уменьшить размер и обеспечить дополнительный уровень защиты от исследователя вредоносных программ.

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

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

Базовая структура PE-файла

На рисунке выше показана базовая структура PE-файла. Также я должен упомянуть здесь это примечание, по крайней мере, PE-файл будет иметь раздел кода и раздел данных, но приложение для Windows может иметь другие предопределенные разделы, такие как text, bss (начало блочного хранилища), rdata (данные только для чтения), data (инициализированные данные), rsrc (ресурсы), edata (данные экспорта), idata (данные импорта), pdata (я не знаю, какова цель этого раздела) и раздел отладки.

Однако раздел в переносимом исполняемом (PE) файле - это именованный непрерывный блок памяти, содержащий код или данные.

Некоторые разделы содержат код или данные, которые ваша программа объявила и использует напрямую, в то время как другие разделы данных создаются для вас компоновщиком и менеджером библиотеки (lib.exe) и содержат информацию, жизненно важную для операционной системы.

Некоторым приложениям не нужны все эти разделы, о которых я упоминал, в то время как другие могут определять еще больше разделов в соответствии со своими конкретными потребностями - это совершенно необязательно. Кроме того, названия этих разделов можно изменить с помощью Visual Studio - ›Свойства проекта -› Компоновщик - ›Дополнительно -› Объединить разделы.

Параметр / MERGE в Visual Studio объединяет первый раздел со вторым разделом, называя получившийся раздел в. Например, /merge:.text=.Milad. Если второй раздел не существует, LINK переименовывает раздел из в в. Параметр / MERGE полезен для создания VxD и переопределения имен разделов, созданных компилятором.

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

Однако точно не копируется в память. Динамический загрузчик Windows решает, какие части необходимо сопоставить в (Page In), и пропускает (Page Out) любые другие. Данные, которые не отображаются, помещаются в конец файла, как информация об отладке.

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

Когда разделы загружаются в ОЗУ, они выравниваются, чтобы соответствовать страницам памяти размером 4 КБ (это указывается элементом выравнивания разделов в необязательном заголовке PE), причем каждый раздел начинается с новой страницы.

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

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

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

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

Было бы непрактично иметь запись в таблице для каждого байта памяти (таблица страниц будет больше, чем общая физическая память), поэтому вместо этого процессоры делят память на страницы. Это дает несколько преимуществ:

  1. Это позволяет создавать несколько адресных пространств с разным доступом и рабочим уровнем. Адресное пространство - это изолированная таблица страниц, которая разрешает доступ только к памяти, имеющей отношение к текущей программе или процессу. Это гарантирует, что программы полностью изолированы друг от друга и что ошибка, приводящая к сбою одной программы, не может отравить адресное пространство другой программы.
  2. Это позволяет процессору применять определенные правила доступа к памяти. Разделы необходимы в файлах PE, потому что разные области файла по-разному обрабатываются диспетчером памяти при загрузке модуля. Во время загрузки диспетчер памяти устанавливает права доступа к страницам памяти для различных разделов на основе их настроек в заголовке раздела. Это определяет, доступен ли данный раздел для чтения, записи или выполнения. Это означает, что каждый раздел обычно должен начинаться с новой страницы.

Примечание. Однако размер страницы по умолчанию для 32-битной Windows составляет 4096 байт, и было бы расточительно выравнивать исполняемый файл по границе страницы в 4096 байт на диске, поскольку это сделало бы их значительно больше, чем необходимо. По этой причине заголовок PE имеет два разных поля выравнивания: выравнивание раздела и выравнивание файла. Выравнивание разделов - это то, как разделы выравниваются в памяти, а выравнивание файлов с размером 512 байт - это то, как разделы выравниваются в файле на диске и кратны размеру сектора диска, чтобы оптимизировать процесс загрузки. На картинке ниже показан этот размер.

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

Модуль (объект) в памяти представляет весь код, данные и ресурсы из исполняемого файла, которые необходимы для выполнения, в то время как термин процесс в основном относится к изолированному адресному пространству, которое может использоваться для запуска такого модуля.

В следующей статье я продолжу обсуждение спецификации PE. Если у вас есть идеи и комментарии по поводу этих статей, отправьте свое сообщение по адресу [email protected].