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. |
00009890 00 00 00 00 21 09 1C 56 00 00 00 00 F6 A7 00 00
000098A0 01 00 00 00 53 00 00 00 53 00 00 00 B8 A4 00 00
000098B0 04 A6 00 00 50 A7 00 00 50 1F 00 00 B0 1F 00 00
0xA490 (file 0x9890): its name at 0xA7F6 (glfw3.dll), base 1, 83 functions, 83 names, and the three tables at 0xA4B8, 0xA604 and 0xA750. The address table starts straight after it: 0x1F50, 0x1FB0.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.
In the example, ordinal 1 is at 0x1F50, which is
glfwCreateCursor, ordinal 2 at 0x1FB0, 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 0xA604 (file 0x9A04) starts
00 A8 00 00 11 A8 00 00 2A A8 00 00: the names at 0xA800
(glfwCreateCursor), 0xA811 and 0xA82A.
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 0xA750 (file 0x9B50) 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 0xA7F6 and
the first function’s name at 0xA800. 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.