EXE File Documentation


Summary

An EXE (Windows Executable) file is a program that Windows loads and runs when you double-click it, so there is nothing to “open” in a viewer. Most are in the Portable Executable (PE) format and carry the MIME type application/vnd.microsoft.portable-executable. Only run a .exe from a source you trust: it can execute any code on your PC, which makes it the most common way Windows malware spreads.

Technical details

FeatureValue
Full nameWindows Executable (Portable Executable)
File extension.exe
MIME typeapplication/vnd.microsoft.portable-executable
Legacy MIME typeapplication/x-msdownload
Format typeBinary executable (PE; legacy 16-bit MZ/DOS)
DeveloperMicrosoft
Introduced1985 (MS-DOS MZ); PE format since Windows NT 3.1 (1993)
Operating systemWindows (and MS-DOS)
Open standardNo — Microsoft-specified, publicly documented
SpecificationMicrosoft PE Format (learn.microsoft.com/windows/win32/debug/pe-format)
Byte orderLittle-endian
Architecture supportx86, x64, ARM64, IA-64 (legacy)
Magic number (hex)4D 5A (MZ) at offset 0; 50 45 00 00 (PE\0\0) at the PE header
Header layoutMZ/DOS header → DOS stub → PE signature + COFF header → optional header → section headers
Common sections.text (code), .data/.rdata, .rsrc (resources), .idata/.edata (imports/exports)
Resource sectionIcons, version info, manifest, dialogs, strings (.rsrc)
DependenciesMay import functions from DLL files at load time
Digital signatureOptional Authenticode certificate identifying the publisher
Malware riskHigh — the primary malware delivery format on Windows
Related extensions.dll, .msi, .com, .scr, .sys, .app
File signature (magic bytes)
4D 5A … 50 45 00 00

Offset 0 holds 4D 5A, the ASCII letters MZ (the initials of Mark Zbikowski, who designed the DOS header). Every EXE begins this way. On a modern Windows program a 4-byte pointer at offset 0x3C gives the location of the PE header, where the signature 50 45 00 00 (PE\0\0) appears. So MZ alone marks any DOS-era or Windows executable, while the PE signature confirms a 32- or 64-bit Windows binary. Self-extracting archives (a ZIP, 7z, or CAB with a setup stub) also start with MZ.

What is an EXE file?

EXE is the file extension for an executable program on Windows and MS-DOS: a file the operating system loads into memory and runs as native machine code. There is nothing to view inside an EXE, only code and data laid out for a loader. Almost every program you install on Windows arrives as one, from a game to a setup wizard like setup.exe.

The format has two generations. Sixteen-bit DOS programs use the MZ layout that shipped with MS-DOS in 1985, named after Microsoft engineer Mark Zbikowski whose initials are the first two bytes. Every modern Windows program uses the Portable Executable (PE) format, introduced with Windows NT 3.1 in 1993 and documented in Microsoft’s PE Format specification. PE is a wrapper around the older COFF object format, and the same container holds both an EXE and a DLL; the difference is a single flag, not a different file structure. The rest of this article walks the PE layout field by field, from the first two bytes to the entry point the CPU finally jumps to.

The DOS header and e_lfanew

A PE file opens with a 64-byte IMAGE_DOS_HEADER. Its first field, e_magic, holds 0x5A4D, which is the little-endian encoding of the ASCII bytes 4D 5A (“MZ”) at offset 0. This is the reason every EXE and DLL begins with “MZ”: the byte order stores the low byte first, so the value 0x5A4D lands on disk as 4D 5A. The header carries a dozen legacy DOS fields (initial stack pointer, relocation count, header size in paragraphs) that a modern loader ignores.

The field that matters is the last one, e_lfanew, a 4-byte offset at position 0x3C. It points to where the real PE header begins, anywhere later in the file. Between the DOS header and that offset sits the DOS stub, a tiny 16-bit program the linker embeds so that running the EXE under real MS-DOS prints “This program cannot be run in DOS mode” and exits cleanly instead of executing garbage. A Windows loader never runs the stub; it reads e_lfanew, seeks straight to that offset, and expects to find the PE signature there.

Offset  Structure            Contents
0x00    IMAGE_DOS_HEADER     e_magic = 0x5A4D ("MZ"); e_lfanew at 0x3C
0x40    DOS stub             16-bit code: "This program cannot be run in DOS mode"
<e_lfanew>
        PE signature         "PE\0\0"  (50 45 00 00)
        IMAGE_FILE_HEADER    Machine, NumberOfSections, TimeDateStamp,
                             SizeOfOptionalHeader, Characteristics    (20 bytes)
        IMAGE_OPTIONAL_HEADER Magic (0x10B / 0x20B), AddressOfEntryPoint,
                             ImageBase, Section/FileAlignment, Subsystem,
                             DataDirectory[16]
        Section table        IMAGE_SECTION_HEADER x NumberOfSections   (40 bytes each)
