A dive into the PE file format - PE file structure - Part 4: Data Directories, Section Headers and Sections
A dive into the PE file format - PE file structure - Part 4: Data Directories, Section Headers and Sections
Introduction
In the last post we talked about the NT Headers and we skipped the last part of the Optional Header which was the data directories.
In this post we’re going to talk about what data directories are and where they are located.
We’re also going to cover section headers and sections in this post.
Data Directories
The last member of the IMAGE_OPTIONAL_HEADER
structure was an array of IMAGE_DATA_DIRECTORY
structures defined as follows:
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
IMAGE_NUMBEROF_DIRECTORY_ENTRIES
is a constant defined with the value 16
, meaning that this array can have up to 16 IMAGE_DATA_DIRECTORY
entries:
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
An IMAGE_DATA_DIRETORY
structure is defines as follows:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
It’s a very simple structure with only two members, first one being an RVA pointing to the start of the Data Directory and the second one being the size of the Data Directory.
So what is a Data Directory? Basically a Data Directory is a piece of data located within one of the sections of the PE file.
Data Directories contain useful information needed by the loader, an example of a very important directory is the Import Directory which contains a list of external functions imported from other libraries, we’ll discuss it in more detail when we go over PE imports.
Please note that not all Data Directories have the same structure, the IMAGE_DATA_DIRECTORY.VirtualAddress
points to the Data Directory, however the type of that directory is what determines how that chunk of data is going to be parsed.
Here’s a list of Data Directories defined in winnt.h
. (Each one of these values represents an index in the DataDirectory array):
// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
If we take a look at the contents of IMAGE_OPTIONAL_HEADER.DataDirectory
of an actual PE file, we might see entries where both fields are set to 0
:
This means that this specific Data Directory is not used (doesn’t exist) in the executable file.
Sections and Section Headers
Sections
Sections are the containers of the actual data of the executable file, they occupy the rest of the PE file after the headers, precisely after the section headers.
Some sections have special names that indicate their purpose, we’ll go over some of them, and a full list of these names can be found on the official Microsoft documentation under the “Special Sections” section.
-
.text
: Contains the executable code of the program. -
.data
: Contains the initialized data. -
.bss
: Contains uninitialized data. -
.rdata
: Contains read-only initialized data. -
.edata
: Contains the export tables. -
.idata
: Contains the import tables. -
.reloc
: Contains image relocation information. -
.rsrc
: Contains resources used by the program, these include images, icons or even embedded binaries. -
.tls
: (Thread Local Storage), provides storage for every executing thread of the program.
Section Headers
After the Optional Header and before the sections comes the Section Headers. These headers contain information about the sections of the PE file.
A Section Header is a structure named IMAGE_SECTION_HEADER
defined in winnt.h
as follows:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
-
Name
: First field of the Section Header, a byte array of the sizeIMAGE_SIZEOF_SHORT_NAME
that holds the name of the section.IMAGE_SIZEOF_SHORT_NAME
has the value of8
meaning that a section name can’t be longer than 8 characters. For longer names the official documentation mentions a work-around by filling this field with an offset in the string table, however executable images do not use a string table so this limitation of 8 characters holds for executable images. -
PhysicalAddress
orVirtualSize
: Aunion
defines multiple names for the same thing, this field contains the total size of the section when it’s loaded in memory. -
VirtualAddress
: The documentation states that for executable images this field holds the address of the first byte of the section relative to the image base when loaded in memory, and for object files it holds the address of the first byte of the section before relocation is applied. -
SizeOfRawData
: This field contains the size of the section on disk, it must be a multiple ofIMAGE_OPTIONAL_HEADER.FileAlignment
.SizeOfRawData
andVirtualSize
can be different, we’ll discuss the reason for this later in the post. -
PointerToRawData
: A pointer to the first page of the section within the file, for executable images it must be a multiple ofIMAGE_OPTIONAL_HEADER.FileAlignment
. -
PointerToRelocations
: A file pointer to the beginning of relocation entries for the section. It’s set to0
for executable files. -
PointerToLineNumbers
: A file pointer to the beginning of COFF line-number entries for the section. It’s set to0
because COFF debugging information is deprecated. -
NumberOfRelocations
: The number of relocation entries for the section, it’s set to0
for executable images. -
NumberOfLinenumbers
: The number of COFF line-number entries for the section, it’s set to0
because COFF debugging information is deprecated. -
Characteristics
: Flags that describe the characteristics of the section.
These characteristics are things like if the section contains executable code, contains initialized/uninitialized data, can be shared in memory.
A complete list of section characteristics flags can be found on the official Microsoft documentation.
SizeOfRawData
and VirtualSize
can be different, and this can happen for multiple of reasons.
SizeOfRawData
must be a multiple of IMAGE_OPTIONAL_HEADER.FileAlignment
, so if the section size is less than that value the rest gets padded and SizeOfRawData
gets rounded to the nearest multiple of IMAGE_OPTIONAL_HEADER.FileAlignment
.
However when the section is loaded into memory it doesn’t follow that alignment and only the actual size of the section is occupied.
In this case SizeOfRawData
will be greater than VirtualSize
The opposite can happen as well.
If the section contains uninitialized data, these data won’t be accounted for on disk, but when the section gets mapped into memory, the section will expand to reserve memory space for when the uninitialized data gets later initialized and used.
This means that the section on disk will occupy less than it will do in memory, in this case VirtualSize
will be greater than SizeOfRawData
.
Here’s the view of Section Headers in PE-bear:
We can see Raw Addr.
and Virtual Addr.
fields which correspond to IMAGE_SECTION_HEADER.PointerToRawData
and IMAGE_SECTION_HEADER.VirtualAddress
.
Raw Size
and Virtual Size
correspond to IMAGE_SECTION_HEADER.SizeOfRawData
and IMAGE_SECTION_HEADER.VirtualSize
.
We can see how these two fields are used to calculate where the section ends, both on disk and in memory.
For example if we take the .text
section, it has a raw address of 0x400
and a raw size of 0xE00
, if we add them together we get 0x1200
which is displayed as the section end on disk.
Similarly we can do the same with virtual size and address, virtual address is 0x1000
and virtual size is 0xD2C
, if we add them together we get 0x1D2C
.
The Characteristics
field marks some sections as read-only, some other sections as read-write and some sections as readable and executable.
PointerToRelocations
, NumberOfRelocations
and NumberOfLinenumbers
are set to 0
as expected.
Conclusion
That’s it for this post, we’ve discussed what Data Directories are and we talked about sections.
The next post will be about PE imports.
Thanks for reading.