Introduction

The Portable Executable, commonly abbreviated as PE, is the main executable file format used by Windows. Files such as .exe, .dll, .sys, .scr, .ocx, and .cpl are usually PE files.

A PE file is not just a block of executable code. It is a structured file that tells Windows how the image should be loaded into memory, where execution should begin, which DLLs are required, which functions are imported or exported, which sections should be readable, writable, or executable, and which security-related metadata is present.

For malware development, malware analysis, reverse engineering, and detection engineering, understanding PE files is important because many techniques require inspecting or modifying PE headers, parsing imports and exports, resolving API addresses, locating sections, reading shellcode from resources, identifying packed binaries, or understanding how the Windows loader prepares an image for execution.

The official Microsoft PE format documentation can be found here.

It is worth noting that this module and future modules will often refer to executables, DLLs, and drivers as images. In Windows terminology, an image is an executable file that can be mapped into a process address space.

File On Disk vs Image In Memory

Before going into the PE headers, it is important to understand that a PE file has two different views:

  1. The File View - This is how the PE exists on disk. Offsets are file offsets, meaning positions inside the physical file. The term file offset will be repeated throughout this course to refer to a location inside the PE file before it is loaded by Windows.
  2. The Memory View - This is how the PE looks after the Windows loader maps it into a process address space. Addresses in this view are virtual addresses. Similar to the file offset term, the terms virtual address (VA) and relative virtual address (RVA) will be repeated throughout this course to refer to locations inside the loaded image. Calculating and resolving virtual addresses from RVAs is an advanced topic for now and is discussed later in future modules.

These two views are related, but they are not identical. Windows does not simply copy the PE file byte-for-byte into memory and execute it from the same layout that exists on disk. Instead, the Windows loader (Windows component that loads PE files into memory) reads the PE headers, reserves memory for the image, maps each section into memory, applies the required permissions, resolves API imports, performs relocations if needed, and then transfers execution to the image entry point. This means that a byte located at file offset 0x400 on disk may appear at RVA 0x1000 after the image is mapped into memory.

All the aforementioned steps implemented by the Windows Loader will be discussed in further detail in later modules.

PE Structure

A PE file contains several headers followed by one or more sections. The headers describe the file, and the sections contain the actual code, data, imports, exports, resources, relocation information, and other runtime metadata. A simplified PE layout looks like this:

image

Both the Data Directories and PE sections contain multiple entries. The exact section layout can differ from one binary to another. For example, a binary compiled with Visual Studio will not always have the same section layout as a packed binary, a manually modified binary, a driver, a .NET executable, or a binary produced by another compiler. The same idea applies to Data Directories; some images may have an import directory but no export directory. Others may have a resource directory, relocation directory, TLS directory, exception directory, or CLR header depending on how the binary was built and what features it uses.

This module will introduce the important Data Directory entries and common PE sections, explain what they represent, and show why they matter when analyzing or parsing PE files. Before covering them, however, we need to examine the PE header structures themselves, as shown in the following sections.

DOS Header

The first structure in a PE file is the DOS header, represented by the IMAGE_DOS_HEADER structure. Unfortunately, at the time of writing this module, this structure does not appear to have a dedicated Microsoft documentation page, but it is defined in the winnt.h header file.

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // Offset to the NT header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;