Export Directory

Note Lets talk about how the loader find the export funtions exported by the DLLs. Lets first look at the structure definition of export directory from winnt.h header file.

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

There are some more important fields than the others. They following are all arrays:

  1. AddressOfFunction - RVAs of function ( or pointers ) which are exported in the DLL, and this array is called export address table.
  2. AddressOfNames - The names of the functions from the export adderess table
  3. AddressOfNameOrdinals. - This is the array of numbers.

The field NumberOfFunctions lists ther number of exported functions. The field NumberOfNames lists the number of entried from the array AddressOfNames and AddressOfNameOrdinals. These sizes of these arrays are similar.

Another field is Name holds the RVA to string with the name of the DLL. The base contai ns the first ordinal number of the DLL.

The DLL can export the functions as names and ordinals ( just a number!).

Lets see how the loader find address to an export function. Lets look at the image below:

c28a1b98de3aafe6e024007f663cf697.png

Lets say the loader is looking for a function with name name3. The loader first checks the array AddressOfNames which is the array that holds the name of the exported functions. The loader finds the name in the array at an particular offset. The loader then checks the array AddressOfNameOrdinals which is array that contains some numbers. At the same offset from the AddressOfNames, the loader finds the ordinal that corresponds to function Name3 which is 4.

Now, armed with this information, loader would then check the 4 memory location in AddressOfFunction to find the RVA of name3 function.

There are sometimes situations when we do not have function name instead only ordinal number is provided. In which case to find the RVA, we find the base of the ordinal, and substract it from the ordinal which we have. For example if 6 is the number then 6-1=5 would be the location where out RVA is located.

Lets take the example of kernel32.dll which are hundreds of export functions. Under Optional Hdr, and Export Directory has the address 90190.

0d8128df72cebaecbd8141f792231162.png

We match the values in the address location with that of fields desribed in the definition:

be564e570cc73e45d0e17650299f9764.png

Using the Go to RVA component, we check the RVA location 094151

1fd153328683c0ef6cd1ad1d7e622d07.png

Clicking on OK, the name of the function was observed as Kernel32.dll:

f5a135b064c1f62185ca2f8e1ab60529.png

Going further we observed the base field constains the value 1 which means number of ordinal starts at 1. The NumberOfFunctions holds value 065D which in decimal 1629 functions. The NumberOfNames has the same value 065D. The last three are arrays which we discussed earlier. To View these values already resolved, we can click on Exports tab of PE Bear.

03152c3570217726ae99da99309a5ade.png

If we click on the AddressOfFunctions value 901BB, addresss layout would show their values.

9a4702e5c541a43119714f0596696ddf.png 8888e8037cbd2fba25ba11604bbf5db4.png

The other two arrays are also resolved in similar fashion.

Another interesting thing to note is related is the function RVA addresses location, for example the high address locations 9417F and there are low address locations 1e860. When one of low address locations are clicked, we are in the .text section of the PE.

f041bf2e4b8e8af86bf715ad860229b6.png

But high addresses are in the .rdata sections:

7bccb50d8e3606ffd41e2926b5d8ca92.png

Upon resolveding the address locations pointed by the RVA, we notice another DLL name:

747a5292f2757845ab6bb2937b7aa800.png

So, when the loader finds another string instead of RVA, this is usually a forwarder which points to another string, in this case NTDLL.RtlAcquireSRWLockExclusive. The loader would go to this new DLL, parse that DLL and look for the function AcquireSRWLockExclusive. This function address is returned.

But, how does the loader know that offset mentioned is a forwarded function or an RVA ? If the address falls within the range of Export Directory range, then its a forwarded function.

c08c8655c80f0ac3e2abaf68ffdb77e3.png

To see same information with dumpbin, we can do that with the command

dumpbin /exports C:\Windows\system32\kernel32.dll

2a379bbb6e2b14cf6fe2838bee679d45.png

back