The old useless DOS header of Windows PE
Published by Alfonso Caruso at July 10, 2022 - 1053 wordsWhen we open native windows executables in a hex-editor we can notice that almost all of them contains strange “This program cannot be run in DOS mode” text at the beginning of the file. The original purpose on this text and surrounding it small MS-DOS program, called MS-DOS stub is to print message to the user and then exit if the .exe file is run from under MS-DOS.
All values in both MS-DOS file header and in PE headers are stored using little-endian convention. This means that a four byte integer e.g. 0x11223344
will be represented on disk by bytes 0x44 0x33 0x22 0x11
(least significant byte first). Of course this applies only to multi-byte types supported by CPU (short, int, long, double and float). Also because characters in ASCII strings are represented by single bytes they are not affected by endianness. For example string “foo” is represented on disk as 0x66
(f) 0x6f
(o) 0x6f
(o) in both little-endian and bit-endian conventions.
PE file format
Portable Executable (PE) file format is a file format for executable / dll files introduced in Windows NT. It’s based on COFF (Common Object File Format) specification. To remain compatible with previous versions of the MS-DOS and Windows, the PE file format retains the old MZ header from MS-DOS.
Much of the definition of individual file components comes from WINNT.H
file included in the Windows SDK. In it you will find structure type definitions for each of the file headers and data directories used to represent various components in the file. In other places in the file, WINNT.H
lacks sufficient definition of the file structure.
The format consists of an MS-DOS MZ header, followed by a real-mode stub program, the PE file signature, the PE file header, the PE optional header, all of the section headers, and finally, all of the section bodies. The optional header ends with an array of data directory entries that are relative virtual addresses to data directories contained within section bodies. Each data directory indicates how a specific section body’s data is structured.
The PE file format has eleven predefined sections, as is common to applications for Windows NT, but each application can define its own unique sections for code and data. The .debug predefined section also has the capability of being stripped from the file into a separate debug file. If so, a special debug header is used to parse the debug file, and a flag is specified in the PE file header to indicate that the debug data has been stripped.
MS-DOS/Real-Mode Header
As mentioned above, the first component in the PE file format is the MS-DOS header. The MS-DOS header is not new for the PE file format. It is the same MS-DOS header that has been around since version 2 of the MS-DOS operating system.
The main reason for keeping the same structure intact at the beginning of the PE file format is so that, when you attempt to load a file created under Windows version 3.1 or earlier, or MS DOS version 2.0 or later, the operating system can read the file and understand that it is not compatible. In other words, when you attempt to run a Windows NT executable on MS-DOS version 6.0, you get this message: “This program cannot be run in DOS mode.” If the MS-DOS header was not included as the first part of the PE file format, the operating system would simply fail the attempt to load the file and offer something completely useless, such as: “The name specified is not recognized as an internal or external command, operable program or batch file.”
A structure representing its content is described below:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
USHORT e_magic; // Magic number
USHORT e_cblp; // Bytes on last page of file
USHORT e_cp; // Pages in file
USHORT e_crlc; // Relocations
USHORT e_cparhdr; // Size of header in paragraphs
USHORT e_minalloc; // Minimum extra paragraphs needed
USHORT e_maxalloc; // Maximum extra paragraphs needed
USHORT e_ss; // Initial (relative) SS value
USHORT e_sp; // Initial SP value
USHORT e_csum; // Checksum
USHORT e_ip; // Initial IP value
USHORT e_cs; // Initial (relative) CS value
USHORT e_lfarlc; // File address of relocation table
USHORT e_ovno; // Overlay number
USHORT e_res[4]; // Reserved words
USHORT e_oemid; // OEM identifier (for e_oeminfo)
USHORT e_oeminfo; // OEM information; e_oemid specific
USHORT e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
The first field, e_magic
, is the so-called magic number. This field is used to identify an MS-DOS-compatible file type. All MS-DOS-compatible executable files set this value to 0x5A4D
, which represents the ASCII characters MZ
. MS-DOS headers are sometimes referred to as MZ
headers for this reason. Many other fields are important to MS-DOS operating systems, but for Windows NT, there is really one more important field in this structure. The final field, e_lfanew
, is a 4-byte offset into the file where the PE file header is located. It is necessary to use this offset to locate the PE header in the file. For PE files in Windows NT, the PE file header occurs soon after the MS-DOS header with only the real-mode stub program between them.
The real-mode stub program is an actual program run by MS-DOS when the executable is loaded. For an actual MS-DOS executable image file, the application begins executing here. For successive operating systems, including Windows, OS/2®, and Windows NT, an MS-DOS stub program is placed here that runs instead of the actual application. The programs typically do no more than output a line of text, such as: “This program requires Microsoft Windows v3.1 or greater.” Of course, whoever creates the application is able to place any stub they like here, meaning you may often see such things as: “You can’t run a Windows NT application on OS/2, it’s simply not possible.”
When building an application for Windows version 3.1, the linker links a default stub program called WINSTUB.EXE
into your executable.
You can override the default linker behavior by substituting your own valid MS-DOS-based program in place of WINSTUB and indicating this to the linker with the STUB module definition statement. Applications developed for Windows NT can do the same thing by using the -STUB: linker option when linking the executable file.