A DLL makes functions and data available to other modules through its exports. Data directory 0 points at the export directory, which locates three arrays: the export address table, with one entry for each ordinal; the name pointer table, with the names in sorted order; and the ordinal table, which links each name to its entry in the address table. Programs rarely export anything; drivers and DLLs nearly always do. All the addresses are RVAs.
The export directory
IMAGE_EXPORT_DIRECTORY, 40 bytes:
| Offset | Size | Type | Field | Meaning |
|---|---|---|---|---|
| 0 | 4 | u32 | Characteristics | Reserved, 0. |
| 4 | 4 | u32 | TimeDateStamp | When the export data was made. |
| 8 | 2 | u16 | MajorVersion | A version, set by the user; usually 0. |
| 10 | 2 | u16 | MinorVersion | |
| 12 | 4 | u32 | Name | RVA of the DLL’s own name. |
| 16 | 4 | u32 | Base | The ordinal of the first entry in the address table, usually 1. |
| 20 | 4 | u32 | NumberOfFunctions | The number of entries in the address table. |
| 24 | 4 | u32 | NumberOfNames | The number of entries in the name pointer and ordinal tables. Exports without a name are reached by ordinal only. |
| 28 | 4 | u32 | AddressOfFunctions | RVA of the export address table. |
| 32 | 4 | u32 | AddressOfNames | RVA of the name pointer table. |
| 36 | 4 | u32 | AddressOfNameOrdinals | RVA of the ordinal table. |
0000B270 00 00 00 00 2D 09 1C 56 00 00 00 00 D6 C3 00 00
0000B280 01 00 00 00 53 00 00 00 53 00 00 00 98 C0 00 00
0000B290 E4 C1 00 00 30 C3 00 00 20 20 00 00 C0 20 00 00
0xC070 (file 0xB270): its name at 0xC3D6 (glfw3.dll), base 1, 83 functions, 83 names, and the three tables at 0xC098, 0xC1E4 and 0xC330. The address table starts straight after it: 0x2020, 0x20C0.Export address table
NumberOfFunctions entries of 4 bytes. Entry i is ordinal Base + i. Each entry is one of:
- the RVA of the exported function or data, in a section of the image;
- an RVA that lies inside the export directory’s own range (data directory 0’s RVA and size): a forwarder;
- 0, for an unused ordinal.
The entries are 4-byte RVAs in PE32+ too, not 64-bit addresses. In the example,
ordinal 1 is at 0x2020, which is glfwCreateCursor, ordinal 2
at 0x20C0, and so on. The
explorer names every export in the disassembly at its address.
Name pointer table
NumberOfNames entries of 4 bytes: the RVAs of the exported names, sorted in ascending order of the names’ bytes, so that a loader can search them by halving. The hint in an importer’s hint/name entry is an index into this table.
In the example the table at 0xC1E4 (file 0xB3E4) starts
E0 C3 00 00 F1 C3 00 00 0A C4 00 00: the names at 0xC3E0
(glfwCreateCursor), 0xC3F1 and 0xC40A.
Ordinal table
NumberOfNames entries of 2 bytes, one for each name in the name pointer table: the index of that name’s entry in the export address table. The index does not include Base, despite the table’s name. To find a function by name: search the name pointer table for the name, take the entry at the same position in the ordinal table, and read that entry of the address table.
In the example, the table at 0xC330 (file 0xB530) is
00 00 01 00 02 00 03 00 …: every name is in the same position as its
function, because each function has a name and they were all exported in name
order.
Names
The DLL’s own name and the exported names, in ASCII, each ending in a zero byte,
usually straight after the ordinal table and in the same order as the name pointers.
In the example the DLL’s name, glfw3.dll, is at 0xC3D6 and
the first function’s name at 0xC3E0. Forwarder strings lie among them.
The explorer groups them into one “Export names” part.
Forwarders
An export can be passed on to another DLL. Its entry in the address table then
points not at code but at a string inside the export directory’s range, naming the
DLL and the function: NTDLL.RtlAllocateHeap, or with an ordinal,
MYDLL.#27. The loader loads that DLL and uses its export instead. The
explorer shows a forwarder’s string in the exports list and does not name code at its
address.