Understanding Portable Executable Format
Understanding Portable Executable Format
Portable Executable
File Format
IN THIS CHAPTER
MICROSOFT INTRODUCED A NEW executable file format with Windows NT. This for-
mat is called the Portable Executable (PE) format because it is supposed to be
portable across all 32-bit operating systems by Microsoft. The same PE format exe-
cutable can be executed on any version of Windows NT, Windows 95, and Win32s.
Also, the same format is used for executables for Windows NT running on proces-
sors other than Intel x86, such as MIPS, Alpha, and Power PC. The 32-bit DLLs and
Windows NT device drivers also follow the same PE format.
It is helpful to understand the PE file format because PE files are almost identi-
cal on disk and in RAM. Learning about the PE format is also helpful for under-
standing many operating system concepts. For example, how operating system
loader works to support dynamic linking of DLL functions, the data structures in-
volved in dynamic linking such as import table, export table, and so on.
The PE format is not really undocumented. The WINNT.H file has several struc-
ture definitions representing the PE format. The Microsoft Developer's Network
(MSDN) CD-ROMs contain several descriptions of the PE format. However, these
descriptions are in bits and pieces, and are by no means complete. In this chapter,
we try to give you a comprehensive picture of the PE format.
Microsoft also provides a DLL with the SDK that has utility functions for inter-
preting PE files. We also discuss these functions and correlate them with other in-
formation about the PE format.
223
224 Part 11: Undocumented Windows NT
Overview of a PE File
In this section, we discuss the overall structure of a PE file. In the sections that fol-
low, we go into detail about the PE format. A PE file comprises various sections.
Because Microsoft's 32-bit operating systems follow the flat memory model, an ex-
ecutable no longer contains segments. Still, different parts of an executable, such as
code and data, have different characteristics. These different parts of an executable
are stored as different sections. Thus, a PE file is a concatenation of data stored
in sections.
A few sections are always present in a PE file generated by the Microsoft linker.
Other linkers may generate similar sections with different names. A PE file generated
with the Microsoft linker has a .text section that contains the code bytes concate-
nated from all the object files. As for the data, it can be classified into different cat-
egories. The .data section contains all the initialized global and static data, while the
.bss section contains the uninitialized data. The read-only data, such as string liter-
als and constants, is stored in the .rdata section. This section also contains some
other read-only structures, such as the debug directory, the Thread Local Storage
(TLS) directory, and so on, which we explain later in this chapter. The .edata section
contains information about the functions exported from a DLL, while the .idata sec-
tion stores information about the functions imported by an executable or a DLL. The
.rsrc section contains various resources, such as menus and dialog boxes. The .reloc
section stores the information required for relocating the image while loading.
The names of the sections do not have any significance. As mentioned earlier,
different linkers may use different names for the sections. Programmers can also
create new sections of their own. The #pragma code_seg and #pragma data_seg
macros can be used to create new sections while working with Microsoft compiler.
The operating system loader locates the required piece of information from the data
directories present in the file headers. Shortly, we will present an overview of file
headers and then look at them in more detail.
Structure of a PE File
Apart from the sections consisting of the actual data, a PE file contains various head-
ers that describe the sections and the important information present in the sections.
If you look at the hex dump of a PE file, the first 2 bytes might look familiar.
Aren't they M and Z? Yes, a PE file starts with the DOS executable header. It is fol-
lowed by a small program that prints an error message saying that the program
cannot be run in DOS mode. It's the same idea that was used in 16-bit Windows ex-
ecutables. This program code is executed, if the PE image is run under DOS.
After the DOS header and the DOS executable stub comes the PE header. A field
in the DOS header points to this new header. The PE header starts with the 4-byte
signature "PE" followed by two nulls. The PE format is based on the Common Object
Chapter 11: Portable Executable Eile Format 225
File Format (COFF) used by Unix. The PE signature is followed by the object file
header borrowed from COFF. This header is present also for the object files produced
by Microsoft's 32-bit compilers. This header contains some general information
about the file, such as the target machine ID, the number of sections in the file, and
so forth. The COFF style header is followed by the optional header. This header is op-
tional in the sense that it is not required for the object files. As far as executables
and DLLs are concerned, this header is mandatory. The optional header has two
parts. The first part is inherited from COFF and can be found in all COFF files. The
second part is an NT-specific extension of COFF. Apart from other NT-specific infor-
mation, such as the subsystem type, this part also contains the data directory. The
data directory is an array in which each entry points to some important piece of in-
formation. One of the entries in the data directory points to the import table of the
executable or DLL, another entry points to the export table of the DLL, and so on.
The data directory is followed by the section table. The section table is an array of
section headers. A section header summarizes the important information about the
respective section. Finally, the section table is followed by the sections themselves.
We hope that this gives you an overview of the organization of a PE file. Before
diving into the details of the PE format, let's discuss a concept that is vital in inter-
preting a PE file. . . . .
memory-mapped file. You first need to find out the section in which the given RVA
lies. You can accomplish this by iterating through the section table. Each section
header stores the starting RVA for the section and the size of the section. A section
is guaranteed to be contiguously loaded in memory. Hence, the offset from the start
of the section for a particular piece of data is bound to be the same whether the file
is memory mapped or loaded by the operating system loader for execution. Hence,
to find out the address in a memory-mapped file, you simply need to add this off-
set to the base address of the section in the memory-mapped file. Now, this base ad-
dress can be calculated from within the file offset of the section, which is also
stored in the respective section header. Quite an easy procedure, isn't it?
ImageRvaToVaQ
Don't worry, there is an easier way out. Microsoft comes to our rescue here with
[Link]. This DLL exports a function that computes the address in the
memory-mapped file, given an RVA.
LPVOID ImageRvaToVa(
PIMAGE_NT_HEADERS NtHeaders,
LPVOID B a s e ,
DWORD R v a ,
P I M A G E _ S E C T I O N _ H E A D E R* L a s t R v a S e c t i o n
); " '
PARAMETERS
RETURN VALUES
If the function succeeds, the return value is the virtual address in the mapped file;
otherwise, it is NULL. The error number can be retrieved using the GetLastErrorQ
function.
ImagelMtHeaderQ
The ImageRvaToVaQ function needs a pointer to the PE header. The ImageNtHeader
exported from the [Link] can provide you this pointer.
P I M A G E _ N T _ H E A D E R S ImageNtHeader(
L P V O I D ImageBase
);
PARAMETERS
ImageBase Base address where the PE file is mapped into memory using the
Win32 API for the memory mapping of files.
RETURN VALUES
If the function succeeds, the return value is a pointer to the IMAGE_NT_HEADERS
structure within the mapped file; otherwise, it returns NULL.
MapAndLoadQ
The [Link] can also take care of memory mapping a PE file for you. The
MapAndLoadQ function maps the requested PE file in memory and fills in the
LOADED_IMAGE structure with some useful information about the mapped file.
BOOL MapAndLoad(
LPSTR ImageName,
LPSTR D l l P a t h ,
PLOADED_IMAGE Loadedlmage.
BOOL D o t D l l .
BOOL Readonly
);
PARAMETERS
ImageName Name of the PE file that is loaded.
DllPath Path used to locate the file if the name provided cannot be
found. If NULL is passed, then normal rules for searching
using the PATH environment variable are applied.
228 Part 11: Undocumented Windows NT
The function sets the members in the structure appropriately after loading the
PE file.
RETURN VALUES
If the function succeeds, the return value is TRUE; otherwise, it is FALSE.
UnrVlapAndLoadQ
After you are done with the mapped file, you should call the UnMapAndLoadO
function. This function unmaps the PE file and deallocates the resources allocated
by the MapAndLoadQ function.
Chapter 11: Portable Executable File Format 229
PARAMETERS
Loadedlmage Pointer to a LOADEDJMAGE structure that is returned from a
call to the MapAndLoadQ function.
RETURN VALUES
If the function succeeds, the return value is TRUE; otherwise, it is FALSE.
We will discuss the other useful functions from this DLL as we continue in this
chapter.
typedef s t r u c t _IMAGE_NT_HEADERS {
DWORD S i g n a t u r e ;
IMAGE_FILE_HEADER F i l e H e a d e r ;
IMAGE_OPTIONAL_HEADER O p t i o n a l H e a d e r ;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
The signature is PE followed by two nulls, as mentioned earlier. The COFF style
header is represented by the IMAGE_FILE_HEADER structure and is followed by the
optional header represented by the IMAGE_OPTIONAL_HEADER structure. The
fields in the COFF style header are as follows:
MachineTarget machine ID. Various values are defined in the WINNT.H file — for
example, Oxl4C is used for Intel 80386 (and compatibles) and 0x184 is used for
Alpha AXP.
230 Part 11: Undocumented Windows NT
The COFF style header is followed by the optional header. The optional
header is absent in the object files. The format of the optional header is de-
Microsoft added some NT-specific fields to the optional header. These fields are
as follows:
c3 '
ImageBase If the file is loaded at this address in memory, the loader need
not do any base relocations. This is because the linker resolves
all the base relocations at the time of linking, assuming that the
file will be loaded at this address. We discuss this in more detail
in the section on the relocation table. For now, it is enough to
know that the loading time is reduced if a file gets loaded at the
preferred base address. A file may not get loaded at the preferred
base address because of the nonavailability of the address. This
happens when more than one DLL used by an executable use the
same preferred base address. The default preferred base address is
0x400000. You may want to have a different preferred base
address for your DLL so that it does not clash with that of any
other DLL used by your application. You can change the
preferred base address using a linker switch. You can also change
the base address of a file using the rebase utility that comes with
the Win32 SDK.
ReBaselmageQ
The ReBaselmageO function from the [Link] also enables you to change
the preferred base address.
I "I
PARAMETERS
CurrentlmageName Filename that is rebased.
SymbolPath In case the symbolic debug information is stored as a
separate file, the path to find the corresponding symbol
file. This is required to update the header information
and timestamp of the symbol file.
fReBase The file is really rebased only if this value is TRUE.
fRebaseSysfileOk If the file is a system file with the preferred base address
above 0x80000000, it is rebased only if this flag is
TRUE.
fGoingDown If you want the loaded image of the file to lie entirely
below the given address, set this flag to TRUE. For
example, if the loaded size of a DLL is 0x2000 and you
call the function with the fGoingDown flag as TRUE and
give the address as 0x600000, the DLL will be rebased at
0x508000.
ChecklmageSize Rebasing might change the loaded image size of the file
because of the section alignment requirements. If this
parameter is nonzero, the file is rebased only if the
changed size is less than this parameter.
OldlmageSize Original image size before the rebase operation is
returned here.
OldlmageBase Original image base before the rebase operation is
returned here.
NewImageSize New loaded image size after the rebase operation is
returned here.
NewImageBase New base address. Upon return, it contains the actual
address where the file is rebased.
TimeStamp New timestamp for the file.
RETURN VALUES
If the function succeeds, the return value is TRUE; otherwise, it is FALSE.
The other fields in the optional header are as follows:
234 Part 11: Undocumented Windows NT
ImageDirectoryEntryToDataQ
The VirtualAddress field contains the RVA of the respective piece of information,
and the Size field contains the size of the data. To get to the actual data, you need
to convert the RVA to the actual address in the memory-mapped PE file. This can be
accomplished with the ImageDirectoryEntryToDataO function exported by IM-
[Link].
236 Part 11: Undocumented Windows NT
PARAMETERS ?t»-
RETURN VALUES
If the function succeeds, the return value is the address in the memory-mapped file
where the required data resides. Otherwise, the function returns NULL.
Export Directory
The data directory entry at the IMAGE_DIRECTORY_ENTRY_EXPORT index points
to the export directory for the file. The RVA in this directory entry points to the
.edata section. The information about the functions exported by the file (generally
a DLL) is stored here. The data directory entry points to the export directory that is
defined as the IMAGE_EXPORT_DIRECTORY structure in the WINNT.H file. The
fields in this structure are as follows:
When an export-functions array entry is not a forwarder - that is, the RVA does
not lie within the export section - the RVA points to the entry point of the function
or to the location of the exported variable.
The export-functions array may have gaps. This is beacause some ordinals might
be left unused while exporting functions, and some ordinals might not have any
corresponding export. In such a case, the corresponding array entry is set to 0.
Import Directory
The next index in the data directory, IMAGE_DIRECTORY_ENTRY_IMPORT, is re-
served for the import directory of an executable/DLL. The RVA in this data directory
entry points to the import directory, which is nothing but a variable-sized array of
IMAGE_IMPORT_DESCRIPTORs, one for each imported DLL. The first field in this
structure is a union. If the Characteristics field in this union is 0, it indicates the end
of the variable-sized import descriptors array. Otherwise, the union is interpreted
using the other member, OriginalFirstThunk.
TimeDateStamp This field is set to 0, unless the imports are bound. Soon,
we discuss what's meant by binding the imports of a PE
file.
ForwarderChain The field is used only if the imports are bound.
Name RVA of the ASCIIZ string that stores the name of the
imported DLL.
FirstThunk RVA of the Import Address Table (LAT). The IAT is another
array parallel to the ILT, unless the image is bound. The
IAT also has ordinals or pointers to the IMAGE_IMPORT_
BY_NAME structures. When the loader resolves the
import references, it replaces the entries in the IAT with
the actual addresses of the corresponding functions.
Astonishingly, that is all it needs to do to achieve
dynamic linking — everything else is already set in place
by the linker and import librarian. Let's see how all these
components work together to achieve dynamic linking.
While loading, the entries in the IAT are replaced by the actual function addresses,
and that's it. Now when the function is called, the control is transferred to the stub
function that performs an indirect jump. As the IAT entry contains the address of
the actual function from the DLL, the control is transferred to the required function.
The situation is a bit different if you use the new _declspec(dllimport) directive
while prototyping an imported function. In that case, the compiler itself generates
an import table. In addition, it generates an indirect call referring to the appropri-
ate location in the generated IAT. This method does away with the overhead of an
extra jump.
BindlrnageQ
The bind utility that is shipped with Win32 SDK enables binding of PE files. Also,
the Bindlmage and BindlmageExO functions in the [Link] provide this
functionality.
BOOL Bindlmage(
LPSTR ImageName,
LPSTR DllPath,
LPSTR Symbol Path
) . -
Chapter 11: Portable Executable File Format 241
PARAMETERS
ImageName The filename of the file to be bound. This can contain only a
filename, a partial path, or a full path.
DllPath A root path to search for ImageName if the filename contained
in ImageName cannot be opened.
SymbolPath A root path to search for the corresponding symbol file. If the
symbol file is stored separately, the header of the symbol file is
changed to reflect the changes in the PE file. -
RETURN VALUES
If the function succeeds, the return value is TRUE; otherwise, it is FALSE.
BindlmageExQ
This function is very similar to Bindlmage function except it provides more cus-
tomization such as getting a periodic callback during the progress of binding
process.
BOOL BindImageEx(
IN DWORD F l a g s ,
IN LPSTR ImageName,
IN LPSTR D l l P a t h ,
IN LPSTR SymbolPath,
IN PIMAGEHLP_STATUS_ROUTINE S t a t u s R o u t i n e
);
PARAMETERS
This function has the following additional parameters:
RETURN VALUES
If the function succeeds, the return value is TRUE; otherwise, it is FALSE.
Calling Bindlmage is equivalent to calling BindlmageEx with Flags as 0 and
StatusRoutine as NULL. That is, calling BindlmageflmageName, DllPath, SymbolPath)
is equivalent to calling BindImageEx(0, ImageName, DllPath, SymbolPath, NULL).
Resource Directory
The next index in the data directory, IMAGE_DIRECTORY_ENTRY_RESOURCE,
refers to the resource directory for a PE file. The resource directory and the re-
sources themselves are generally stored in a section named .rsrc section. The re-
sources are maintained in a tree structure similar to that in a file system. The root
directory contains subdirectories. A subdirectory can contain subdirectories or re-
source data. The subdirectories can be nested to any level. But Windows NT only
uses a three-level structure. At each level, the resource directory branches accord-
ing to certain characteristics of the resources. At the first level, the type of the re-
source - bitmap, menu, and so on - is considered. All the bitmaps are stored under
one subtree, all the menus are stored under another subtree, and so on. At the next
level, the name of the resource is considered, and the third level classifies the re-
source according to the language ID. The third-level resource directory points to a
leaf node that stores the actual resource data.
A resource directory consists of summary information about the directory fol-
lowed by the directory entries. Each directory entry has a name or ID that is inter-
preted as a type ID, a name ID, or a language ID, depending on the level of the
directory. A directory entry can point either to the resource data or to a subdirec-
tory that has a similar format.
The format of the resource directory is defined as the IMAGE_RESOURCE_DI-
RECTORY structure in WINNT.H.
Relocation Table
A PE file needs only based relocations. The linker resolves all the relative reloca-
tions, assuming that the file will get loaded at the preferred base address. For exam-
ple, if a function foo has the RVA as 0x100 and the preferred base address is
0x400000, the linker resolves the call to foo as a call to address 0x400100. At run
time, if the file is loaded at the preferred base address of 0x400000, then no reloca-
tion needs to be preformed. If, for some reason, the file cannot be loaded at the base
address of 0x400000, the loader needs to patch the call. If the loader manages to
load the file at a base address of 0x600000, it needs to change the call address to
0x600100. In general, it needs to add the difference of 0x200000 to all the to-be-
patched locations. This process is called as the based relocation. The list of the to-be-
patched locations, also called as fixups, is maintained in the relocation table that is
generally present in the .reloc section and is pointed to by the data directory entry at
the IMAGE_DIRECTORY_ENTRY_BASERELOC index. The relocation table is nothing
but a series of relocation blocks, each representing the fixups for a 4K page. Each re-
location block has a header followed by the relocation entries for the corresponding
page. The relocation block format is defined as the IMAGE_BASE_RELOCATION
structure in the WINNT.H file, and it has following fields:
244 Part 11: Undocumented Windows NT
Each relocation entry is a 16-bit word. The higher 4 bits indicate the type of re-
location, and the lower 12 bits are the offset of the fixup location within the 4K
page. The address-to-patched is calculated by adding the base address for loading,
the RVA of the page to be patched, and the 12-bit offset within the page. The relo-
cation types are defined in the WINNT.H file - only two of them are used on Intel
machines:
Debug Directory
The operating system is not concerned with the debug information present in a PE
file. The debugging tools access the debug information in a PE file. There are vari-
ous debugging tools, which expect the debug information in different formats. The
corresponding compilers/linkers also store the debug information in different for-
mats. The PE format allows the debug information to be stored in different formats,
such as COFF, Frame Pointer Omission (FPO), CodeView (CV4), and so on. A single
file may contain debug information in more than one format. The debug directory
>
pointed to by the IMAGE_DTRECTORY_ENTRY_DEBUG entry in the data directory
is an array of debug directory entries, one for each debug information format. The
IMAGE_DEBUG_DIRECTORY structure in the WINNT.H file represents the format of
a debug directory entry.
Characteristics Reserved.
Section Table
We've roamed through the PE format without bothering about the section formats.
This is possible because of the data directory that directly locates the important
pieces of information within a PE file. You need not know about the sections at all
to interpret a PE file. Nevertheless, in case you need to modify a PE file, you may
be required to know about the sections and section headers. For example, you may
want to add, remove, or extend a particular section, and this requires changes to
the section table, among other things.
As mentioned earlier, the PE header is followed by the section table. The section
table is an array of section headers. The format of the section header is defined by
the IMAGE_SECTION_HEADER structure in the WINNT.H file. The members of a
section header are as follows:
Chapter 11: Portable Executable File Format 247
Loading Procedure
Let's see how the loader interprets a PE file and prepares a memory image for exe-
cution. The loader needs to find the free virtual address space to map the file in
memory. The loader tries to load the image at the preferred base address. After this
is done, the loader maps the sections in memory. The loader goes through the sec-
tion table and maps each section at the address calculated by adding the RVA of the
section to the base address. The page attributes are set according to the section's
characteristic requirements. After mapping the section in memory, the loader per-
forms based relocation if the base address is not equal to the preferred base address.
Then, the import table is checked and the required DLLs are loaded. The same pro-
cedure for loading an executable - mapping sections, based relocation, resolving
imports, and so on - is applied while loading a DLL. After loading each DLL, the
IAT is fixed to point to the actual imported function address.
That's it! The image is ready for execution.
Summary
Microsoft introduced the Portable Executable (PE) file format with Windows NT.
The PE format serves as the executable file format for all the 32-bit Microsoft oper-
ating systems (that is, the various versions of Windows NT and Windows 95/98)
though these operating systems still support the older executable file formats, in-
cluding the DOS executable file format.
Chapter 11: Portable Executable File Format 249
Various components in a PE file are addressed using the relative virtual address
(RVA). The [Link] provides us with utility functions to memory map a PE
file to find the address in the memory corresponding to the RVA specified in the PE
file. A PE file is composed of the file headers, the data directory, the section table,
and the various sections. The data directory points to the important parts of the PE
file: the export directory, the import directory, the relocation table, the debug direc-
tory, and the Thread Local Storage. The export directory lists the symbols exported
from the PE file, which is most likely a DLL. The import directory lists all the sym-
bols imported by the PE file. When a PE file is loaded in memory for execution, the
loader resolves the imported symbols to actual virtual addresses in the DLL that ex-
ports the symbols. This process is termed dynamic linking.
The PE headers are followed by the section table that points to all the sections,
including the ones pointed to by the various data directory entries. The loader reads
the section table and maps various sections of a PE file in memory. Then it prepares
the image for execution by relocating the image for the mapped address and re-
solving various imported symbols after loading the required DLLs.