Format plików PE/PE32+ dla początkujących
🕰 ✒️ Dawid Farbaniec 📄 1520 słów0x01. Wprowadzenie
Pliki wykonywalne, których tak często się używa w systemie Windows mogą być nieco owiane tajemnicą dla osób niezajmujących się programowaniem niskopoziomowym i inżynierią odwrotną kodu (ang. RCE). Pliki wykonywalne z rozszerzeniem *.exe, które tak często uruchamiasz jako użytkownik, a może i budujesz jako programista posiadają określony format nazywany PE (z ang. przenośny wykonywalny) oraz zmodyfikowaną wersję PE32+. Określenie Portable Executable (z ang. przenośny wykonywalny) informuje, że format ten jest niezależny od architektury. Warto jednak wspomnieć, że 32-bitowe wersje Windows korzystają z formatu PE, natomiast wersje 64-bitowe Windows obsługują dodatkowo format PE32+. Format PE obejmuje nie tylko pliki wykonywalne, ale także obiektowe oraz biblioteki DLL. Jako, że format PE bazuje na Unixowym formacie plików COFF, to często używa się też określenia PE/COFF.
Podstawowe pojęcia
Przesunięcie w pliku (ang. file offset) — liczba wyznaczająca położenie określonych danych od ustalonego miejsca w pliku. Bardzo często miejscem startowym jest początek pliku, który ma offset równy zero.
Adres wirtualny (ang. Virtual Address, VA) — jest to adres do danych po załadowaniu ich do pamięci. Prawie zawsze określa się go po prostu adresem. Można spotkać też określenia: liniowy lub efektywny. Adres wirtualny zawiera w sobie tzw. ImageBase, czyli miejsce w pamięci pod które ładowany jest plik.
Relatywny adres wirtualny (ang. Relative Virtual Address, RVA) — ten adres można otrzymać odejmując ImageBase danego modułu od adresu wirtualnego (VA) w tym module.
Schemat ogólny formatu PE
Ogólny schemat budowy pliku PE przedstawiono na rysunku 1.1. Bardziej szczegółowy opis określonych elementów znajdziesz w dalszej części wpisu.

0x02. Struktura pliku PE
Nagłówek MS-DOS (MS-DOS Stub)
Na samym początku pliku PE znajduje się nagłówek MS-DOS.
Dwa pierwsze bajty to 0x4D
i 0x5A
, czyli
znaki ASCII "MZ"
. Te dwie litery to inicjały
Marka Zbikowskiego (Mark Joseph Zbikowski)
jednego z ważniejszych programistów systemu MS-DOS.
Sygnatura "MZ"
technicznie określa, że nagłówek
MS-DOS jest programem typu EXE dla tego podsystemu.
Kod aplikacji nazywanej MS-DOS Stub został wstawiony
na samym początku pliku PE w celach informacyjnych.
Podczas próby uruchomienia w systemie MS-DOS pliku PE dla systemu Windows
program MS-DOS Stub wyświetli informację
This program cannot be run in DOS mode.
Istnieje możliwość zmiany programu MS-DOS Stub za pomocą opcji konsolidatora (ang. linker).
Nagłówek MS-DOS w postaci struktury można znaleźć w pliku winnt.h, który zawiera środowisko Visual Studio (Visual C++).
Sygnatura MZ oraz nagłówek MS-DOS (Visual C++, winnt.h):
Warto zaznaczyć, że w polu e_lfanew
(adres w pliku: 0x3C
) znajduje się wartość przesunięcia (ang. offset),
która określa gdzie znajduje się sygnatura "PE"
, czyli początek nagłówka PE.
Na rysunku 2.1 przedstawiono przykładowy plik PE otwarty w edytorze szesnastkowym XVI32. Pokolorowane obszary oznaczają poszczególne elementy pliku PE. Na rysunku 2.1 zmieścił się nagłówek MS-DOS, program MS-DOS Stub oraz początek nagłówka PE.

