$Id: pe.txt,v 1.7 1998/07/29 17:55:13 LUEVELSMEYER Exp $
The PE file format==================
Purpose-------
The PE ("portable executable") file format is the format of executablebinaries (DLLs and programs) for MS windows NT, windows 95 andwin32s; in windows NT, the drivers are in this format, too.It can also be used for object files and libraries.
The format is designed by Microsoft, apparently based on a goodknowledge of COFF, the "common object file format" used for object filesand executables on several UNIXes and on VMS.
The win32 SDK includes a header file <winnt.h> containing #defines andtypedefs for the PE-format. I will mention the struct-member-names and#defines as we go.
You may also find the DLL "imagehelp.dll" to be helpful. It is part ofwindows NT, but documentation is rare. Some of its functions aredescribed in the "Developer Network".
General Layout--------------
At the start of a PE file we find an MS-DOS executable ("stub"); thismakes any PE file a valid MS-DOS executable.
After the DOS-stub there is a 32-bit-signature with the magic number0x00004550 (IMAGE_NT_SIGNATURE).
Then there is a file header (in the COFF-format) that tells on whichmachine the binary is supposed to run, how many sections are in it, thetime it was linked, whether it is an executable or a DLL and so on. (Thedifference between executable and DLL in this context is: a DLL can notbe started but only be used by another binary, and a binary cannot linkto an executable).
After that, we have an optional header (it is always there but stillcalled "optional" - COFF uses an "optional header" for libraries but notfor objects, that's why it is called "optional"). This tells us moreabout how the binary should be loaded: The starting address, the amountof stack to reserve, the size of the data segment etc..
An interesting part of the optional header is the trailing array of'data directories'; these directories contain pointers to data in the'sections'. If, for example, the binary has an export directory, youwill find a pointer to that directory in the array memberIMAGE_DIRECTORY_ENTRY_EXPORT, and it will point into one of thesections.
Following the headers we find the 'sections', introduced by the 'sectionheaders'. Essentially, the sections' contents is what you really need toexecute a program, and all the header and directory stuff is just thereto help you find it.Each section has some flags about alignment, what kind of data itcontains ("initialized data" and so on), whether it can be shared etc.,and the data itself. Most, but not all, sections contain one or moredirectories referenced through the entries of the optional header's"data directory" array, like the directory of exported functions or thedirectory of base relocations. Directoryless types of contents are, forexample, "executable code" or "initialized data".
+-------------------+ | DOS-stub | +-------------------+ | file-header | +-------------------+ | optional header | |- - - - - - - - - -| | | | data directories | | | +-------------------+ | | | section headers | | | +-------------------+ | | | section 1 | | | +-------------------+ | | | section 2 | | | +-------------------+ | | | ... | | | +-------------------+ | | | section n | | | +-------------------+
DOS-stub and Signature----------------------
The concept of a DOS-stub is well-known from the 16-bit-windows-executables (which were in the "NE" format). The stub is used forOS/2-executables, self-extracting archives and other applications, too.For PE-files, almost always this MS-DOS-executable consists of about 100bytes that output an error message such as "this program needs windowsNT".You recognize a DOS-stub by the validating the DOS-header, being astruct IMAGE_DOS_HEADER. The first 2 bytes should be the sequence "MZ"(there is a #define IMAGE_DOS_SIGNATURE for this WORD).You distinguish a PE binary from other stubbed binaries by the trailingsignature, which you find at the offset given by the header member'e_lfanew' (which is 32 bits long beginning at byte offset 60). For OS/2and windows binaries, the signature is a 16-bit-word; for PE files, itis a 32-bit-longword with the value IMAGE_NT_SIGNATURE #defined to be0x00004550.
File Header-----------
To get to the IMAGE_FILE_HEADER, validate the "MZ" of the DOS-header(1st 2 bytes), then find the 'e_lfanew' member of the DOS-stub's headerand skip that many bytes from the beginning of the file. Verify thesignature you will find there. The file header, a structIMAGE_FILE_HEADER, begins immediatly after it; the members are describedtop to bottom.
The first member is the 'Machine', a 16-bit-value indicating the systemthe binary is intended to run on. Known legal values are
IMAGE_FILE_MACHINE_I386 (0x14c) for Intel 80386 processor or better
0x014d for Intel 80486 processor or better
0x014e for Intel Pentium processor or better
0x0160 for R3000 (MIPS) processor, big endian
IMAGE_FILE_MACHINE_R3000 (0x162) for R3000 (MIPS) processor, little endian
IMAGE_FILE_MACHINE_R4000 (0x166) for R4000 (MIPS) processor, little endian
IMAGE_FILE_MACHINE_R10000 (0x168) for R10000 (MIPS) processor, little endian
IMAGE_FILE_MACHINE_ALPHA (0x184) for DEC Alpha AXP processor
IMAGE_FILE_MACHINE_POWERPC (0x1F0) for IBM Power PC, little endian
Then we have the 'NumberOfSections', a 16-bit-value. It is the number ofsections that follow the headers. We will discuss the sections later.
Next is a timestamp 'TimeDateStamp' (32 bit), giving the time the filewas created. You can distinguish several versions of the same file bythis value, even if the "official" version number was not altered. (Theformat of the timestamp is not documented except that it should besomewhat unique among versions of the same file, but apparently it is'seconds since January 1 1970 00:00:00' in UTC - the format used by mostC compilers for the time_t.)This timestamp is used for the binding of import directories, which willbe discussed later.Warning: some compilers tend to set this timestamp to absurd values.
The members 'PointerToSymbolTable' and 'NumberOfSymbols' (both 32 bit)are used for debugging information. I don't know how to decipher them,and I've found the pointer to be always 0.
'SizeOfOptionalHeader' (16 bit) is simply sizeof(IMAGE_OPTIONAL_HEADER).You can use it to validate the correctness of the PE file's structure.
'Characteristics' is 16 bits and consists of a collection of flags, mostof them being valid only for object files and libraries:
Bit 0 (IMAGE_FILE_RELOCS_STRIPPED) is set if there is no relocation information in the file. This refers to relocation information per section in the sections themselves; it is not used for executables, which have relocation information in the 'base relocation' directory described below.
Bit 1 (IMAGE_FILE_EXECUTABLE_IMAGE) is set if the file is executable (i.e. it is not an object file or a library).
Bit 2 (IMAGE_FILE_LINE_NUMS_STRIPPED) is set if the line number information is stripped; this is not used for executable files.
Bit 3 (IMAGE_FILE_LOCAL_SYMS_STRIPPED) is set if there is no information about local symbols in the file (this is not used for executable files).
Bit 4 (IMAGE_FILE_AGGRESIVE_WS_TRIM) is set if the operating system is supposed to trim the working set of the running process (the amount of RAM the process uses) aggressivly by paging it out. This should be set if it is a demon-like application that waits most of the time and only wakes up once a day, or the like.
Bits 7 (IMAGE_FILE_BYTES_REVERSED_LO) and 15 (IMAGE_FILE_BYTES_REVERSED_HI) are set if the endianess of the file is not what the machine would expect, so it must swap bytes before reading. This is unreliable for executable files (the OS expects executables to be correctly byte-ordered).
Bit 8 (IMAGE_FILE_32BIT_MACHINE) is set if the machine is expected to be a 32 bit machine. This is always set.
Bit 9 (IMAGE_FILE_DEBUG_STRIPPED) is set if there is no debugging information in the file. This is unused for executable files.
Bit 10 (IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) is set if the application may not run from a removable medium such as a floppy or a CD-ROM. In this case, the operating system is advised to copy the file to the swapfile and execute it from there.
Bit 11 (IMAGE_FILE_NET_RUN_FROM_SWAP) is set if the application may not run from the network. In this case, the operating system is advised to copy the file to the swapfile and execute it from there.
Bit 12 (IMAGE_FILE_SYSTEM) is set if the file is a system file such as a driver. This is unused for executable files.
Bit 13 (IMAGE_FILE_DLL) is set if the file is a DLL.
Bit 14 (IMAGE_FILE_UP_SYSTEM_ONLY) is set if the file is not designed to run on multiprocessor systems (that is, it will crash there because it relies in some way on exactly one processor).
Relative Virtual Addresses--------------------------
The PE format makes heavy use of so-called RVAs. An RVA, aka "relativevirtual address", is used to describe a memory address if you don't knowthe base address. It is the value you need to add to the base address toget the linear address.The base address is the address the PE image is loaded to, and may varyfrom one invocation to the next.
Example: suppose an executable file is loaded to address 0x400000 andexecution starts at RVA 0x1560. The effective execution start will thenbe at the address 0x401560. If the executable were loaded to 0x100000,the execution start would be 0x101560.
Things become complicated because the parts of the PE-file (thesections) are not necessarily aligned the same way the loaded image is.For example, the sections of the file are often aligned to512-byte-borders, but the loaded image is perhaps aligned to4096-byte-borders. See 'SectionAlignment' and 'FileAlignment' below.
So to find a piece of information in a PE-file for a specific RVA,you must calculate the offsets as if the file were loaded, but skipaccording to the file-offsets.As an example, suppose you knew the execution starts at RVA 0x1560, andwant to diassemble the code starting there. To find the address in thefile, you will have to find out that sections in RAM are aligned to 4096bytes and the ".code"-section starts at RVA 0x1000 in RAM and is 16384bytes long; then you know that RVA 0x1560 is at offset 0x560 in thatsection. Find out that the sections are aligned to 512-byte-borders inthe file and that ".code" begins at offset 0x800 in the file, and youknow that the code execution start is at byte 0x800+0x560=0xd60 in thefile.
Easy if you know how it works :-)
Optional Header---------------
Immediatly following the file header is the IMAGE_OPTIONAL_HEADER(which, in spite of the name, is always there). It containsinformation about how to treat the PE-file exactly. We'll also have themembers from top to bottom.
The first 16-bit-word is 'Magic' and has, as far as I looked intoPE-files, always the value 0x010b.
The next 2 bytes are the version of the linker ('MajorLinkerVersion' and'MinorLinkerVersion') that produced the file. These values, again, areunreliable and do not always reflect the linker version properly.(Several linkers simply don't set this field.)And, coming to think about it, what good is the version if you have gotno idea *which* linker was used?
The next 3 longwords (32 bit each) are intended to be the size of theexecutable code ('SizeOfCode'), the size of the initialized data('SizeOfInitializedData', the so-called "data segment"), and the size ofthe uninitialized data ('SizeOfUninitializedData', the so-called "bsssegment"). These values are, again, unreliable (e.g. the data segmentmay actually be split into several segments by the compiler or linker),and you get better sizes by inspecting the 'sections' that follow theoptional header.
Next is a 32-bit-value that is a RVA. This RVA is the offset to thecodes's entry point ('AddressOfEntryPoint'). Execution starts here; itis e.g. the address of a DLL's LibMain() or a program's startup code(which will in turn call main()) or a driver's DriverEntry().If you dare to load the image "by hand", you call this address to startthe process after you have done all the fixups and relocations.
The next 2 32-bit-values are the offsets to the executable code('BaseOfCode') and the initialized data ('BaseOfData'), both of themRVAs again, and both of them being of little interest because you getmore reliable information by inspecting the 'sections' that follow theheaders.There is no offset to the uninitialized data because, beinguninitialized, there is little point in providing this data in theimage.
The next entry is a 32-bit-value that is a RVA giving the preferred loadaddress ('ImageBase'). This is the address the file has been relocatedto by the linker; if the binary can in fact be loaded to that address,the loader doesn't need to relocate the file again, which is a big winin loading time.The preferred load address can not be used if another image has alreadybeen loaded to that address (an "address clash", which happens quiteoften if you load several DLLs that are all relocated to the linker'sdefault), or the memory in question has been used for other purposes(stack, malloc(), uninitialized data, whatever). In these cases, theimage must be loaded to some other address and it needs to be relocated(see 'relocation directory' below). This has further consequences if theimage is a DLL, because then the "bound imports" are no longer valid,and fixups have to be made to the binary that uses the DLL - see 'importdirectory' below.
The next 2 32-bit-values are the alignments of the PE-file's sections inRAM ('SectionAlignment', when the image has been loaded) and in the file('FileAlignment'). Usually both values are 32, or FileAlignment is 512and SectionAlignment is 4096. Sections will be discussed later.
The next 2 16-bit-words are the expected operating system version('MajorOperatingSystemVersion' and 'MinorOperatingSystemVersion' [they_do_ like self-documenting names at MS]). This version information isintended to be the operating system's (e.g. NT or Win95) version, asopposed to the subsystem's version (e.g. Win32); it is often notsupplied, or wrong supplied. The loader doesn't use it, apparently.
The next 2 16-bit-words are the binary's version, ('MajorImageVersion' and'MinorImageVersion'). Many linkers don't set this information correctlyand many programmers don't bother to supply it, so it is better to relyon the version-resource if one exists.
The next 2 16-bit-words are the expected subsystem version('MajorSubsystemVersion' and 'MinorSubsystemVersion'). This should bethe Win32 version or the POSIX version, because 16-bit-programs orOS/2-programs won't be in PE-format, obviously.This subsystem version should be supplied correctly, because it *is*checked and used:If the application is a Win32-GUI-application and runs on NT, and thesubsystem version is *not* 4.0, the dialogs won't be 3D-style andcertain other features will also work "old-style" because theapplication expects to run on NT 3.51, which had the program managerinstead of explorer and so on, and NT 4.0 will mimic that behaviour asfaithfully as possible.
Then we have a 'Win32VersionValue' of 32 bits. I don't know what it isgood for. It has been 0 in all the PE files that I inspected.
Next is a 32-bits-value giving the amount of memory the image will need,in bytes ('SizeOfImage'). It is the sum of all sections' lengths. It isa hint to the loader how many pages it will need in order to load the image.
The next thing is a 32-bit-value giving the total length of all headersincluding the data directories and the section headers('SizeOfHeaders'). It is at the same time the offset from the beginningof the file to the first section's raw data.
Then we have got a 32-bit-checksum ('CheckSum'). This checksum is, forcurrent versions of NT, only checked if the image is a NT-driver (thedriver will fail to load if the checksum isn't correct).The algorithm to compute the checksum is property of Microsoft, and theywon't tell you. However, several tools of the Win32 SDK will compute avalid checksum, and the function CheckSumMappedFile() in theimagehelp.dll will do so too.The checksum is supposed to prevent loading of damaged binaries thatwould crash anyway - and a crashing driver would result in a BSOD, soit is better not to load it at all.
Then there is a 16-bit-word 'Subsystem' that tells in which of theNT-subsystems the image runs:
IMAGE_SUBSYSTEM_NATIVE (1) The binary doesn't need a subsystem. This is used for drivers. IMAGE_SUBSYSTEM_WINDOWS_GUI (2) The image is a Win32 graphical binary. (It can still open a console with AllocConsole() but won't get one automatically at startup.) IMAGE_SUBSYSTEM_WINDOWS_CUI (3) The binary is a Win32 console binary. (It will get a console per default at startup.) IMAGE_SUBSYSTEM_OS2_CUI (5) The binary is a OS/2 console binary. (OS/2 binaries will be in OS/2 format, so this value will seldom be used in a PE file.) IMAGE_SUBSYSTEM_POSIX_CUI (7) The binary uses the POSIX console subsystem.
Windows 95 binaries will always use the Win32 subsystem, so the onlylegal values for these binaries are 2 and 3; I don't know if "native"binaries on windows 95 are possible.
The next thing is a 16-bit-value that tells, if the image is a DLL, whento call the DLL's entry point ('DllCharacteristics'). This seems not tobe used; apparently, the DLL is always notified about everything. If bit 0 is set, the DLL is notified about process attachment (i.e. DLL load). If bit 1 is set, the DLL is notified about thread detachments (i.e. thread terminations). If bit 2 is set, the DLL is notified about thread attachments (i.e. thread creations). If bit 3 is set, the DLL is notified about process detachment (i.e. DLL unload).
The next 4 32-bit-values are the size of reserved stack('SizeOfStackReserve'), the size of initially committed stack('SizeOfStackCommit'), the size of the reserved heap('SizeOfHeapReserve') and the size of the committed heap('SizeOfHeapCommit').The 'reserved' amounts are address space (not real RAM) that is reservedfor the specific purpose; at program startup, the 'committed' amount isactually allocated in RAM. The 'committed' value is also the amount bywhich the committed stack or heap grows if necessary.So, as an example, if a program has a reserved heap of 1 MB and acommitted heap of 64 KB, the heap will start out at 64 KB and isguaranteed to be enlargeable up to 1 MB. The heap will grow in64-KB-chunks.The 'heap' in this context is the primary (default) heap.DLLs don't have a stack or heap of their own, so the values are ignoredfor their images.
After these stack- and heap-descriptions, we find 32 bits of'LoaderFlags', which I didn't find a useful description of. I only foundthat you may set bits that automatically invoke a breakpoint or adebugger after loading the image; however, this doesn't seem to work.
Then we find 32 bits of 'NumberOfRvaAndSizes', which is the number ofvalid entries in the directories that follow immediatly. I've found thisvalue to be unreliable; you might wish use the constantIMAGE_NUMBEROF_DIRECTORY_ENTRIES instead, or the lesser of both.
After the 'NumberOfRvaAndSizes' there is an array ofIMAGE_NUMBEROF_DIRECTORY_ENTRIES (16) IMAGE_DATA_DIRECTORYs.Each of these directories describes the location (32 bits RVA called'VirtualAddress') and size (also 32 bit, called 'Size') of a particularpiece of information, which is located in one of the sections thatfollow the directory entries.For example, the security directory is found at the RVA and has the sizethat are given at index 4.The directories that I know the structure of will be discussed later.Defined directory indexes are:
IMAGE_DIRECTORY_ENTRY_EXPORT (0) The directory of exported symbols; mostly used for DLLs. Described below. IMAGE_DIRECTORY_ENTRY_IMPORT (1) The directory of imported symbols; see below. IMAGE_DIRECTORY_ENTRY_RESOURCE (2) Directory of resources. Described below. IMAGE_DIRECTORY_ENTRY_EXCEPTION (3) Exception directory - structure and purpose unknown. IMAGE_DIRECTORY_ENTRY_SECURITY (4) Security directory - structure and purpose unknown. IMAGE_DIRECTORY_ENTRY_BASERELOC (5) Base relocation table - see below. IMAGE_DIRECTORY_ENTRY_DEBUG (6) Debug directory - contents is compiler dependent. Moreover, many compilers stuff the debug information into the code section and don't create a separate section for it. IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7) Description string - some arbitrary copyright note or the like. IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8) Machine Value (MIPS GP) - structure and purpose unknown. IMAGE_DIRECTORY_ENTRY_TLS (9) Thread local storage directory - structure unknown; contains variables that are declared "declspec(thread)". IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10) Load configuration directory - structure and purpose unknown. IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11) Bound import directory - see description of import directory. IMAGE_DIRECTORY_ENTRY_IAT (12) Import Address Table - structure and purpose unknown As an example, if we find at index 7 the 2 longwords 0x12000 and 33, andthe load address is 0x10000, we know that the copyright data is ataddress 0x10000+0x12000 (in whatever section there may be), and thecopyright note is 33 bytes long.If a directory of a particular type is not used in a binary, the Sizeand VirtualAddress are both 0.
Section directories-------------------
The sections consist of two major parts: first, a section description(of type IMAGE_SECTION_HEADER) and then the raw section data. So afterthe data directories we find an array of 'NumberOfSections' sectionheaders.
A section header is actually a very *small* struct. It contains:
An array of IMAGE_SIZEOF_SHORT_NAME (8) bytes that make up the name(ASCII) of the section. If all of the 8 bytes are used there is no 0-terminator for the string! The name is typically something like ".data"or ".text" or ".bss". There need not be a leading '.', the names mayalso be "CODE" or "IAT" or the like.Please note that the names are not at all related to the contents of thesection. A section named ".code" may or may not contain the executablecode; it may just as well contain the import address table; it may alsocontain the code *and* the address table *and* the initialized data.To find information in the sections, you will have to look it up via thedata directories of the optional header. Do not rely on the names, anddo not assume that the section's raw data starts at the beginning of asection.
The next member of the IMAGE_SECTION_HEADER is a 32-bit-union of'PhysicalAddress' and 'VirtualSize'. In an object file, this is theaddress the contents is relocated to; in an executable, it is the size ofthe contents. In fact, the field seems to be unused; There are linkersthat enter the size, and there are linkers that enter the address, andI've also found a linker that enters a 0, and all the executables runlike the gentle wind.
The next member is 'VirtualAddress', a 32-bit-value holding the RVA tothe section's data when it is loaded in RAM.
Then we have got 32 bits of 'SizeOfRawData', which is the size of thesecion's data rounded up to the next multiple of 'FileAlignment'.
Next is 'PointerToRawData' (32 bits), which is incredibly usefullbecause it is the offset from the file's beginning to the section's data.If it is 0, the section's data are not contained in the file and will bearbitrary.
Then we have got 'PointerToRelocations' (32 bits) and'PointerToLinenumbers' (also 32 bits), 'NumberOfRelocations' (16 bits)and 'NumberOfLinenumbers' (also 16 bits). All of these are informationthat's only used for object files. Executables have a special baserelocation directory, and the line number information, if present atall, is usually contained in a special purpose debugging segment orelsewhere.
The last member of a section header is the 32 bits 'Characteristics',which is a bunch of flags describing how the section's memory should betreated:
If bit 5 (IMAGE_SCN_CNT_CODE) is set, the section contains executable code. If bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA) is set, the section contains data that gets a defined value before execution starts. In other words: the section's data in the file is meaningful. If bit 7 (IMAGE_SCN_CNT_UNINITIALIZED_DATA) is set, this section's data is uninitialized data and will be initialized to all-0-bytes before execution starts. This is normally the BSS.
If bit 9 (IMAGE_SCN_LNK_INFO) is set, the section doesn't contain image data but comments, description or other documentation.
If bit 11 (IMAGE_SCN_LNK_REMOVE) is set, the data is part of an object file's section that is supposed to be left out when the executable file is linked.
If bit 12 (IMAGE_SCN_LNK_COMDAT) is set, the section contains "common block data", which are packaged functions of some sort.
If bit 15 (IMAGE_SCN_MEM_FARDATA) is set, we have far data - whatever that means. This bit's meaning is unsure.
If bit 17 (IMAGE_SCN_MEM_PURGEABLE) is set, the section's data is purgeable - but I don't think that this is the same as "discardable", which has a bit of its own, see below. The same bit is apparently used to indicate 16-bit-information as there is also a define IMAGE_SCN_MEM_16BIT for it. This bit's meaning is unsure.
If bit 18 (IMAGE_SCN_MEM_LOCKED) is set, the section should not be moved in memory? This bit's meaning is unsure.
If bit 19 (IMAGE_SCN_MEM_PRELOAD) is set, the section should be paged in before execution starts? This bit's meaning is unsure.
Bits 20 to 23 specify an alignment that I have no information about. There are #defines IMAGE_SCN_ALIGN_16BYTES and the like. The only value I've ever seen used is 0, for the default 16-byte- alignment. I suspect that this is the alignment of objects in a library file or the like.
If bit 24 (IMAGE_SCN_LNK_NRELOC_OVFL) is set, the section contains some extended relocations that I don't know about.
If bit 25 (IMAGE_SCN_MEM_DISCARDABLE) is set, the section's data is not needed after the process has started. This is the case, for example, with the relocation information. I've seen it also for startup routines of drivers and services that are only executed once.
If bit 26 (IMAGE_SCN_MEM_NOT_CACHED) is set, the section's data should not be cached. Don't ask my why not. Does this mean to switch off the 2nd-level-cache?
If bit 27 (IMAGE_SCN_MEM_NOT_PAGED) is set, the section's data should not be paged out. This may be interesting for drivers.
If bit 28 (IMAGE_SCN_MEM_SHARED) is set, the section's data is shared among all running instances of the image. If it is e.g. the initialized data of a DLL, all running instances of the DLL will at any time have the same variable contents. Note that only the first instance's section is initialized. Sections containing code are always shared.
If bit 29 (IMAGE_SCN_MEM_EXECUTE) is set, the process gets 'execute'-access to the section's memory. If bit 30 (IMAGE_SCN_MEM_READ) is set, the process gets 'read'-access to the section's memory. If bit 31 (IMAGE_SCN_MEM_WRITE) is set, the process gets 'write'-access to the section's memory.
After the section headers we find the sections themselves. They are, inthe file, aligned to 'FileAlignment' bytes (that is, after the optionalheader and after each section's data there will be padding bytes). Whenloaded (in RAM), the sections are aligned to 'SectionAlignment' bytes.
As an example, if the optional header ends at file offset 981 and'FileAlignment' is 512, the first section will start at byte 1024. Notethat you can find the sections via the 'PointerToRawData' or the'VirtualAddress', so there is hardly any need to actually fuss aroundwith the alignments.
I will try to make an image of it all: +-------------------+ | DOS-stub | +-------------------+ | file-header | +-------------------+ | optional header | |- - - - - - - - - -| | |----------------+ | data directories | | | | | |(RVAs to direc- |-------------+ | |tories in sections)| | | | |---------+ | | | | | | | +-------------------+ | | | | |-----+ | | | | section headers | | | | | | (RVAs to section |--+ | | | | | borders) | | | | | | +-------------------+<-+ | | | | | | | <-+ | | | section data 1 | | | | | | | <-----+ | +-------------------+<----+ | | | | | section data 2 | | | | <--------------+ +-------------------+There is one section header for each section, and each data directorywill point to one of the sections (several data directories may point tothe same section, and there may be sections without data directorypointing to them).
Sections' raw data------------------
general-------All sections are aligned to 'SectionAlignment' when loaded in RAM, and'FileAlignment' in the file. The sections are described by entries inthe section headers: You find the sections in the file via'PointerToRawData' and in memory via 'VirtualAddress'; the length is in'SizeOfRawData'.
There are several kinds of sections, depending on what's contained inthem. In most cases (but not in all) there will be at least onedata directory in a section, with a pointer to it in the optionalheader's data directory array.
code section------------First, I will mention the code section. The section will have, at least,the bits 'IMAGE_SCN_CNT_CODE', 'IMAGE_SCN_MEM_EXECUTE' and'IMAGE_SCN_MEM_READ' set, and 'AddressOfEntryPoint' will point somewhereinto the section, to the start of the function that the developer wantsto execute first.'BaseOfCode' will normally point to the start of this section, but maypoint to somewhere later in the section if some non-code-bytes areplaced before the code in the section.Normally, there will be nothing but executable code in this section, andthere will be only one code section, but don't rely on this.Typical section names are ".text", ".code", "AUTO" and the like.
data section-------------The next thing we'll discuss is the initialized variables; this sectioncontains initialized static variables (like "static int i = 5;"). It willhave, at least, the bits 'IMAGE_SCN_CNT_INITIALIZED_DATA','IMAGE_SCN_MEM_READ' and 'IMAGE_SCN_MEM_WRITE' set. Some linkers mayplace constant data into a section of their own that doesn't have thewriteable-bit. If part of the data is shareable, or there are otherpeculiarities, there may be more sections with the apropriate section-bits set.The section, or sections, will be in the range 'BaseOfData' up to'BaseOfData'+'SizeOfInitializedData'.Typical section names are '.data', '.idata', 'DATA' and so on.
bss section-----------Then there is the uninitialized data (for static variables like "staticint k;"); this section is quite like the initialized data, but will havea file offset ('PointerToRawData') of 0 to indicate its contents is notstored in the file, and 'IMAGE_SCN_CNT_UNINITIALIZED_DATA' is setinstead of 'IMAGE_SCN_CNT_INITIALIZED_DATA' to indicate that thecontents should be set to 0-bytes at load-time.The length will be 'SizeOfUninitializedData'.Typical names are '.bss', 'BSS' and the like.
These were the section data that are *not* pointed to by datadirectories. Their contents and structure is supplied by the compiler,not by the linker.(The stack-segment and heap-segment are not sections in the binary butcreated by the loader from the stacksize- and heapsize-entries in theoptional header.)
copyright---------To begin with a simple directory-section, let's look at the datadirectory 'IMAGE_DIRECTORY_ENTRY_COPYRIGHT'. The contents is acopyright- or description string in ASCII (not 0-terminated), like"Gonkulator control application, copyright (c) 1848 Hugendubel & Cie".This string is, normally, supplied to the linker with the command line.This string is not needed at runtime and may be discarded. It is notwriteable; in fact, the application doesn't need access at all.So the linker will find out if there is a discardable non-writeablesection already and if not, create one (named '.descr' or the like). Itwill then stuff the string into the section and let thecopyright-directory-pointer point to the string. The'IMAGE_SCN_CNT_INITIALIZED_DATA' bit should be set.
exported symbols----------------The next-simplest thing is the export directory,'IMAGE_DIRECTORY_ENTRY_EXPORT'. This is a directory typically foundin DLLs; it contains the entry points of exported functions (and theaddresses of exported objects etc.). Executables may of course also haveexported symbols but usually they don't.The containing section should be "initialized data" and "readable". Itcan not be "discardable" because the process might call "GetProcAddress()"to find a function's entry point at runtime.The section is normally called '.edata' if it is a separate thing; oftenenough, it is merged into some other section like "initialized data".
The structure of the export table ('IMAGE_EXPORT_DIRECTORY') comprises aheader and the export data, that is: the symbol names, their ordinalsand the offsets to their entry points.
First, we have 32 bits of 'Characteristics' that are unused and normally0. Then there is a 32-bit-'TimeDateStamp', which presumably should givethe time the table was created in the time_t-format; alas, it is notalways valid (some linkers set it to 0). Then we have 2 16-bit-words ofversion-info ('MajorVersion' and 'MinorVersion'), and these, too, areoften enough set to 0.
The next thing is 32 bits of 'Name'; this is an RVA to the DLL name as a0-terminated ASCII string. (The name is necessary in case the DLL file isrenamed - see "binding" at the import directory.)Then, we have got a 32-bit-'Base'. We'll come to that in a moment.
Now we have got sort of a problem, because the next two 32-bit-values arethe number of exported functions ('NumberOfFunctions') and the number ofexported names ('NumberOfNames'). These values are *not* always equal,and weird things happen if you try to decipher the export directory pastthe minimum of both. In *most* export-directories the values are equaland we're fine. It seems to be always safe to decipher up to the minimumof both values.I've got a suspicion that it is somehow possible do export symbolswithout a name (just by ordinal), but I didn't find anything about thistopic.
The next 3 32-bit-values are RVAs to 3 arrays. The arrays run parallel:the array of entry points 'AddressOfFunctions' (given as 32-bit-RVAs),the array of 32-bit-RVAs to symbol names 'AddressOfNames', and the arrayof 16-bit-ordinals 'AddressOfNameOrdinals'.
To get information about the i-th exported symbol, find the exportdirectory, follow the 3 RVAs, skip to index i in the 3 arrays, andyou have got a) the RVA of the function's entry point; b) the RVA to thefunction's name (a 0-terminated ASCII-string) and c) the function'sordinal (you have to add the 'Base' to the value to get the realordinal).
For functions, the entry-point-RVA will normally point into the codesection; for objects, it will almost always point into the data- orbss-section.
imported symbols----------------This is one for the tough.
When the compiler finds a call to a function that is in a differentexecutable (mostly in a DLL), it will, in the most simplistic case, notknow anything about the circumstances and simply output a normalcall-instruction to that symbol, the address of which the linker willhave to fix. The linker uses an import library to supply the addresses,and that library has stubs for all the exported symbols, each of whichconsists of a jump-instruction; the stubs are the actual call-targets.These jump-instructions will actually jump to an address that's fetchedfrom the import address table we're discussing here.In more sophisticated applications (when "declspec(dllimport)" is used),the compiler knows the function is imported, and outputs a call to theaddress that's in the import address table, bypassing the jump.
Hm. So far for that matter. You are kindly referred to [1] and [2].
However, the address of the function in the DLL is always necessary andwill be supplied by the loader when the application is loaded. Theloader knows which symbols in what libraries have to be looked up andtheir addresses fixed by searching the import directory.
I will better give you an example. The calls with or without__declspec(dllimport) look like this:
source: int symbol(char *); __declspec(dllimport) int symbol2(char*); void foo(void) { int i=symbol("bar"); int j=symbol2("baz"); } assembly: ... call _symbol ; without declspec(dllimport) ... call ptr cs:0xdeadbeef ; with declspec(dllimport) ...
the stub provided by the import library (not used with __declspec(dllimport)) is located in the code section and is the above mentioned jump-instruction: _symbol: jmp dword ptr [0xdeadbeef]
So however you do it, there is a call or a jump to a location theaddress of which is at 0xdeadbeef (in our example). This 0xdeadbeef isright in the middle of the import directory; it is in the entry for thefunction "symbol" in the DLL "foo.dll".
Now we'll look how an import directory is made up.
The import directory should reside in a section that's "initializeddata" and "readable".The import directory is an array of IMAGE_IMPORT_DESCRIPTORs, one foreach used DLL. The list is terminated by a IMAGE_IMPORT_DESCRIPTORthat's entirely filled with 0-bytes.An IMAGE_IMPORT_DESCRIPTOR is a struct with these members:
OriginalFirstThunk An RVA (32 bit) to a 0-terminated array of RVAs to IMAGE_THUNK_DATAs, each describing one imported function. The array will never change.
TimeDateStamp A 32-bit-timestamp that has several purposes. Let's pretent that the timestamp is 0, and handle the advanced cases later.
ForwarderChain The 32-bit-index of the first forwarder in the list of imported functions. Forwarders are also advanced stuff. Name A 32-bit-RVA to the Name (a 0-terminated ASCII string) of the DLL. FirstThunk An RVA (32 bit) to a 0-terminated array of RVAs to IMAGE_THUNK_DATAs, each describing one imported function. The array will change.
Each IMAGE_IMPORT_DESCRIPTOR in the array gives you the name of theexporting DLL and, apart from the forwarder and timestamp, it gives you2 RVAs to arrays of IMAGE_THUNK_DATAs, using 32 bits. (The last memberof each array is entirely filled with 0-bytes.)Each IMAGE_THUNK_DATA is, for now, an RVA to a IMAGE_IMPORT_BY_NAMEwhich describes the imported function.The interesting point is now, the arrays run parallel, i.e.: they pointto the same IMAGE_IMPORT_BY_NAMEs.
No need to be desparate, I will draw another picture. This is theessential contents of one IMAGE_IMPORT_DESCRIPTOR:
OriginalFirstThunk FirstThunk | | | | | | V V
0--> func1 <--0 1--> func2 <--1 2--> func3 <--2 3--> foo <--3 4--> mumpitz <--4 5--> knuff <--5 6-->0 0<--6 /* the last RVA is 0! */
where the names in the center are the yet to discussIMAGE_IMPORT_BY_NAMEs. Each of them is a 16-bit-number (the ordinal ofthe imported symbol) followed by an unspecified amount of bytes, beingthe 0-terminated ASCII name of the imported symbol.Mind you, the ordinal may be wrong. It is only a hint where to startlooking for the symbol name in the export table of the exporting DLL;you may not compare the ordinals and take the matching entry, but youmust find the matching symbol name.
To summarize, if you want to look up information about the importedfunction "foo" from DLL "knurr", you first find the entryIMAGE_DIRECTORY_ENTRY_IMPORT in the data directories, get an RVA, findthat address in the raw section data and now have an array ofIMAGE_IMPORT_DESCRIPTORs. Get the member of this array that relates tothe DLL "knurr" by inspecting the strings pointed to by the 'Name's.When you have found the right IMAGE_IMPORT_DESCRIPTOR, follow its'OriginalFirstThunk' and get hold of the pointed-to array ofIMAGE_THUNK_DATAs; inspect the RVAs and find the function "foo".
This was the basic structure, for simple cases. Now we'll learn abouttweaks in the import directories.
First, the bit IMAGE_ORDINAL_FLAG (that is: the MSB) of theIMAGE_THUNK_DATA in the arrays can be set, in which case there is nosymbol-name-information in the list and the symbol is imported purely byordinal. You get the ordinal by inspecting the lower word of theIMAGE_THUNK_DATA.Obviously, in this case the warning about unreliable ordinals doesn'tapply because all you have *is* the ordinal.
Ok, now, why do we have *two* lists of pointers to theIMAGE_IMPORT_BY_NAMEs? Because at runtime the application doesn't needthe imported functions' names but the addresses. The loader will look upeach imported symbol in the export-directory of the DLL in question andreplace the IMAGE_THUNK_DATA in the 'FirstThunk'-list with the linearaddress of the DLL's entry point. Remember the stubs "jmp dword ptr[0xdeadbeef]"; this address 0xdeadbeef is exactly the address of theIMAGE_THUNK_DATA in the 'FirstThunk'-list.The 'OriginalFirstThunk' remains untouched, so you can always look upthe original list of imported names via the 'OriginalFirstThunk'-list.
Now we get the so-called "bound imports".Think about the loader's task: when a binary that it wants to executeneeds a function from a DLL, the loader loads the DLL, finds its exportdirectory, looks up the function's RVA and calculates the function'sentry point. Then it patches the so-found address into the 'FirstThunk'-list.Given that the programmer was clever and supplied unique preferred loadaddresses for the DLLs that don't clash, we can assume that thefunctions' entry points will always be the same. They can be computedand patched into the 'FirstThunk'-list at link-time, and that's whathappens with the "bound imports". (The utility "bind" does this.)
Of course, one must be cautious: The user's DLL may have a differentversion, or it may be necessary to relocate the DLL, thus invalidatingthe pre-patched 'FirstThunk'-list; in this case, the loader will stillbe able to walk the 'OriginalFirstThunk'-list, find the imported symbolsand re-patch the 'FirstThunk'-list. The loader knows that this isnecessary if a) the versions of the exporting DLL don't match or b) theexporting DLL had to be relocated.
To decide whether there were relocations is no problem for the loader,but how to find out if the versions differ? This is where the'TimeDateStamp' of the IMAGE_IMPORT_DESCRIPTOR comes in. If it is 0, theimport-list has not been bound, and the loader must fix the entry pointsalways. Otherwise, the imports are bound, and 'TimeDateStamp' must matchthe 'TimeDateStamp' of the exporting DLL's 'FileHeader'; if it doesn'tmatch, the loader assumes that the binary is bound to a "wrong" DLL andwill re-patch the import list.
This was the so-called "old-style" binding.
There is an additional quirk about "forwarders" in the import-list. A DLLcan export a symbol that's not defined in the DLL but imported fromanother DLL; such a symbol is said to be forwarded. Now, obviously youcan't tell if the symbol's entry point is valid by looking into thetimestamp of a DLL that doesn't actually contain the entry point. So theforwarded symbols' entry points must always be fixed up, for safetyreasons. In the import list of a binary, imports of forwarded symbolsneed to be found so the loader can patch them.
This is done via the 'ForwarderChain'. It is an index in the thunk-lists; the import at the indexed position is a forwarded export, and thecontents of the 'FirstThunk'-list at this position is the index of the*next* forwarded import, and so on, until the index is "-1" whichindicates there are no more forwards.
At this point, we should sum up what we have had so far :-)
Ok, I will assume you have found the IMAGE_DIRECTORY_ENTRY_IMPORT and you havefollowed it to find the import-directory, which will be in one of thesections. Now you're at the beginning of an array ofIMAGE_IMPORT_DESCRIPTORs the last of which will be entirely 0-bytes-filled.To decipher one of the IMAGE_IMPORT_DESCRIPTORs, you first look into the'Name'-field, follow the RVA and thusly find the name of the exportingDLL. Next you decide whether the imports are bound or not;'TimeDateStamp' will be non-zero if the imports are bound. If they arebound, now is a good time to check if the DLL version matches yours bycomparing the 'TimeDateStamp's.Now you follow the 'OriginalFirstThunk'-RVA to go to theIMAGE_THUNK_DATA-array; walk down this array (it is be 0-terminated),and each member will be the RVA of a IMAGE_IMPORT_BY_NAME (unless thehi-bit is set in which case you don't have a name but are left with amere ordinal). Follow the RVA, and skip 2 bytes (the ordinal), and nowyou have got a pointer to a 0-terminated ASCII-string that's the name ofthe imported function.To find the supplied entry point addresses in case it is a bound import,follow the 'FirstThunk' and walk it parallel to the'OriginalFirstThunk'-array; the array-members are the linear addressesof the entry points.
There is one thing I didn't mention until now: Apparently there arelinkers that exhibit a bug when they build the import directory (I'vefound this bug being in use by a Borland C linker). These linkers setthe 'OriginalFirstThunk' in the IMAGE_IMPORT_DESCRIPTOR to 0 and createonly the 'FirstThunk'-array. Obviously, such import directories cannotbe bound (else the necessary information to re-fix the imports werelost). In this case, you will have to follow the 'FirstThunk'-array to getthe imported symbol names, and you will never have pre-patched entry pointaddresses.
The last tweak about the import directories is the so-called "new style"binding (it is described in [3]), which can also be done with the"bind"-utility. When this is used, the 'TimeDateStamp' is set toall-bits-1 and there is no forwarderchain; all imported symbols get theiraddress patched, whether they are forwarded or not. Still, you need toknow the DLLs' version, and you need to distinguish forwarded symbolsfrom ordinary ones. For this purpose, theIMAGE_DIRECTORY_ENTRY_BOUND_IMPORT directory is created. This will, asfar as I could find out, *not* be in a section but in the header, afterthe section headers and before the first section. (Hey, I didn't inventthis, I'm only describing it!)This directory tells you, for each used DLL, from which other DLLs thereare forwarded exports.The structure is an IMAGE_BOUND_IMPORT_DESCRIPTOR, comprising (in thisorder):A 32-bit number, giving you the 'TimeDateStamp' of the DLL;a 16-bit-number 'OffsetModuleName', being the offset from the beginningof the directory to the 0-terminated name of the forwarded-to DLL;a 16-bit-number 'NumberOfModuleForwarderRefs' giving you the number ofDLLs that this DLL uses for its forwarders.
Immediatly following this struct you find 'NumberOfModuleForwarderRefs'structs that tell you the names and versions of the DLLs that this DLLforwards from. These structs are 'IMAGE_BOUND_FORWARDER_REF's:A 32-bit-number 'TimeDateStamp';a 16-bit-number 'OffsetModuleName', being the offset from the beginningof the directory to the 0-terminated name of the forwarded-from DLL;16 unused bits.
Following the 'IMAGE_BOUND_FORWARDER_REF's is the next'IMAGE_BOUND_IMPORT_DESCRIPTOR' and so on; the list is terminated by anall-0-bits-IMAGE_BOUND_IMPORT_DESCRIPTOR.
Sorry for the inconvenience, but that's what it looks like :-) Now, if you have a new-bound import directory, you load all the DLLs,use the directory pointer IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT to find theIMAGE_BOUND_IMPORT_DESCRIPTOR, scan through it and check if the'TimeDateStamp's of the loaded DLLs match the ones given in thisdirectory. If not, fix them in the 'FirstThunk'-array of the importdirectory.It is easy if you know how it works :-)
resources---------The resources, like dialog boxes, menus, icons and so on, are stored inthe data directory pointed to by IMAGE_DIRECTORY_ENTRY_RESOURCE. It is ina section that has, at least, the bits 'IMAGE_SCN_CNT_INITIALIZED_DATA'and 'IMAGE_SCN_MEM_READ' set.
A resource base is a 'IMAGE_RESOURCE_DIRECTORY's; it contains several'IMAGE_RESOURCE_DIRECTORY_ENTRY's which in turn may point to a'IMAGE_RESOURCE_DIRECTORY'. This way, you get a tree of'IMAGE_RESOURCE_DIRECTORY's with 'IMAGE_RESOURCE_DIRECTORY_ENTRY's asleafs; these leafs point to the actual resource data.
In real life, the situation is somewhat relaxed. Normally you won't findconvoluted trees you can't possibly sort out.The hierarchy is, normally, like this: one directory is the root. Itpoints to directories, one for each resource type. These directoriespoint to subdirectories, each of which will have a name or an ID andpoint to a directory of the languages provided for this resource; foreach language you will find one resource entry, which will finally pointto the data.
The tree, without the pointer to the data, may look like this:
(root) | +----------------+------------------+ | | | menu dialog icon | | | +-----+-----+ +-+----+ +-+----+----+ | | | | | | | "main" "popup" 0x10 "maindlg" 0x100 0x110 0x120 | | | | | | | +---+-+ | | | | | | | | default english default def. def. def.german english
A IMAGE_RESOURCE_DIRECTORY comprises:32 bits of unused flags called 'Characteristics';32 bits 'TimeDateStamp' (again in the common time_t representation),giving you the time the resource was created (if the entry is set);16 bits 'MajorVersion' and 16 bits 'MinorVersion', thusly allowing youto maintain several versions of the resource;16 bits 'NumberOfNamedEntries' and another 16 bits 'NumberOfIdEntries'.Immediatly following such a structure are'NumberOfNamedEntries'+'NumberOfIdEntries' structs which are of theformat 'IMAGE_RESOURCE_DIRECTORY_ENTRY', those with the names coming first.They may point to further 'IMAGE_RESOURCE_DIRECTORY's or they point tothe actual resource data.A IMAGE_RESOURCE_DIRECTORY_ENTRY consists of:32 bits giving you the id of the resource or the directoryit describes;32 bits offset to the data or offset to the next sub-directory.
The meaning of the id depends on the level in the tree; the id may be anumber (if the hi-bit is clear) or a name (if the hi-bit is set). If itis a name, the lower 31 bits are the offset from the beginning of theresource section's raw data to the name (the name consists of 16 bitslength and trailing wide characters, in unicode, not 0-terminated).
If you are in the root-directory, the id, if it is a number, is theresource-type: 1: cursor 2: bitmap 3: icon 4: menu 5: dialog 6: string table 7: font directory 8: font 9: accelerators 10: unformatted resource data 11: message table 12: group cursor 14: group icon 16: version informationAny other number is user-defined. Any resource-type with a type-name isalways user-defined.
If you are one level deeper, the id is the resource-id (or resource-name).
If you are another level deeper, the id must be a number, and it is thelanguage-id of the specific instance of the resource; for example, youcan have the same dialog in english, french and german localized forms,and they all share the same resource-id. The system will choose thedialog to load based on the process's locale, which in turn will usuallyreflect the user's "regional setting".(If the resource cannot be found for the process locale, the system willfirst try to find a resource for the locale using a neutral sublanguage;if it still can't be found, the instance with the smallest language id willbe used.)To decipher the language id, split it into the primary language id andthe sublanguage id using the macros PRIMARYLANGID() and SUBLANGID(),giving you the bits 0 to 9 or 10 to 15, respectivly. The values aredefined in the file "winresrc.h".Language-resources are only supported for accelerators, dialogs, menus,rcdata or stringtables; other resource-types should beLANG_NEUTRAL/SUBLANG_NEUTRAL.
To find out whether the next level below a resource directory is anotherdirectory, you inspect the hi-bit of the offset. If it is set, theremaining 31 bits are the offset from the beginning of the resourcesection's raw data to the next directory, again in the formatIMAGE_RESOURCE_DIRECTORY with trailing IMAGE_RESOURCE_DIRECTORY_ENTRYs.
If the bit is clear, the offset is the distance from the beginning ofthe resource section's raw data to the resource's raw data description,a IMAGE_RESOURCE_DATA_ENTRY. It consists of 32 bits 'OffsetToData' (theoffset to the raw data, counting from the beginning of the resourcesection's raw data), 32 bits of 'Size' of the data, 32 bits 'CodePage'and 32 unused bits.(The use of codepages is discouraged, you should use the 'language'-feature to support multiple locales.)
The raw data format depends on the resource type; descriptions can befound in the MS SDK documentation. Note that any string in resources isalways in UNICODE. relocations-----------The last data directory I will describe is the base relocationdirectory. It is pointed to by the IMAGE_DIRECTORY_ENTRY_BASERELOC entryin the data directories of the optional header. It is typicallycontained in a section if its own, with a name like ".reloc" and thebits IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE andIMAGE_SCN_MEM_READ set.The relocation data is needed by the loader if the image cannot beloaded to the preferred load address 'ImageBase' mentioned in theoptional header. In this case, the fixed addresses supplied by thelinker are no longer valid, and the loader has to apply fixups forabsolute addresses used for locations of static variables, stringliterals and so on.
The relocation directory is a sequence of chunks. Each chunk containsthe relocation information for 4 KB of the image. A chunk starts with a'IMAGE_BASE_RELOCATION' struct. It consists of 32 bits 'VirtualAddress'and 32 bits 'SizeOfBlock'. It is followed by the chunk's actualrelocation data, being 16 bits each.The 'VirtualAddress' is the base RVA that the relocations of this chunkneed to be applied to; the 'SizeOfBlock' is the size of the entire chunkin bytes.The number of trailing relocations is('SizeOfBlock'-sizeof(IMAGE_BASE_RELOCATION))/2The relocation information ends when you encounter aIMAGE_BASE_RELOCATION struct with a 'VirtualAddress' of 0.
Each 16-bit-relocation information consists of the actual relocationposition in the lower 12 bits and a relocation type in the high 4 bits.To get the relocation RVA, you need to add the IMAGE_BASE_RELOCATION's'VirtualAddress' to the 12-bit-position. The type is one of: IMAGE_REL_BASED_ABSOLUTE (0) This is a no-op; it is used to align the chunk to a 32-bits- border. IMAGE_REL_BASED_HIGH (1) The relocation must be applied to the high 16 bits of the DWORD in question. IMAGE_REL_BASED_LOW (2) The relocation must be applied to the low 16 bits of the DWORD in question. IMAGE_REL_BASED_HIGHLOW (3) The relocation must be applied to the entire 32 bits in question. IMAGE_REL_BASED_HIGHADJ (4) Unknown IMAGE_REL_BASED_MIPS_JMPADDR (5) Unknown IMAGE_REL_BASED_SECTION (6) Unknown IMAGE_REL_BASED_REL32 (7) Unknown
As an example, if you find the relocation information to be 0x00004000 (32 bits, starting RVA) 0x00000010 (32 bits, size of chunk) 0x3012 (16 bits reloc data) 0x3080 (16 bits reloc data) 0x30f6 (16 bits reloc data) 0x0000 (16 bits reloc data) 0x00000000 (next chunk's RVA) 0xff341234you know the first chunk describes relocations starting at RVA 0x4000 andis 16 bytes long. Because the header uses 8 bytes and one relocationuses 2 bytes, there are (16-8)/2=4 relocations in the chunk.The first relocation is to be applied to the DWORD at 0x4012, the nextto the DWORD at 0x4080, and the third to the DWORD at 0x40f6. The lastrelocation is a no-op.The next chunk has a RVA of 0 and finishes the list.
Now, how do you do a relocation?You know that the image *is* relocated to the preferred load address'ImageBase' in the optional header; you also know the address you didload the image to. If they match, you don't need to do anything.If they don't match, you calculate the differenceactual_base-preferred_baseand add that value to the relocation positions, which you will find withthe method described above.
Acknowledgments---------------Thanks go to David Binette for his debugging and proof-reading.(The remaining errors are entirely mine.) Copyright---------This text is copyright 1998 by B. Luevelsmeyer. It is freeware, and youmay use it for any purpose but on you own risk. It contains errors andit is incomplete. You have been warned. Bug reports-----------Send any bug reports (or other comments) tobernd.luevelsmeyer@iplan.heitec.net Literature----------[1]"Peering Inside the PE: A Tour of the Win32 Portable Executable FileFormat" (M. Pietrek), in: Microsoft Systems Journal 3/1994
[2]"Why to Use _declspec(dllimport) & _declspec(dllexport) In Code", MSKnowledge Base Q132044
[3]"Windows Q&A" (M. Pietrek), in: Microsoft Systems Journal 8/1995
[4]"Writing Multiple-Language Resources", MS Knowledge Base Q89866
[5]"The Portable Executable File Format from Top to Bottom" (Randy Kath),in: Microsoft Developer Network
Appendix: hello world---------------------In this appendix I will show how to make programs by hand. The examplewill use Intel-assembly, because I don't speak DEC Alpha.The program will be the equivalent of
#include <stdio.h> int main(void) { puts(hello,world); return 0; }
First, I translate it to use Win32 functions instead of the C runtime:
#define STD_OUTPUT_HANDLE -11UL #define hello "hello, world\n"
__declspec(dllimport) unsigned long __stdcall GetStdHandle(unsigned long hdl);
__declspec(dllimport) unsigned long __stdcall WriteConsoleA(unsigned long hConsoleOutput, const void *buffer, unsigned long chrs, unsigned long *written, unsigned long unused );
static unsigned long written;
void startup(void) { WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE),hello,sizeof(hello)-1,&written,0); return; }
Now I will fumble out the assembly: startup: ; parameters for WriteConsole(), backwards 6A 00 push 0x00000000 68 ?? ?? ?? ?? push offset _written 6A 0D push 0x0000000d 68 ?? ?? ?? ?? push offset hello ; parameter for GetStdHandle() 6A F5 push 0xfffffff5 2E FF 15 ?? ?? ?? ?? call dword ptr cs:__imp__GetStdHandle@4 ; result is last parameter for WriteConsole() 50 push eax 2E FF 15 ?? ?? ?? ?? call dword ptr cs:__imp__WriteConsoleA@20 C3 ret
hello: 68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A "hello, world\n" _written: 00 00 00 00
I need to find the functions WriteConsoleA() and GetStdHandle(). Theyhappen to be in "kernel32.dll".
Now I can start to make the executable. Question marks will take theplace of yet-to-find-out values; they will be patched afterwards.
First the DOS-header, starting at 0x0 and being 0x40 bytes long: 00 | 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 | 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00As you can see, this isn't really a MS-DOS program. It's just the headerwith the signature "MZ" at the beginning and the e_lfanew pointingimmediatly after the header, without any code. That's because it isn'tintended to run on MS-DOS.
Then the signature, starting at 0x40 and being 0x4 bytes long: 50 45 00 00
Now the file-header, which will start at byte 0x44 and is 0x14 bytes long: Machine 4c 01 ; i386 NumberOfSections 02 00 ; code and data TimeDateStamp 00 00 00 00 ; who cares? PointerToSymbolTable 00 00 00 00 ; unused NumberOfSymbols 00 00 00 00 ; unused SizeOfOptionalHeader e0 00 ; constant Characteristics 02 01 ; executable on 32-bit-machine
And the optional header, which will start at byte 0x58 and is 0x60 bytes long: Magic 0b 01 ; constant MajorLinkerVersion 00 ; I'm version 0.0 :-) MinorLinkerVersion 00 ; SizeOfCode 20 00 00 00 ; 32 bytes of code SizeOfInitializedData ?? ?? ?? ?? ; yet to find out SizeOfUninitializedData 00 00 00 00 ; we don't have a BSS AddressOfEntryPoint ?? ?? ?? ?? ; yet to find out BaseOfCode ?? ?? ?? ?? ; yet to find out BaseOfData ?? ?? ?? ?? ; yet to find out ImageBase 00 00 10 00 ; 1 MB, chosen arbitrarily SectionAlignment 20 00 00 00 ; 32-bytes-alignment FileAlignment 20 00 00 00 ; 32-bytes-alignment MajorOperatingSystemVersion 04 00 ; NT 4.0 MinorOperatingSystemVersion 00 00 ; MajorImageVersion 00 00 ; version 0.0 MinorImageVersion 00 00 ; MajorSubsystemVersion 04 00 ; Win32 4.0 MinorSubsystemVersion 00 00 ; Win32VersionValue 00 00 00 00 ; unused? SizeOfImage ?? ?? ?? ?? ; yet to find out SizeOfHeaders ?? ?? ?? ?? ; yet to find out CheckSum 00 00 00 00 ; not used for non-drivers Subsystem 03 00 ; Win32 console DllCharacteristics 00 00 ; unused (not a DLL) SizeOfStackReserve 00 00 10 00 ; 1 MB stack SizeOfStackCommit 00 10 00 00 ; 4 KB to start with SizeOfHeapReserve 00 00 10 00 ; 1 MB heap SizeOfHeapCommit 00 10 00 00 ; 4 KB to start with LoaderFlags 00 00 00 00 ; unknown NumberOfRvaAndSizes 10 00 00 00 ; constant
As you can see, I plan to have only 2 sections, one for code and one forall the rest (data, constants and import directory). There will be norelocations and no other stuff like resources.The section alignment is the same in the file and in RAM (32 bytes);this helps to keep the task easy.
Now we set up the data directories, beginning at byte 0xb8 and being 0x80 bytes long: Address Size 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXPORT (0) ?? ?? ?? ?? ?? ?? ?? ?? ; IMAGE_DIRECTORY_ENTRY_IMPORT (1) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_SECURITY (4) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_DEBUG (6) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_TLS (9) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IAT (12) 00 00 00 00 00 00 00 00 ; 13 00 00 00 00 00 00 00 00 ; 14 00 00 00 00 00 00 00 00 ; 15Only the import directory is in use.
Next are the section headers. First we make the code section, which willcontain the above mentioned assembly. It is 32 bytes long, and so willbe the code section. The header begins at 0x138 and is 0x28 bytes long:
Name 2e 63 6f 64 65 00 00 00 ; ".code" VirtualSize 00 00 00 00 ; unused VirtualAddress ?? ?? ?? ?? ; yet to find out SizeOfRawData 20 00 00 00 ; size of code PointerToRawData ?? ?? ?? ?? ; yet to find out PointerToRelocations 00 00 00 00 ; unused PointerToLinenumbers 00 00 00 00 ; unused NumberOfRelocations 00 00 ; unused NumberOfLinenumbers 00 00 ; unused Characteristics 20 00 00 60 ; code, executable, readable
The second section will contain the data. The header begins at 0x160 andis 0x28 bytes long:
Name 2e 64 61 74 61 00 00 00 ; ".data" VirtualSize 00 00 00 00 ; unused VirtualAddress ?? ?? ?? ?? ; yet to find out SizeOfRawData ?? ?? ?? ?? ; yet to find out PointerToRawData ?? ?? ?? ?? ; yet to find out PointerToRelocations 00 00 00 00 ; unused PointerToLinenumbers 00 00 00 00 ; unused NumberOfRelocations 00 00 ; unused NumberOfLinenumbers 00 00 ; unused Characteristics 40 00 00 c0 ; initialized, readable, writeable
The next byte is 0x188, but the sections need to be aligned to 32 bytes(because I chose so), so we need padding bytes up to 0x1a0:
00 00 00 00 00 00 ; padding 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Now the first section, being the code section with the above mentionedassembly, *does* come. It begins at byte 0x1a0 and is 0x20 bytes long: 6A 00 ; push 0x00000000 68 ?? ?? ?? ?? ; push offset _written 6A 0D ; push 0x0000000d 68 ?? ?? ?? ?? ; push offset hello_string 6A F5 ; push 0xfffffff5 2E FF 15 ?? ?? ?? ?? ; call dword ptr cs:__imp__GetStdHandle@4 50 ; push eax 2E FF 15 ?? ?? ?? ?? ; call dword ptr cs:__imp__WriteConsoleA@20 C3 ; retBecause of the previous section's length we don't need any paddingbefore the next section (data), and here it comes, beginning at 0x1c0:
68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n" 00 00 00 ; padding to align _written 00 00 00 00 ; _written
Now all that's left is the import directory. It will import 2 functionsfrom "kernel32.dll", and it's immediatly following the variables in thesame section. First we will align it to 32 bytes:
00 00 00 00 00 00 00 00 00 00 00 00 ; padding
It begins at 0x1e0 with the IMAGE_IMPORT_DESCRIPTOR: OriginalFirstThunk ?? ?? ?? ?? ; yet to find out TimeDateStamp 00 00 00 00 ; unbound ForwarderChain ff ff ff ff ; no forwarders Name ?? ?? ?? ?? ; yet to find out FirstThunk ?? ?? ?? ?? ; yet to find out
We need to terminate the import-directory with a 0-bytes-entry (we are at 0x1f4): OriginalFirstThunk 00 00 00 00 ; terminator TimeDateStamp 00 00 00 00 ; ForwarderChain 00 00 00 00 ; Name 00 00 00 00 ; FirstThunk 00 00 00 00 ;
Now there's the DLL name left, and the 2 thunks, and the thunk-data, andthe function names. But we will be finished real soon now!
The DLL name, 0-terminated, beginning at 0x208: 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll" 00 00 00 ; padding to 32-bit-boundary
The original first thunk, starting at 0x218: AddressOfData ?? ?? ?? ?? ; RVA to function name "WriteConsoleA" AddressOfData ?? ?? ?? ?? ; RVA to function name "GetStdHandle" 00 00 00 00 ; terminator
The first thunk is exactly the same list and starts at 0x224:(__imp__WriteConsoleA@20, at 0x224) AddressOfData ?? ?? ?? ?? ; RVA to function name "WriteConsoleA"(__imp__GetStdHandle@4, at 0x228) AddressOfData ?? ?? ?? ?? ; RVA to function name "GetStdHandle" 00 00 00 00 ; terminator
Now what's left is the two function names in the shape of anIMAGE_IMPORT_BY_NAME. We are at byte 0x230. 01 00 ; ordinal, need not be correct 57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00 ; "WriteConsoleA" 02 00 ; ordinal, need not be correct 47 65 74 53 74 64 48 61 6e 64 6c 65 00 ; "GetStdHandle"
Ok, that's about all. The next byte, which we don't really need, is0x24f. We need to fill the section with padding up to 0x260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; padding 00
------------
We are done. Now that we know all the byte-offsets, we can apply fixupsto all those addresses and sizes that were indicated as "unknown" with'??'-marks.I won't force you to read that step-by-step (it's quitestraightforward), and simply present the result:
------------
DOS-header, starting at 0x0: 00 | 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 | 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00
signature, starting at 0x40: 50 45 00 00
file-header, starting at 0x44: Machine 4c 01 ; i386 NumberOfSections 02 00 ; code and data TimeDateStamp 00 00 00 00 ; who cares? PointerToSymbolTable 00 00 00 00 ; unused NumberOfSymbols 00 00 00 00 ; unused SizeOfOptionalHeader e0 00 ; constant Characteristics 02 01 ; executable on 32-bit-machine
optional header, starting at 0x58: Magic 0b 01 ; constant MajorLinkerVersion 00 ; I'm version 0.0 :-) MinorLinkerVersion 00 ; SizeOfCode 20 00 00 00 ; 32 bytes of code SizeOfInitializedData a0 00 00 00 ; data section size SizeOfUninitializedData 00 00 00 00 ; we don't have a BSS AddressOfEntryPoint a0 01 00 00 ; beginning of code section BaseOfCode a0 01 00 00 ; RVA to code section BaseOfData c0 01 00 00 ; RVA to data section ImageBase 00 00 10 00 ; 1 MB, chosen arbitrarily SectionAlignment 20 00 00 00 ; 32-bytes-alignment FileAlignment 20 00 00 00 ; 32-bytes-alignment MajorOperatingSystemVersion 04 00 ; NT 4.0 MinorOperatingSystemVersion 00 00 ; MajorImageVersion 00 00 ; version 0.0 MinorImageVersion 00 00 ; MajorSubsystemVersion 04 00 ; Win32 4.0 MinorSubsystemVersion 00 00 ; Win32VersionValue 00 00 00 00 ; unused? SizeOfImage c0 00 00 00 ; sum of all section sizes SizeOfHeaders a0 01 00 00 ; offset to 1st section CheckSum 00 00 00 00 ; not used for non-drivers Subsystem 03 00 ; Win32 console DllCharacteristics 00 00 ; unused (not a DLL) SizeOfStackReserve 00 00 10 00 ; 1 MB stack SizeOfStackCommit 00 10 00 00 ; 4 KB to start with SizeOfHeapReserve 00 00 10 00 ; 1 MB heap SizeOfHeapCommit 00 10 00 00 ; 4 KB to start with LoaderFlags 00 00 00 00 ; unknown NumberOfRvaAndSizes 10 00 00 00 ; constant
data directories, starting at 0xb8: Address Size 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXPORT (0) e0 01 00 00 6f 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IMPORT (1) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_SECURITY (4) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_DEBUG (6) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_TLS (9) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IAT (12) 00 00 00 00 00 00 00 00 ; 13 00 00 00 00 00 00 00 00 ; 14 00 00 00 00 00 00 00 00 ; 15
section header (code), starting at 0x138: Name 2e 63 6f 64 65 00 00 00 ; ".code" VirtualSize 00 00 00 00 ; unused VirtualAddress a0 01 00 00 ; RVA to code section SizeOfRawData 20 00 00 00 ; size of code PointerToRawData a0 01 00 00 ; file offset to code section PointerToRelocations 00 00 00 00 ; unused PointerToLinenumbers 00 00 00 00 ; unused NumberOfRelocations 00 00 ; unused NumberOfLinenumbers 00 00 ; unused Characteristics 20 00 00 60 ; code, executable, readable
section header (data), starting at 0x160: Name 2e 64 61 74 61 00 00 00 ; ".data" VirtualSize 00 00 00 00 ; unused VirtualAddress c0 01 00 00 ; RVA to data section SizeOfRawData a0 00 00 00 ; size of data section PointerToRawData c0 01 00 00 ; file offset to data section PointerToRelocations 00 00 00 00 ; unused PointerToLinenumbers 00 00 00 00 ; unused NumberOfRelocations 00 00 ; unused NumberOfLinenumbers 00 00 ; unused Characteristics 40 00 00 c0 ; initialized, readable, writeable
(padding) 00 00 00 00 00 00 ; padding 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
code section, starting at 0x1a0: 6A 00 ; push 0x00000000 68 d0 01 10 00 ; push offset _written 6A 0D ; push 0x0000000d 68 c0 01 10 00 ; push offset hello_string 6A F5 ; push 0xfffffff5 2E FF 15 28 02 10 00 ; call dword ptr cs:__imp__GetStdHandle@4 50 ; push eax 2E FF 15 24 02 10 00 ; call dword ptr cs:__imp__WriteConsoleA@20 C3 ; ret
data section, beginning at 0x1c0: 68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n" 00 00 00 ; padding to align _written 00 00 00 00 ; _writtenpadding: 00 00 00 00 00 00 00 00 00 00 00 00 ; paddingIMAGE_IMPORT_DESCRIPTOR, starting at 0x1e0: OriginalFirstThunk 18 02 00 00 ; RVA to orig. 1st thunk TimeDateStamp 00 00 00 00 ; unbound ForwarderChain ff ff ff ff ; no forwarders Name 08 02 00 00 ; RVA to DLL name FirstThunk 24 02 00 00 ; RVA to 1st thunkterminator (0x1f4): OriginalFirstThunk 00 00 00 00 ; terminator TimeDateStamp 00 00 00 00 ; ForwarderChain 00 00 00 00 ; Name 00 00 00 00 ; FirstThunk 00 00 00 00 ;The DLL name, at 0x208: 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll" 00 00 00 ; padding to 32-bit-boundaryoriginal first thunk, starting at 0x218: AddressOfData 30 02 00 00 ; RVA to function name "WriteConsoleA" AddressOfData 40 02 00 00 ; RVA to function name "GetStdHandle" 00 00 00 00 ; terminatorfirst thunk, starting at 0x224: AddressOfData 30 02 00 00 ; RVA to function name "WriteConsoleA" AddressOfData 40 02 00 00 ; RVA to function name "GetStdHandle" 00 00 00 00 ; terminatorIMAGE_IMPORT_BY_NAME, at byte 0x230: 01 00 ; ordinal, need not be correct 57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00 ; "WriteConsoleA"IMAGE_IMPORT_BY_NAME, at byte 0x240: 02 00 ; ordinal, need not be correct 47 65 74 53 74 64 48 61 6e 64 6c 65 00 ; "GetStdHandle"(padding) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; padding 00First unused byte: 0x260
--------------
Alas, this works on NT but didn't on windows 95. windows95 can't runapplications with a section alignment of 32 bytes, it needs analignment of 4 KB and, apparently, a file alignment of 512 bytes. So forwindows95 you'll have to insert a large number of 0-bytes (for padding)and adjust the RVAs. Thanks go to D. Binette for testing on windows95.
-- end of text --