Back to homepage

Structure of dynamic functions imports and exports

I. Introductory

About this article

This article is just a small summary regarding structures of dynamic functions import or export in 32 bits portable executable binary files. The aim is not to be comprehensive or easy to understand but small and quick to summarize.

Vocabulary

asciiz
character string (bytes) terminated by a null caracter '\0'.
dword 1
unsigned integer of 32 bits (4 bytes).
word 1
unsigned integer of 16 bits (2 bytes).
null
value equal to zero.
point to
to contain the address (or offset) to variable.
pointer
variable (in 32 bits portable executable, a dword) pointing to another one.
rva
relvative virtual address: pointer, relative to imagebase, to data in memory. Absolute virtual address pointer is defined by address = imagebase + rva
imagebase
value specifying the virtual memory address from which an portable executable is loaded in memory.
raw
address in file data (corresponding to a memory address).

1 A multi-byte value such as dword or word is coded using little endianess, which means that in the file data as well as in memory, the bytes order as reversed: 0x01020304 is stored as 04 03 02 01 in memory.

II. Portable executable header

In the portable executable format, a header – the PE header – is defined at the begin of the file. The position of the PE header is indicated at the file offset 0x3C by a dword and is composed by a static part followed by an optional part: the optional header.

PE header

In the PE header, at relative offset +20 bytes, a word value indicates the size of the Optional header.
If the Optional header is classical – all fields with 16 Data Directory entries – then, this value is 0x00E0.

Data Directory

In the Optional header, you can see also the value NumberOfDirectoryEntries that give the number of Data Directory entries, most of time, this value is set to 16.
This value is followed by the definition of Data directory table in the Optional header.

The Data directory table is an array of NumberOfDirectoryEntries × 2 dword values, at relative position +120 bytes from PE header.
So this array is composed by a couple of cells (most of time 16), themselves composed by two dword: Address (rva) to data and data Size.

struct DirectoryEntry
{
	dword Address;     // rva
	dword Size;
}

DirectoryEntry DataDirectory[NumberOfDirectoryEntries];

As Address is relative, to compute absolute address, it's needed to add the imagebase of the module.
The imagebase is specified as dword value at relative address +52 from the PE header.

For each cell corresponds to a directory (see array bellow). If a directory is not used, the Address and the Size are null.

0Export Table
1Import Table
2Resource Table
3Exception Table
4Certificate File
5Relocation Table
6Debug Data
7Architecture Data
8Global Ptr
9Thread Local Storage Table
10Load Config Table
11Bound Import Table
12Import Address Table
13Delay Import Descriptor
14COM+ Runtime Header
15Reserved

Here, we see that Export table is DataDirectory[0] while Import table is DataDirectory[1]. Note also the existence of Import Address TableDataDirectory[12] – which will be explained after.

The directory Import Table points to a non-length-fixed array for which each cell is a structure of 5 dword values.

The directory Export Table points to a structure of 10 dword values.

III. Import

This array has a dynamic size. To length of the array is not known, we need to browse each cell until the terminal cell.
The terminal cell has the Module value defined to null. Then we know the last cell is reached, this last cell which is of course ignored.

import table cell

The structure corresponding to a cell has two different design, whose I call Borland design or Microsoft design

a. Borland design

struct functionEntry
{
	word Hint;          // ordinal number of function, null if unused
	byte Name[];        // asciiz
}

struct importEntry
{
	dword Functions;    // null
	dword Timestamp;    // not interesting
	dword Forwad;       // idem
	dword Module;       // points (rva) to the asciiz module name of dll
	dword Thunk;        // in the file, points (rva) to an array of dword pointers (rva) ponting to a functionEntry.
	             	    // Windows loader will overwrite pointers to points functionEntry by loaded functions address. 2,3
}

importEntry importTable[];

borland import table cell

2 The loader overwrite data of this array (in red, above) with functions address.
If this program is dumped from memory to a file, an Import Table reconstruction is needed to restore original rva pointer to functionEntry.

b. Microsoft design

struct functionEntry
{
	word Hint;          // ordinal number of function, null if unused
	byte Name[];        // asciiz
}

struct importEntry
{
	dword Functions;    // points (rva) to an array of dword pointers (rva) ponting to a functionEntry
	dword Timestamp;    // not interesting
	dword Forwad;       // idem
	dword Module;       // points (rva) to the asciiz module name of dll
	dword Thunk;        // points (rva) to an array of pointers (rva) pointing to functions address destination. This array is filled by the loader of Windows. 3
}

importEntry importTable[];

microsoft import table cell

3 If these arrays are contiguously located in memory for all import libraries, then this zone should be pointed by the Import Address Table, in the Directory data table.

address import table

IV. Exports

An exported function can be called using its name or its Hint ordinal number.

struct
{
	dword Reserved;               // not used
	dword Timestamp;              // not interestting
	dword Version;                // idem
	dword Module;                 // points (rva) to asciiz name of module.
	dword Base;                   // first ordinal attributed to a function (most of time, 1)
	dword NumberOfFunctions;      // number of exported functions
	dword NumberOfNames;          // number of exported functions
	dword AddressOfFunctions;     // points (rva) to an array of dword pointers to code functions.
	dword AddressOfNames;         // points (rva) to an array of dword pointers (rva) to name of exported functions.
	dword AddressOfOrdinals;      // points (rva) to an array of word values corresponding to ordinal (unique number) of functions.
}

Values NumberOfFunctions and NumberOfNames are identicals.

This value defines the number of elements of the three arrays AddressOfFunctions, AddressOfNames and AddressOfOrdinals.

export table cell

contact/mail protection