Sygnatura PE (PE Signature)
Sygnatura PE (wartości: 0x50
i 0x45
, czyli znaki ASCII "PE"
)
określa początek nagłówka PE. W pliku winnt.h sygnaturę definiuje stała:
Nagłówek COFF (COFF Header)
Po sygnaturze PE znajduje się nagłówek COFF, który posiada następujące pola:
Nazwa pola | Przesunięcie (ang. offset) | Rozmiar | Opis |
Machine | 0 |
2 | Numer określający typ maszyny docelowej. Dla architektury x64 będzie to stała IMAGE_FILE_MACHINE_AMD64 (wartość: 0x8664). Szczegóły w specyfikacji. |
NumberOfSections | 2 |
2 | Ilość sekcji w pliku PE. |
TimeDateStamp | 4 |
4 | Znacznik czasowy określający kiedy plik został utworzony (typ wartości time_t z języka C). |
PointerToSymbolTable | 8 |
4 | Adres w pliku do tablicy symboli debugowania. Dla plików wykonywalnych powinno być tu zero. |
NumberOfSymbols | 12 |
4 | Ilość wpisów w tablicy symboli debugowania. Dla plików wykonywalnych powinno być tu zero. |
SizeOfOptionalHeader | 16 |
2 | Rozmiar opcjonalnego nagłówka PE (Optional Header). Wymagane tylko dla plików wykonywalnych. |
Characteristics | 18 |
2 | Flagi określające atrybuty pliku. Szczegóły w specyfikacji. |
Definicja nagłówka COFF w pliku winnt.h prezentuje się następująco:
Opcjonalny nagłówek PE (PE Optional Header)
Kolejnym elementem, który można wyróżnić w formacie PE jest nagłówek opcjonalny. Jego nazwa informuje, że nie występuje on zawsze. Pliki wykonywalne muszą go posiadać, natomiast pliki obiektowe nie.
Nagłówek ten różni się dla 32-bitowych i 64-bitowych plików wykonywalnych.
Struktura IMAGE_OPTIONAL_HEADER32
odpowiada plikom PE (32-bit), natomiast struktura
IMAGE_OPTIONAL_HEADER64
jest dla plików PE32+ (64-bit).
Definicja nagłówka opcjonalnego dla plików PE32+ w nagłówku winnt.h prezentuje się następująco:
Warto zaznaczyć, że sygnatura PE, nagłówek COFF oraz nagłówek opcjonalny
zostały objęte w struktury o nazwach _IMAGE_NT_HEADERS
oraz _IMAGE_NT_HEADERS64
.
Definicja struktury _IMAGE_NT_HEADERS
z pliku winnt.h prezentuje się następująco:
Szczegóły i opis pól nagłówka opcjonalnego w specyfikacji.
Nagłówki sekcji (Section Headers)
Nagłówki sekcji znajdują się zaraz po nagłówku opcjonalnym. Zawierają one informacje o sekcjach, jakie zawiera plik PE. W celu otrzymania adresu do nagłówków sekcji należy obliczyć przesunięcie sumując rozmiar nagłówków występujących wcześniej.
Rozmiar nagłówka sekcji to 40 bajtów
(#define IMAGE_SIZEOF_SECTION_HEADER 40
).
Każdy nagłówek sekcji posiada następujące pola:
Nazwa pola | Przesunięcie (ang. offset) | Rozmiar | Opis |
Name | 0 |
8 | Nazwa sekcji jako napis UTF-8 o maksymalnej długości 8 bajtów (dla plików wykonywalnych). W przypadku krótszej nazwy puste miejsce jest wypełniane zerami. |
VirtualSize | 8 |
4 | Rozmiar wirtualny sekcji, czyli po załadowaniu do pamięci. |
VirtualAddress | 12 |
4 | Dla plików wykonywalnych jest to adres początku sekcji relatywnie do adresu ImageBase (RVA). |
SizeOfRawData | 16 |
4 | Dla plików wykonywalnych jest to rozmiar sekcji w pliku na dysku. |
PointerToRawData | 20 |
4 | Adres w pliku do danych sekcji. |
PointerToRelocations | 24 |
4 | Dla plików wykonywalnych tutaj zero. |
PointerToLinenumbers | 28 |
4 | Dla plików wykonywalnych tutaj zero. |
NumberOfRelocations | 32 |
2 | Dla plików wykonywalnych tutaj zero. |
NumberOfLinenumbers | 34 |
2 | Dla plików wykonywalnych tutaj zero. |
Characteristics | 36 |
4 | Flagi określające atrybuty sekcji. Szczegóły w specyfikacji. |
Definicja nagłówka sekcji w pliku winnt.h prezentuje się następująco:
Dane sekcji (Section Data)
Dane sekcji znajdują się po nagłówkach sekcji. Adres do danych określonej sekcji można uzyskać poprzez
odczytanie wartości pola PointerToRawData
z nagłówka sekcji.
Rozmiar sekcji określa natomiast wartość pola SizeOfRawData
.
Szczególne typy sekcji
Warte uwagi są zarezerwowane sekcje specjalne dotyczące plików wykonywalnych.
Nazwa sekcji | Opis |
.bss | Dane niezainicjalizowane w wolnym formacie. |
.data | Dane zainicjalizowane w wolnym formacie. |
.edata | Tabele eksportu. Zawierają symbole, które pozwalają innym plikom wykonywalnym korzystać z funkcji, które są eksportowane przez ten moduł. |
.idata | Tabele importu. Zawierają symbole importowanych funkcji przez plik wykonywalny. |
.rdata | Dane zainicjalizowane tylko do odczytu. |
.text | Kod do wykonania w wolnym formacie. |
... | Szczegóły w specyfikacji |
0x03. Trochę kodu dla przykładu
Na listingu 3.1 poniżej przedstawiono program, który czyta określony plik PE,
a następnie odczytuje wartość pola e_magic
z nagłówka MS-DOS.
Działanie programu z listingu 3.1 przedstawiono na rysunku 3.1.

0x04. Różnice pomiędzy PE, a PE32+
Różnice w strukturze IMAGE_OPTIONAL_HEADER32
, a IMAGE_OPTIONAL_HEADER64
przedstawiono w tabeli poniżej.
Nazwa pola | Format PE | Format PE32+ |
BaseOfData |
ULONG |
Usunięto |
ImageBase |
ULONG |
ULONGLONG |
SizeOfStackReserve |
ULONG |
ULONGLONG |
SizeOfStackCommit |
ULONG |
ULONGLONG |
SizeOfHeapReserve |
ULONG |
ULONGLONG |
SizeOfHeapCommit |
ULONG |
ULONGLONG |
0x05. Zakończenie
Mam nadzieję, że artykuł ten przybliżył początkującym format PE/COFF.
Nie chciałem tego wprowadzenia za bardzo obciążać dużą ilością szczegółowych opisów.
Z tego powodu jako kolejny krok polecam dokument
Microsoft Portable Executable and Common Object File Format Specification firmy Microsoft.
Dziękuję za przeczytanie!
Wykaz literatury (bibliografia)
[1] Microsoft Corporation, 2001 – Microsoft Portable Executable and Common Object File Format Specification[2] Microsoft Corporation, 2002 – An In-Depth Look into the Win32 Portable Executable File Format