DEV Community

loading...

Dissecting the PE Header [Windows PE Internals]

wireless90
A Software Engineer trying to venture out to the Security world... Documenting my learning journey.
Updated on ・4 min read

Previous Windows PE Internals Writeups

  1. Creating a Windows Project in Visual Studio
  2. Getting a Handle to a Dynamically Linked Library
  3. Validating the MZ Signature
  4. Validating the PE Signature

Previously

In the previous article, we learnt about how do we validate the PE Signature, also commonly referred to as the File Signature, which tells us that the file is very likely, a Portable Executable (PE) file.

This signature is PE\0\0 (the letters "P" and "E" followed by two null bytes).

We have also seen how to navigate our way from the IMAGE_DOS_HEADER into the IMAGE_NT_HEADER

We also saw that the IMAGE_NT_HEADER comprises of 3 parts.

  • PE Signature
  • PE Header
  • Optional Header

We will be dissecting the PE Header in this article.

Let's Begin

Following the PE Signature, we have the PE Header.

image

As the PE Header is one of the subsection of the IMAGE_NT_HEADER, we can easily retrieve as such.

PIMAGE_FILE_HEADER imageFileHeader = &imageNtHeaders->FileHeader;
Enter fullscreen mode Exit fullscreen mode

Taking a deeper look into the PE Header, we have the following sections.

image

In visual studio, we can see the matching fields as well.

image

Let's write some code to visualize these information

wsprintfA(c + strlen(c), "Number of Symbols: %d\n", imageFileHeader->NumberOfSymbols);
wsprintfA(c + strlen(c), "Pointer to Symbol Table: 0x%02X\n", imageFileHeader->PointerToSymbolTable);
wsprintfA(c + strlen(c), "Number of Sections: %hd\n", imageFileHeader->NumberOfSections);
wsprintfA(c + strlen(c), "TimeDateStamp: %d\n", imageFileHeader->TimeDateStamp);
wsprintfA(c + strlen(c), "Size of Optional Header: %hd\n", imageFileHeader->SizeOfOptionalHeader);
wsprintfA(c + strlen(c), "Machine: 0x%02X\n", imageFileHeader->Machine);
wsprintfA(c + strlen(c), "Characteristix: 0x%02X\n", imageFileHeader->Characteristics);

MessageBoxA(0, c, "PE Header", MB_OK | MB_ICONINFORMATION);
Enter fullscreen mode Exit fullscreen mode

And the result is,

image

Number of Symbols

This is deprecated and should contain 0.

Pointer to symbol table

This is also deprecated and should contain 0.

Number of sections

Sections will be covered in another article. This field basically stores the number of sections.

TimeDateStamp

The number of seconds that has passed from epoch since the file creation.

Size of Optional Headers

The Optional Header comes after the PE Header. We will discuss it in other articles.

Machine

The number that identifies the type of target machine that this executable was compiled for.

Looking into winnt.h,

#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
// omitted
#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
Enter fullscreen mode Exit fullscreen mode

Visual studio always runs in a 32bit emulator while debugging even though I am using a 64 bit machine. Thus it shows as 0x014c which is a 32 bit executable.

Characteristics

We can use tools like CFF Explorer to understand this.
image

As expected, all of these macros can be found in winnt.h.

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved external references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.
Enter fullscreen mode Exit fullscreen mode

The overall code would look like,

#include <Windows.h>

int  WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nCmdShow
)
{
    HMODULE peBase = GetModuleHandleA("user32.dll");

    if (peBase == NULL)
    {
        MessageBoxA(0, "Can't load user32.dll", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_DOS_HEADER imageDosHeader = (PIMAGE_DOS_HEADER)peBase;

    if (imageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        MessageBoxA(0, "user32.dll has the wrong Image Dos Signature!", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_NT_HEADERS imageNtHeaders = (PIMAGE_NT_HEADERS)((unsigned char*)imageDosHeader + imageDosHeader->e_lfanew);

    if (imageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
    {
        MessageBoxA(0, "user32.dll has the wrong PE Signature!", "Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    PIMAGE_FILE_HEADER imageFileHeader = &imageNtHeaders->FileHeader;

    char buffer[1024 * 20] = { 0 };

    wsprintfA(buffer + strlen(buffer), "Number of Symbols: %d\n", imageFileHeader->NumberOfSymbols);
    wsprintfA(buffer + strlen(buffer), "Pointer to Symbol Table: 0x%02X\n", imageFileHeader->PointerToSymbolTable);
    wsprintfA(buffer + strlen(buffer), "Number of Sections: %hd\n", imageFileHeader->NumberOfSections);
    wsprintfA(buffer + strlen(buffer), "TimeDateStamp: %d\n", imageFileHeader->TimeDateStamp);
    wsprintfA(buffer + strlen(buffer), "Size of Optional Header: %hd\n", imageFileHeader->SizeOfOptionalHeader);
    wsprintfA(buffer + strlen(buffer), "Machine: 0x%02X\n", imageFileHeader->Machine);
    wsprintfA(buffer + strlen(buffer), "Characteristix: 0x%02X\n", imageFileHeader->Characteristics);

    MessageBoxA(0, buffer, "PE Header", MB_OK | MB_ICONINFORMATION);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

In this article, we have understood the meaning behind each field in the PE Header.

Next

Exploring the Export Table

Discussion (0)