PE Internals Part 2: Exploring PE import tables (IDT, ILT, IAT, Hint/Name tables)
- Import Directory Table (IDT)
- Import Lookup Table (ILT) and Hint/Name Table
- Import Address Table (IAT)
- Debugging time!
- References
Whenever an imported function is used in our PE executable, the PE loader will have to somehow resolve and store the address of that function in the Import Address Table (IAT), for later reference. Other tables participate in the process, such as the Import Lookup Table (ILT) and the Import Directory Table (IDT).
Let’s dive a bit into PE imports!
Import Directory Table (IDT)
Import Directory Table (IDT) is located at the beginning of the PE Import Data Section (commonly known as .idata) and can be summarized as an array of structs of type “IMAGE_IMPORT_DESCRIPTOR” ending in 0 (NULL-Padded). The IMAGE_IMPORT_DESCRIPTOR struct is defined as:
1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR { //Import Directory Table
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
- OriginalFirstThunk: Pointer (RVA) to Import Lookup Table (ILT).
- TimeDateStamp: A value used on Bound Imports. If it is 0 , no binding in imported DLL has happened. In newer days, it sets to 0xFFFFFFFF to describe that binding occurred.
- ForwarderChain: In the old version of binding, it refers to the first forwarder chain of the API. It can be set 0xFFFFFFFF to describe no forwarder.
- Name: Pointer to a string containing the DLL name.
- FirstThunk: Pointer (RVA) to Import Address Table (IAT).
As we can see, the IDT points to both the Import Lookup Table (ILT) and the Import Address Table (IAT), through the OriginalFirstThunk and FirstThunk pointers, respectively. The “Name” field points to the name of the imported DLL, which means that we will have an “IMAGE_IMPORT_DESCRIPTOR” entry for each DLL imported in our code.
Using “PE Bear”, we can load a PE executable as example and go to “Imports” section to see exactly these fields mapped on IDT:
As mentioned, we have an IDT entry for each DLL imported on the PE executable.
Import Lookup Table (ILT) and Hint/Name Table
You can already imagine that when importing a DLL, most of the time we are not interested in all the exported functions but in some specific functions. To solve this, Import Lookup Table (ILT) (also known as “Import Name Table”) plays an important role, as this table contains references for names of functions/ordinals actually used in our code for a given imported DLL.
Each ILT table entry is a 64-bit number (or 32-bit number in case of 32-bit binaries) that can contain RVAs for a Hint/Name table (if ordinal flag is false) or an ordinal number (if ordinal flag is true).
Considering a PE32+ executable (64-bit), each ILT entry is summarized as:
- If the high bit is set (bit 63, also known as “ordinal flag”), the bottom 63 bits (0 to 62) is treated as an ordinal function number.
- If the high-bit is not set (i.e. ordinal flag is false), the whole entry is an RVA to the Hint/Name table.
And what is a Hint/Name table?
The Hint/Name table is defined through the struct _IMAGE_IMPORT_BY_NAME in winnt.h as follows:
1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME { //HintName Table
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
As shown above, this struct has only two fields: Hint and Name (and that’s why it’s called Hint/Name table 🙂). Each function imported into our executable will have a pair of (Hint, Name) in the Hint/Name table, with “Hint” being an ordinal (index) and “Name” a string representing the function name. If the loader is not able to identify the function’s RVA in the Export Address Table (I have a post about the Export Address Table and its lookup process here) through the ordinal in “Hint”, the string from the “Name” field will be considered for the EAT lookup process.
In a graphical view, this is how an ILT relates to a Hint/Name table in a PE:
Import Address Table (IAT)
As discussed earlier, the IDT also points, via the FirstThunk pointer, to the Import Address Table (IAT). The IAT has the main purpose of providing the executable PE with the actual address of the imported functions.
When on disk, the IAT is identical to the ILT, that is, it points to the Hint/Name table. However, in memory, PE loader will overwrite the IAT entries with the actual addresses of the imported functions, resolved from the DLLs Export Address Table (EAT).
So, the “bigger picture” on disk will be like:
And then, in memory, after PE loader fills the IAT:
Debugging time!
A good learning exercise is to load an executable PE on x64dbg and see how the IAT is filled at runtime. First of all, let’s set a breakpoint on “DLL load”, so we can track the initial state of the IAT:
And then load “notepad.exe” on x64dbg:
As expected, a breakpoint will be reached just before the imported DLLs are loaded. Below, a breakpoint was reached just before loading kernel32.dll, which is a DLL imported by “notepad.exe” process:
If we move to the .text of notepad.exe and look at the function calls at this point, we won’t see references to the function names as usual. The call at 00007FF7BB491530 actually points to a memory address (00007FF7BB4B6AF0) containing the “02DCAA” RVA.
If we follow “00007FF7BB4B6AF0” address on dump, we will actually get the Import Address Table mapped in memory, having RVAs to the Hint/Name Table:
The Hint/Name table can be confirmed if we go to 00007FF7BB490000 (notepad.exe base address - VA) + 02DCAA (RVA) = 00007FF7BB4BDCAA:
Clearly, we see the Hint/Name table contents and the function name “InitializeCriticalSectionEx” to be resolved by the PE loader from the Export Address Table of kernel32.dll:
Therefore, upon resuming execution of notepad.exe, the IAT is expected to be filled with the actual address of kernel32.InitializeCriticalSectionEx() function.
Resuming execution and going through all the breakpoints, we see that this is confirmed:
And now IAT points to the actual address of kernel32.InitializeCriticalSectionEx(), which is 00007FFD83D349F0:
Jumping to 00007FFD83D349F0, we see the actual implementation of this function on kernel32.dll: