The Import Section
This section contains info about all functions imported by the executable from DLLs. This is stored in various data structures and the most important of those being the import directory and the import address table. You might also have bound_import and Delay_Import directories.
The Windows loader is responsible for loading all the DLLs that the application uses and mapping them into the process address space. It has to find the addresses of all the imported functions in their various DLLs and make them available for the executable being loaded.
The addresses of functions inside a DLL are not static but change when DLLs are updated. To account for this, the IAT was used. This is a table of pointers to the function addresses which is filled in the windows loader as the DLLs are loaded.
By using a pointer table, the loader does not need to change the addresses of imported functions everywhere in the code they are called. It only has to correct the addresses in the import table.
The Import Directory
It is an array of IMAGE_IMPORT_DESCRIPTOR structures. Each structure is 20 bytes and contains information about the DLL which our PE file imports functions from. The number of structures is equal to the number of DLLs the PE file imports functions from. There's no field indicating the no. of structures and the last structure is filled with all 0's.
You can find the Import Directory by looking at the Data Directory (80 bytes from the beginning of the PE header)
The first member - OriginalFirstThink which is a DWORD union, may have at one time been a set of flags. However, Microsoft changed its meaning but did not update winnt.h. This field really contains the RVA of an array of IMAGE_THUNK_DATA structures.
ASIDE - Union is just a redefinition of the same area of memory. The union above does not contain 2 DWORDs but only one which could either be OriginalFirstThink or Characteristics
The TimeDateStamp is 0 unless the executable is bound when it contains -1.
The ForwarderChain was used for old-style binding and not considered here.
Name1 contains a pointer (RVA) to the ascii name of the DLL.
The last member FirstThunk, also contains the RVA of an array of DWORD-sized IMAGE_THINK_DATA structures. - a duplicate of the first array. If the function described is a bound import (explained below), then FirstThunk contains the actual address of the function instead of an RVA to an IMAGE_THUNK_DATA. These structures are defined as:
Each IMAGE_THUNK_DATA is a DWORD union which effectively has only one of the two values. In the file on disk it either contains the ordinal of the imported function or the RVA to an IMAGE_IMPORT_BY_NAME structure. Once loaded, the ones pointed at by FirstThunk are overwritten with addresses if imported functions - this becomes the IAT.
Each IMAGE_IMPORT_BY_NAME structure is defined as follows:
Hint - contains the index into the EAT of the DLL the functions resides in. This field is for use by the PE loader so it can look up the functions in the DLLs EAT quickly. The name at that index is tried and if it doesn't match then a binary search is done to find the name. For some linkers this value is not essential and is set to 0.
Name1 - contains the name of the imported function and is a null terminated ASCII string.
The most important parts are the imported DLL names and the arrays of IMAGE_THUNK_DATA structures. Each IMAGE_THUNK_DATA structure corresponds to one imported function from the DLL. The arrays pointed to by the OriginalFirstThunk and FirstThunk run parallel and are terminated by a null DWORD. There are separate paris of arrays of IMAGE_THUNK_DATA structures. for each imported DLL.
In other words, there are several IMAGE_IMPORT_BY_NAME structures. You create two arrays, then fill them with RVAs of those IMAGE_IMPORT_BY_NAME structures, so both arrays contain exactly the same values. Now you assign the RVA of the first array to OriginalFirstThunk and the RVA of the second array to FirstThunk.
The number of elements in the OriginalFirstThunk and FirstThunk arrays depend on the number of functions imported from the DLL. e.g. if the PE file imports 10 functions from user32.dll, Name1 in the IMAGE_IMPORT_DESCRIPTOR structure will contain the RVA of the string "user32.dll" and there will be 10 IMAGE_THUNK_DATAs in each array.
The 2 parallel arrays have been called by several different names but the most common are Import Address Table ( for the one pointed by FirstThunk) or Import Lookup Table (pointed by OriginalFirstThunk)
Why are there 2 parallel arrays of pointers to IMAGE_IMPORT_BY_NAME structures?
The Import Name Table is left alone and never modified. The IAT is overwritten by the actual function addresses by the loader. The loader iterates through each pointer in the arrays and finds the address of the function that each structure refers to. The loader then overwrites the pointer to IMAGE_IMPORT_BY_NAME with the function's addresses. The arrays of RVAs in the Import Name Tables remain unchanged so that if the need arises to find the names of imported functions, the PE loader can still find them.
Although the IAT is pointed to by entry number 12 in data directory, some linkers dont set this directory entry and the app will run nevertheless. The loader only uses this to temporarily mark the IATs as read-write during import resolution and can resolve imports without it.
This is how the windows loader is able to overwrite the IATs when it resides in the read-only section. At load time, the system temporarily sets the attribute of the pages containing the imports data to read/write. Once the imports table is initialized the pages are set back to their original attributes.
Calls to imported functions take place via a function pointer in the IAT and can take 2 forms, one more efficient than the other. e.g. imagine the address 00405030 refers to one of the entries in the FirstThunk array thats overwritten by the loader with the address of GetMessage in USER32.DLL
The efficient way to call GetMessage looks like this:
0040100C CALL DWORD PTR [00405030 ]
The inefficient way looks like this:
0040100C CALL [00402200]
.......
.......
00402200 JMP DWORD PTR [00405030]
The second method achieves the same but with 5 additional bytes of code and takes longer to execute.
Why are calls to imported functions implemented this way?
The compiler cannot distinguish between calls to ordinary functions within the same module and imported functions and emits the same output for both: CALL [XXXXXX]
where XXXXXX has to be an actual code address (not a pointer) to be filled by the linker later. The linker does not know the address of the imported function and so has to supply a substitute chunk of code - the JMP stub seen above.
The optimized form is obtained using the __declspec(dllimport) modifier to tell the compiler that the function resides in a DLL. It will then output CALL DWORD PTR [XXXXXX].
If __declspec(dllimport) has not been used when compiling the executable there will be a whole collection of jump stubs for imported functions located together somewhere in the code. This has been known by various names - "transfer area", "trampoline", "jump thunk table".
Functions Exported By Ordinal Only
When functions are exported by ordinal only, there will be no IMAGE_THUNK_BY_NAME structure for that function in the caller's module. Instead, the IMAGE_THUNK_DATA for that function contains the ordinal for that function.
Before the executable is loaded, you can tell if the IMAGE_THUNK_DATA structure contains an ordinal or an RVA by looking at the MSB or high bit. If set, then the lower 32 bits are treated as an ordinal value. If clear, the value is an RVA to an IMAGE_IMPORT_BY_NAME.
Bound Imports
When the loader loads a PE file into memory, it examines the import table and loads the required DLLs into the process address space. Then it walks the array pointed at by FirstThunk and replaces the IMAGE_THUNK_DATAs with the real addresses of import functions. If somehow the programmer can predict the addresses of functions correctly, the PE loader doesn't have to fix the IMAGE_THUNK_DATAs each time the PE file is run as the correct address is already there.
Microsoft compilers come with a utility bind.exe that examines the IAT of a PE file and replaces the IMAGE_THUNK_DATA dwords with the addresses of imported functions. When the file is loaded, the loader must check i the addresses are valid. If the DLL versions do not match the ones in the PE file or if the DLLs need to be relocated, the loader knows that the bound addresses are stale and it walks the INT to calculate new addresses.
Therefore, although the INT is not necessary for the executable to load, if not present the executable cannot be bound.
The Bound Import Directory
The information the loader uses to determine if bound addresses are valid is kept in a IMAGE_BOUND_IMPORT_DESCRIPTOR structure. A bound executable contains a list of those structures, one for each imported DLL that has been bound:
TimeDateStamp must match the TimeDateStamp of the exporting DLL's FileHeader; if it doesn't match the loader assumes that the binary is bound to a wrong dll and will re-patch the import list.
The OffsetModuleName member contains the offset (not RVA) from the first IMAGE_BOUND_IMPORT_DESCRIPTOR to the name of the DLL in the null-terminated ASCII.
The NumberOfModuleForwarderRefs member contains the number of IMAGE_BOUND_FORWARDER_REF structures that immediately follow this structure.
Its almost similar to the previous structure. The reason to have 2 similar structures like this is that when binding against a function which is forwarded to another DLL, the validity of that forwarded DLL has to be checked at load time too. This structure contains the details of those forwarded DLLs.
e.g. the function HeapAlloc in kernel32.dll is forwarded to RtAllocateHeap in ntdll.dll. If we created an app which imports HeapAlloc and used bind.exe on the app, there would be an IMAGE_BOUND_IMPORT_DESCRIPTOR for kernel32.dll followed by an IMAGE_BOUND_FORWARDER_REF for ntdll.dll.
In the next post we talk more about the loader.