> For the complete documentation index, see [llms.txt](https://ymmfty0.gitbook.io/ymmfty0/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ymmfty0.gitbook.io/ymmfty0/pe-file-format/headers/nt-headers.md).

# NT Headers

## NT Headers

NT Headers — это структура, которая состоит из трёх значений: `Signature`, `FileHeader` и `OptionalHeader`. Для разных типов архитектуры структура будет выглядеть вот так:

```cpp
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
```

### Signature

Первым членом структуры заголовков NT является подпись PE. Он всегда имеет фиксированное значение `0x50450000` что переводится как `PE\0\0` в ASCII.

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

Получить это значение можно вот так

```cpp
DWORD ntHeaderSign = *reinterpret_cast<DWORD*>(pBaseAddr + eLfaNew);
std::cout << "nt header sign: 0x" << std::hex << << ntHeaderSign << std::endl;
```

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FJNoT9pkLBO0NtpB4KHET%2FPasted%20image%2020240904212730.png?alt=media&amp;token=03994fd9-d839-43d5-9591-e7177a276044" alt=""><figcaption></figcaption></figure>

Значение, которое здесь возвращается, мы получаем в формате little-endian, и нули не отображаются. Мы можем обратиться напрямую по адресу, посмотреть в режиме отладки и проверить, всё ли так.

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2F8P8i7KfqAyCNDHLjSK4r%2FPasted%20image%2020240904212124.png?alt=media&amp;token=45ab5eec-99bf-4490-9e40-3bdb4d19436f" alt=""><figcaption></figcaption></figure>

Как видим, значение действительно хранит в себе сигнатурное выражение `PE` Так же мы можем посмотреть это в PE Bear

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FWdDpCg4OTsYB3lHayhr1%2FPasted%20image%2020240904213006.png?alt=media&amp;token=d7a1dd5c-ab2b-4b94-af96-c08826f5d5b5" alt=""><figcaption></figcaption></figure>

### COFF

Прежде всего, давайте разберемся, что такое COFF.

**What is COFF?**

Common Object File Format (COFF) — это формат объектных файлов, который был изначально разработан для операционной системы Unix. Он предназначен для хранения промежуточных результатов компиляции, таких как скомпилированный код, данные, символы и информация о секциях.

Microsoft взяла файл COFF и создала собственную версию для Windows под названием PE COFF или Microsoft Portable Executable COFF

В Windows Visual C++ (и каждый компилятор Windows) генерирует файлы PE COFF. Эти сгенерированные файлы хранят в себе:

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

#### **Что такое таблица символов COFF?**

Таблица символов в формате COFF PE — это структура данных, которая содержит информацию о символах, таких как имена функций, переменных и других объектов, используемых в программе. Она служит для облегчения процесса линковки и отладки программ, особенно на этапе разработки.

Таблица символов представляет собой массив записей длиной 18 байт каждая&#x20;

<https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-symbol-table>

Вот так можно посмотреть таблицу символов:

```powershell
dumpbin /symbols yourfile.obj
```

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2Fwv708jtzcqk4V1KNRYxl%2FPasted%20image%2020240916124538.png?alt=media&amp;token=d1ce3c16-762f-421e-a13d-5a193693f475" alt=""><figcaption></figcaption></figure>

## **FileHeader**

**`FileHeader`** (полное название — **`IMAGE_FILE_HEADER`**) , так же его называют COFF File Header — это структура, которая содержит основную информацию о файле. Эта структура следует сразу после PE-сигнатуры.

Вот так выглядит эта структура

```cpp
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
```

* `Machine` - хранит значение, которое указывает на тип архитектуры исполняемого файла. `0x8864` для `AMD64` и `0x14c` для `i386` . Полный список возможных значений можно найти в [официальной документации Microsoft](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) .
* **`NumberOfSections`** - это поле содержит, количество секций, которые хранит в себе PE файл.
* **`TimeDateStamp`** - дата когда был создан файл в формате `unix`
* `PointerToSymbolTable` - Смещение таблицы символов, в байтах, или ноль, если таблица символов COFF не существует.
* `NumberOfSymbols` - Количество символов в таблице символов.
* `SizeOfOptionalHeader` - Размер OptionalHeader
* `Characteristics`- Флаг или флаги , указывающие на особенности файла. Например: **`IMAGE_FILE_EXECUTABLE_IMAGE` (0x0002)**: Указывает, что файл является исполняемым. Если этот флаг установлен, файл может быть выполнен как программа.

Получить сам FileHeader не так сложно:

```cpp
IMAGE_NT_HEADERS ntHeader = *reinterpret_cast<IMAGE_NT_HEADERS*>(pBaseAddr + eLfaNew);
IMAGE_FILE_HEADER sourceFileHeader = ntHeader.FileHeader;

std::cout << "Machine value: " << sourceFileHeader.Machine << std::endl;
```

Мы уже знаем, как получить NT Headers с помощью смещения `elfanew`.\
Поэтому мы просто записываем всё в структуру `IMAGE_NT_HEADERS`, и оттуда уже можем достать `FileHeader`, где сможем получить нужные данные.

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2Fa03jqmiGEfI6eCVeZbE5%2FPasted%20image%2020240916131654.png?alt=media&amp;token=d80c2475-ae3e-470a-9921-1fad968cea18" alt=""><figcaption></figcaption></figure>

## **OptionalHeader**&#x20;

### RVA

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

### **Введение**

Optional Header — это заголовок, который содержит информацию, необходимую загрузчику для правильной загрузки и запуска файла. Он не имеет фиксированного размера, и его точный размер хранится в поле `SizeOfOptionalHeader` в структуре `IMAGE_FILE_HEADER`.

Этот заголовок называется Optional, потому что некоторые типы файлов, например объектные файлы, его не имеют. Однако для image-файлов этот заголовок обязателен.

В OptonalHeader существует значение Magic, который указывает, какой тип образа файла (например, PE32 или PE32+). 0x10b - для pe32 0x20b - для pe32+

Давайте проверим это при помощи кода

```cpp
IMAGE_NT_HEADERS ntHeader = *reinterpret_cast<IMAGE_NT_HEADERS*>(pBaseAddr + eLfaNew);
IMAGE_OPTIONAL_HEADER sourceOptonalHeader = ntHeader.OptionalHeader;
std::cout << "Magic optional header: " << sourceOptonalHeader.Magic << std::endl;
```

Вот результат вывода

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FzCtY7fC9CkQal95X0XDr%2FPasted%20image%2020240917110938.png?alt=media&amp;token=28307c69-6eca-4b27-a212-516154e9843f" alt=""><figcaption></figcaption></figure>

Проверим так же это в PE-Bear. Для нашего файла , так же magic равен 20b

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FovbdHtk4tDoRsi8UkcdY%2FPasted%20image%2020240917111039.png?alt=media&amp;token=80187344-cef5-44fc-9c8b-1b75e99b2167" alt=""><figcaption></figcaption></figure>

OptionalHeader состоит из трех частей:

* **Стандартные поля** — обязательны для всех реализаций COFF.
* **Специфичные для Windows поля** — добавлены для поддержки особенностей работы в Windows.
* **Data Direcotries** — указывают на специальные таблицы в файле образа, такие как таблицы импорта и экспорта, которые используются операционной системой при загрузке.

**Первые восемь полей Optional Header** — это стандартные поля, которые присутствуют во всех версиях или вариациях формата COFF.

Эти поля содержат общую информацию, полезную для загрузки и запуска исполняемого файла. Они о

* Magic - Значение , которое определяет тип файла. Чаще всего используется число 0x10B, которое идентифицирует его как обычный исполняемый файл. 0x107 идентифицирует его как образ ROM, а 0x20B - как исполняемый файл PE32+. ;
* MajorLinkerVersion - Номер основной версии компоновщика.;
* MinorLinkerVersion - Номер минорной версии компоновщика.;
* SizeOfCode - Размер секции кода (текста) или сумма всех секций кода, если секций несколько.;
* SizeOfInitializedData - Размер инициализированной секции данных или сумма всех таких секций, если секций данных несколько.;
* SizeOfUninitializedData - Размер неинициализированной секции данных (BSS) или сумма всех таких секций, если секций BSS несколько.;
* AddressOfEntryPoint - RVA точки входа, когда файл загружается в память . Для образов программ это начальный адрес. Для драйверов устройств это адрес функции инициализации. Для DLL точка входа необязательна. Если точка входа отсутствует, это поле должно быть равно нулю.;
* BaseOfCode - RVA начала раздела кода, когда файл загружается в память.;

Давайте посмотрим на них при помощи PE-Bear

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FhjkZOCTff75gwwKz7ymu%2FPasted%20image%2020240917115327.png?alt=media&amp;token=118eba0e-1bd2-4627-b336-9f524c326d5d" alt=""><figcaption></figcaption></figure>

PE32 содержит дополнительное поле, отсутствующее в PE32+, следующее за BaseOfCode.&#x20;

* BaseOfData - RVA начала раздела данных, когда файл загружается в память.

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FXNGDrDZoGig836bLaUL3%2FPasted%20image%2020240917153943.png?alt=media&amp;token=28d6f880-5f55-409c-b9e4-6ff92e801cf3" alt=""><figcaption></figcaption></figure>

### **Специфичные для Windows поля**

Это поля содержится только в COFF Windows. Они содержат дополнительную информацию, необходимую компоновщику и загрузчику в Windows.

* **ImageBase** - это поле содержит предпочтительный адрес первого байта изображения при загрузке в память (предпочтительный базовый адрес) , это значение должно быть кратно 64 КБ. Из-за защиты памяти, такой как ASLR, и множества других причин, адрес, указанный в этом поле, почти никогда не используется;
* **SectionAlignment** - это поле хранит в себе значение( в байтах ), кратно которому должна быть равна каждая секция при загрузке в память ( это относится к выравниванию **виртуальных адресов** секции в памяти) . Оно должно быть больше или равно FileAlignment. По умолчанию , для большинства архитектур , это значение равно размеру страницы памяти ( обычно 4096 байт )
* **FileAlignment** - это поле, которое хранит значение в байтах и используется для выравнивания данных секций в **исходном файле на диске**. Оно определяет, как данные каждой секции будут выравниваться в пределах исполняемого файла перед загрузкой в память. Значение должно быть равным степени 2 от 512 вплоть до **65536** (64 КБ). Что имеется в виду под степени (2^9 = 512, 2^10 = 1024 и т.д.) . По умолчанию используется значение 512. Если SectionAlignment меньше размера страницы архитектуры, то FileAlignment должно совпадать с SectionAlignment.
* **MajorOperatingSystemVersion** - это поле хранит значение, указывающее основную версию операционной системы, на который данный файл должен работать корректно.
* **MinorOperatingSystemVersion** - это поле хранит значение, указывающее на минимальную версию операционной системы, на которой данный файл должен корректно работать;
* **MajorImageVersion** - Основной номер версии образа.;
* **MinorImageVersion** - Младший номер версии образа;
* **MajorSubsystemVersion** - Основной номер версии подсистемы.;
* **MinorSubsystemVersion** - Минорный номер версии подсистемы.;
* **Win32VersionValue** - Зарезервированое поле, которое должно быть равно нулю.;
* **SizeOfImage**- Размер файла изображения (в байтах), включая все заголовки. Оно округляется до кратного `SectionAlignment` потому что это значение используется при загрузке изображения в память;
* **SizeOfHeaders** - это поле хранит в себе значение, которые равно сумvарному размеру всех заголовков, включая DOS Stub. Значение должно быть кратно FileAlignment;
* **CheckSum** - контрольная сумма файла изображения. Она используется для проверки изображения во время загрузки.;
* **Subsystem** - в этом поле указывается подсистема Windows (если таковая имеется), необходимая для запуска образа. Полный список возможных значений этого поля можно найти в [официальной документации Microsoft](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) .;
* **DllCharacteristics** - представляет собой битовую маску, которая описывает различные атрибуты и особенности файла. Полный список возможных флагов для `DLLCharacteristics` можно найти в [официальной документации Microsoft](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) .;
* **SizeOfStackReserve** - это поле содержит размер стека, которое должно быть зарезервированное для загрузке в памяти;
* **SizeOfStackCommit** - указывает размер стека, который будет выделен при загрузке процесса в память.
* **SizeOfHeapReserve** - это значение определяет количество памяти, зарезервированной для использования в качестве кучи, когда процесс загружается в память.;
* **SizeOfHeapCommit** - указывает начальный размер памяти, выделенной для кучи при запуске процесса;
* **LoaderFlags** - зарезервировано, должно быть равно нулю.;
* **NumberOfRvaAndSizes** - размер `DataDirectory`;
* **DataDirectory** - Массив `IMAGE_DATA_DIRECTORY` структуры;

Вот так они выглядят в PE-Bear

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2F775y8ZG6XGtpIgXrfZQf%2FPasted%20image%2020240918124829.png?alt=media&amp;token=9024686b-9101-4956-8b17-a0bcbfef734b" alt=""><figcaption></figcaption></figure>

Разберем некоторые поля

Получение AddressOfEntryPoint

```cpp
IMAGE_NT_HEADERS ntHeader = *reinterpret_cast<IMAGE_NT_HEADERS*>(pBaseAddr + eLfaNew);
IMAGE_OPTIONAL_HEADER sourceOptonalHeader = ntHeader.OptionalHeader;

std::cout << "Entry point: " << sourceOptonalHeader.AddressOfEntryPoint << std::endl;
```

Давайте сделаем вывод Subsytem

```cpp
IMAGE_NT_HEADERS ntHeader = *reinterpret_cast<IMAGE_NT_HEADERS*>(pBaseAddr + eLfaNew);
IMAGE_OPTIONAL_HEADER sourceOptonalHeader = ntHeader.OptionalHeader;
std::cout << "Subsytem:" << sourceOptonalHeader.Subsystem << std::endl;
```

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FzC5T8QNG5eFQZPkZVZ46%2FPasted%20image%2020240918151244.png?alt=media&amp;token=73df0b8b-ac1e-4603-8d4d-1bb3e7d5294d" alt=""><figcaption></figcaption></figure>

Если посмотрим в PE-Bear , мы увидим, то что это значение указывает, то что это консольное приложение

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FMNX6AC8Ou43wS0YcXUNc%2FPasted%20image%2020240918151359.png?alt=media&amp;token=f50558c0-d32d-42d0-b0c0-f8995acadd2e" alt=""><figcaption></figcaption></figure>

Давайте сами вычислим SizeOfHeaders. Суть функции простой, размер заголовков и округлить до FileAlligment.

```cpp
DWORD SizeOfHeaders(const PBYTE& pBaseAddr)
{
	if (!pBaseAddr)
	{
		std::cout << "Base address is null." << std::endl;
		return 0;
	}

	PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pBaseAddr);
	DWORD eLfaNew = dosHeader->e_lfanew;
	PIMAGE_NT_HEADERS ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(pBaseAddr + eLfaNew);

	DWORD sizeOfSectionHeaders = ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);

	DWORD combinedSize = sizeof(IMAGE_DOS_HEADER) + eLfaNew + sizeof(IMAGE_NT_HEADERS) + sizeOfSectionHeaders;
	
	std::cout << combinedSize << std::endl;

	DWORD fileAlignment = ntHeaders->OptionalHeader.FileAlignment;

	DWORD roundedSize = ((combinedSize + fileAlignment - 1) / fileAlignment) * fileAlignment;

	return roundedSize;
}
```

Давайте разберём некоторые части более подробно.\
Нам нужно посчитать размер каждого заголовка. Как видно на скриншоте из PE Bear, нужно сложить размеры всех этих заголовков, чтобы получить `SizeOfHeaders`.

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2F8QvFdO1KCTKbcWvzHE8K%2FPasted%20image%2020240918154701.png?alt=media&amp;token=0020a480-fa52-48a3-9e95-929ed829343c" alt=""><figcaption></figcaption></figure>

DOS Header размер , мы получаем при помощи `sizeof(IMAGE_DOS_HEADER),` Размер NT Headers , мы получаем при помощи `sizeof(IMAGE_NT_HEADERS)`. Так как SizeOfHeadesr включает в себе так же DOS Stub, там нужно просто прибавить смещение e\_lfanew `DWORD eLfaNew = dosHeader->e_lfanew`; , которое как раз таки идет от конца DOS Header и до начала NT Header , это как раз так и получается размеру DOS Stub

Теперь нам надо посчитать Section Headers , это просто структура IMAGE\_SECTION\_HEADER, нам нужно просто умножить количество секций на размер этой структуры

```cpp
DWORD sizeOfSectionHeaders = ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
```

Получаем размер

```cpp
DWORD combinedSize = sizeof(IMAGE_DOS_HEADER) + eLfaNew + sizeof(IMAGE_NT_HEADERS) + sizeOfSectionHeaders;
```

И теперь нам просто нужно будет округлить на FileAlligment. Это округление нужно, чтобы просто числа были кратны и было легче с ними работать, как я понял. Ну и chatgpt подсказал, что-то насчет фрагментации данных на диски: *Если размеры не округлены, это может привести к фрагментации данных на диске, что усложняет управление памятью и может снизить производительность.* . Я такое округление нашел на [StackOverflow](https://stackoverflow.com/a/3407392)

```cpp
DWORD roundedSize = ((combinedSize + fileAlignment - 1) / fileAlignment) * fileAlignment;
```

И получаем результат

Давайте проверим

<figure><img src="https://2856195150-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGu8YwYKUroiRnA4eB3ld%2Fuploads%2FpB45L167zn1tUjLHE2VA%2FPasted%20image%2020240918154126.png?alt=media&amp;token=b13db339-9f02-41bb-8028-3356b0c3840d" alt=""><figcaption></figcaption></figure>

Как видим результат такой же, значит все работает правильно.

## Итог

Мы подошли к концу этого поста. Подводя итоги, мы рассмотрели структуру NT Headers и подробно обсудили структуры FileHeader и OptionalHeader.

Для практического понимания работы с заголовком PE-файла, вы можете ознакомиться с кодом в моем репозитории Github: <https://github.com/ymmfty0/PEFormatTutorial/tree/master/NTHeaders>