---- sections (file order) ----
   .text     executable machine code            (R-X)
   .rdata    read-only data, imports, IAT       (R--)
   .data     initialized read/write data        (RW-)
   .rsrc     resources: icon, version, manifest (R--)
   .reloc    base relocations                   (R--)
   [attribute certificate]  optional Authenticode signature, appended after sections

The PE signature and COFF File Header

At e_lfanew the loader reads a 4-byte signature, 50 45 00 00, which is “PE” followed by two null bytes. “MZ” alone marks any DOS-era or Windows binary; the “PE\0\0” signature confirms a 32-bit or 64-bit Windows image. Note that self-extracting archives still begin with “MZ” even though their payload is a ZIP or CAB, so the magic bytes identify the wrapper, not the contents.

Immediately after the signature comes the 20-byte IMAGE_FILE_HEADER, the COFF File Header. Its fields are compact and load-bearing:

  • Machine (2 bytes): the target CPU. 0x014C is i386 (x86), 0x8664 is x64 (AMD64), and 0xAA64 is ARM64. The loader refuses an image whose Machine does not match the host architecture, though 64-bit Windows runs x86 images through the WoW64 subsystem.
  • NumberOfSections (2 bytes): how many IMAGE_SECTION_HEADER entries follow the optional header. This sets the size of the section table.
  • TimeDateStamp (4 bytes): a Unix epoch timestamp for when the linker produced the file. Reproducible builds often zero or hash this field, so it is unreliable as a true build date.
  • SizeOfOptionalHeader (2 bytes): the byte length of the optional header that follows. The loader uses it to find where the section table starts, since the optional header’s size differs between PE32 and PE32+.
  • Characteristics (2 bytes): a bit field. 0x0002 (IMAGE_FILE_EXECUTABLE_IMAGE) marks a runnable image; 0x2000 (IMAGE_FILE_DLL) marks a library. This single flag is what separates an EXE from a DLL at the header level.

The Optional Header and Data Directory

Despite the name, the IMAGE_OPTIONAL_HEADER is mandatory for an image; “optional” is COFF terminology inherited from object files, where it can be absent. Its first two bytes are a Magic value that decides the whole layout: 0x010B means PE32 (32-bit), and 0x020B means PE32+ (64-bit). PE32+ is not a different format, it is the same header with several address fields widened from 4 to 8 bytes.

The key fields are relative virtual addresses and load-time parameters:

  • AddressOfEntryPoint: the RVA (offset from the image base) of the first instruction to run. For an EXE this points into .text, usually at the C runtime startup code that later calls main or WinMain. A value of 0 is legal only for a DLL with no entry point.
  • ImageBase: the preferred virtual address to map the image. Historically 0x00400000 for 32-bit EXEs and 0x0000000140000000 for 64-bit. In PE32+ this field is 8 bytes wide.
  • SectionAlignment and FileAlignment: sections are aligned to SectionAlignment in memory (typically 0x1000, one page) and to FileAlignment on disk (typically 0x200). This mismatch is why a section’s RVA and its file offset differ.
  • Subsystem: 2 = WINDOWS_GUI (no console window), 3 = WINDOWS_CUI (a console application). The loader reads this to decide whether to allocate a console.
  • DllCharacteristics: flags including ASLR (DYNAMIC_BASE, 0x0040), DEP (NX_COMPAT, 0x0100), and control-flow guard.

The header ends with the Data Directory, an array of 16 IMAGE_DATA_DIRECTORY entries. Each is a pair of (RVA, Size) pointing at a table elsewhere in the image. This array is how the loader finds the imports, exports, resources, relocations, and signature without scanning the sections. The entries that carry the most weight:

IndexDirectoryWhat it points to
0Export TableFunctions this image exposes by name/ordinal (central to a DLL).
1Import TableDLLs and functions the image needs, resolved at load time.
2Resource TableRoot of the .rsrc tree: icon, version info, manifest, dialogs.
3Exception TableFunction tables for structured exception handling (x64/ARM64 unwind info).
4Certificate TableAuthenticode signature (an attribute certificate appended to the file).
5Base Relocation TableFix-ups applied when the image cannot load at its ImageBase (.reloc).
9TLS TableThread-local storage callbacks and template data.
12Import Address TableThe IAT: patched pointers to imported functions.

The section table and RVAs

After the optional header sits the section table, an array of 40-byte IMAGE_SECTION_HEADER structures, one per section named in NumberOfSections. Each entry carries an 8-byte name (.text, .rdata and so on), a VirtualSize, a VirtualAddress (the section’s RVA once mapped), a SizeOfRawData, a PointerToRawData (its byte offset in the file), and a Characteristics flag word.

The two offset fields are the crux of PE layout. VirtualAddress is where the section lives in memory relative to ImageBase, aligned to SectionAlignment. PointerToRawData is where its bytes sit in the file, aligned to FileAlignment. Because the two alignments differ, a data structure’s RVA is not its file offset; converting between them means finding which section the RVA falls in, then computing fileOffset = RVA - VirtualAddress + PointerToRawData. Almost every address inside a PE (entry point, data directory entries, import thunks) is an RVA, so this translation is the single most common operation when parsing the format.

The Characteristics flags set the memory protection each section receives:

SectionHoldsProtection
.textExecutable machine codeRead + Execute (CNT_CODE | MEM_EXECUTE | MEM_READ)
.rdataRead-only data, import descriptors, the IAT, debug infoRead only
.dataInitialized global/static read-write dataRead + Write
.rsrcResource tree: icon, version, application manifestRead only
.relocBase relocation blocksRead only, discardable

The W^X discipline (a page is writable or executable, never both) is enforced through these flags together with the DEP/NX policy. The .rsrc section is why a tool can pull an icon or the version string out of an EXE without executing it: those resources are ordinary read-only data, indexed by the resource directory.

How the loader maps the image and relocates it

When you launch an EXE, the Windows image loader creates the process address space and maps the file at ImageBase. It walks the section table and copies each section to ImageBase + VirtualAddress, applying the protection from Characteristics. Headers and sections are page-aligned in memory even though they were tightly packed on disk, which is the visible effect of SectionAlignment being larger than FileAlignment.

If the preferred ImageBase is already occupied, or ASLR moves the image, the loader cannot use the addresses baked into the code. It reads the base relocation table from the .reloc section, a series of blocks each covering a 4 KB page, and adds the difference between the actual load address and ImageBase to every absolute address the table lists. Position-independent code needs no relocations, but Windows images historically embed absolute addresses, so .reloc is what makes ASLR possible. After relocation the loader has a correctly addressed image but no resolved imports yet.

The Import Address Table

The import directory (data directory index 1) is an array of IMAGE_IMPORT_DESCRIPTOR structures, one per DLL the program depends on, such as kernel32.dll or user32.dll. Each descriptor names the DLL and points at two parallel arrays of thunks: the Import Name Table (the lookup table, which stays as names/ordinals) and the Import Address Table, or IAT (data directory index 12). Before load, both arrays hold the same values; the IAT holds either a function name hint or an ordinal.

At load time the loader maps each required DLL, resolves every imported function to its actual address in that DLL, and overwrites the corresponding IAT slot with that address. The compiled code calls imports indirectly through the IAT, for example call [__imp_MessageBoxW], so once the loader patches the slot every call reaches the right function. This indirection is why a missing or wrong-version DLL fails the program at start-up with a “DLL not found” or side-by-side error rather than crashing mid-run: import resolution happens before a single instruction of the program executes. Only after the image is mapped, relocated, and its IAT is bound does the loader transfer control to AddressOfEntryPoint.

PE32 versus PE32+: 32-bit and 64-bit images

The optional header magic decides the bitness. PE32 (0x010B) is a 32-bit image with a 4-byte ImageBase and 32-bit thunk entries; PE32+ (0x020B) is 64-bit, widening ImageBase, SizeOfStackReserve, and the IAT thunks to 8 bytes. PE32+ also drops the BaseOfData field that PE32 carries. The COFF Machine field agrees with the magic in a valid image: 0x014C pairs with PE32, 0x8664 and 0xAA64 with PE32+.

A 64-bit Windows runs both, executing 32-bit images through WoW64, which is why many programs still ship a 32-bit build; a 32-bit Windows cannot load a PE32+ image at all and reports “not a valid Win32 application”. The same PE container also underlies other Microsoft binaries: .dll libraries, .sys drivers, and the OS files inside an MSI installer are all PE images distinguished by their subsystem and Characteristics flags.

Security: entry-point execution, Authenticode, and packers

The security model of an EXE follows directly from its structure. The moment the loader jumps to AddressOfEntryPoint, native code runs with the launching user’s full privileges: it can create files, write to the registry, spawn processes, and open network sockets with no further gate. That is exactly why EXE is the primary malware vector on Windows. Trojans and ransomware are typically EXE files, or scripts that drop one, because a single entry point gives an attacker arbitrary code execution as the victim.

Windows layers several checks around that entry point. Authenticode signing places an X.509 certificate and a signed hash in the Certificate Table (data directory index 4), appended after the sections as an attribute certificate so it does not disturb any RVA. Verifying the signature re-hashes the image (excluding the certificate and the checksum field) and confirms both the publisher identity and that no byte changed since signing. SmartScreen checks a downloaded EXE against a reputation service and warns on unrecognized files; User Account Control (UAC) prompts before a process is granted the administrator token needed for system-wide changes. None of these inspect what the code does, only its origin and integrity, so an unsigned EXE is not automatically malicious and a signed one is not automatically safe, only accountable.

Analysis is complicated by packers and obfuscators. A packed EXE stores its real .text compressed or encrypted; the visible entry point is a small stub that unpacks the payload into memory and then jumps to the original entry point. This defeats naive static reading of the sections and is used both by legitimate protectors and by malware to hide from signature scanners, which is why suspicious binaries are examined dynamically or in a sandbox. Compiled EXE code cannot be translated to another operating system either: a PE image needs the Windows loader, so on Linux and macOS projects such as Wine and CrossOver reimplement that loader and the Windows API rather than converting the file. Related executable containers on other platforms are entirely separate formats, from the Linux ELF to the Android APK package, and none share the PE structure described here.

References