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
| Feature | Value |
|---|---|
| Full name | Windows Executable (Portable Executable) |
| File extension | .exe |
| MIME type | application/vnd.microsoft.portable-executable |
| Legacy MIME type | application/x-msdownload |
| Format type | Binary executable (PE; legacy 16-bit MZ/DOS) |
| Developer | Microsoft |
| Introduced | 1985 (MS-DOS MZ); PE format since Windows NT 3.1 (1993) |
| Operating system | Windows (and MS-DOS) |
| Open standard | No — Microsoft-specified, publicly documented |
| Specification | Microsoft PE Format (learn.microsoft.com/windows/win32/debug/pe-format) |
| Byte order | Little-endian |
| Architecture support | x86, 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 layout | MZ/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 section | Icons, version info, manifest, dialogs, strings (.rsrc) |
| Dependencies | May import functions from DLL files at load time |
| Digital signature | Optional Authenticode certificate identifying the publisher |
| Malware risk | High — the primary malware delivery format on Windows |
| Related extensions | .dll, .msi, .com, .scr, .sys, .app |
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.
0x014Cis i386 (x86),0x8664is x64 (AMD64), and0xAA64is 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_HEADERentries 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 callsmainorWinMain. A value of 0 is legal only for a DLL with no entry point. - ImageBase: the preferred virtual address to map the image. Historically
0x00400000for 32-bit EXEs and0x0000000140000000for 64-bit. In PE32+ this field is 8 bytes wide. - SectionAlignment and FileAlignment: sections are aligned to
SectionAlignmentin memory (typically0x1000, one page) and toFileAlignmenton disk (typically0x200). 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:
| Index | Directory | What it points to |
|---|---|---|
| 0 | Export Table | Functions this image exposes by name/ordinal (central to a DLL). |
| 1 | Import Table | DLLs and functions the image needs, resolved at load time. |
| 2 | Resource Table | Root of the .rsrc tree: icon, version info, manifest, dialogs. |
| 3 | Exception Table | Function tables for structured exception handling (x64/ARM64 unwind info). |
| 4 | Certificate Table | Authenticode signature (an attribute certificate appended to the file). |
| 5 | Base Relocation Table | Fix-ups applied when the image cannot load at its ImageBase (.reloc). |
| 9 | TLS Table | Thread-local storage callbacks and template data. |
| 12 | Import Address Table | The 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:
| Section | Holds | Protection |
|---|---|---|
.text | Executable machine code | Read + Execute (CNT_CODE | MEM_EXECUTE | MEM_READ) |
.rdata | Read-only data, import descriptors, the IAT, debug info | Read only |
.data | Initialized global/static read-write data | Read + Write |
.rsrc | Resource tree: icon, version, application manifest | Read only |
.reloc | Base relocation blocks | Read 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
- Microsoft — PE Format specification
- Microsoft — Defender SmartScreen & Authenticode code signing
- WineHQ — run Windows applications on macOS and Linux
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.