When a PE file (.dll/.exe) needs to access functions from other modules (typically .dll files), it requires a catalog of dependencies. This catalog, known as the import table, records information such as which DLLs are used and which functions they contain. Each PE file usually contains multiple import tables corresponding to its various dependencies.
Locating the Import Table
The second entry in the PE file's data directory points to the import table. Using the VirtualAddress field from this directory, we convert the Relative Virtual Address (RVA) to a File Offset Address (FOA) to access the import table data.
Import Table Structure
The import table structure is 20 bytes in size:
struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // Unused
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
};
- OriginalFirstThunk: Points to the INT table (RVA)
- FirstThunk: Points to the IAT table (RVA), serves as backup when OriginalFirstThunk is invalid
- TimeDateStamp: Used to determine if IAT entries are bound. 0xFFFFFFFF indicates binding, 0x00000000 indicates no binding
- Name: RVA of the associated DLL name
INT Table Structure
The INT table is an array of IMAGE_THUNK_DATA structures:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // Unused
DWORD Function; // Function address
DWORD Ordinal; // Function ordinal
DWORD AddressOfData; // RVA to _IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
The _IMAGE_IMPORT_BY_NAME structure contains:
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // Export table index hint
BYTE Name[1]; // Null-terminated function name
};
Memory Loading Phases
Before Memory Load: Both IAT and INT tables contain raw data. INT stores function names while IAT remains empty.
After Memory Load: The OS populates IAT with actual function addresses via GetProcAddress().
Import Table Traversal
void AnalyzeImportTable(char *pDataBuffer) {
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pDataBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDataBuffer + pDos->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptHeader = &pNt->OptionalHeader;
PIMAGE_DATA_DIRECTORY pImportDir = pOptHeader->DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(pImportDir->VirtualAddress, pDataBuffer) + pDataBuffer);
while (pImportDesc->Name != 0) {
char *pModuleName = (char*)(RvaToFoa(pImportDesc->Name, pDataBuffer) + pDataBuffer);
printf("Module: %s\n", pModuleName);
printf("Time Stamp: %08X\n", pImportDesc->TimeDateStamp);
PIMAGE_THUNK_DATA pThunkData = (PIMAGE_THUNK_DATA)(RvaToFoa(pImportDesc->OriginalFirstThunk, pDataBuffer) + pDataBuffer);
for (int i = 0; pThunkData->u1.Ordinal != 0; i++) {
DWORD rva = pImportDesc->OriginalFirstThunk + i;
DWORD offset = RvaToFoa(rva, pDataBuffer);
if (!(pThunkData->u1.Ordinal & 0x80000000)) {
PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pThunkData->u1.AddressOfData, pDataBuffer) + pDataBuffer);
printf("Function: %s (Hint: %04X)\n", pFuncName->Name, pFuncName->Hint);
} else {
int ordinal = pThunkData->u1.Ordinal - 0x80000000;
char buffer[64];
sprintf(buffer, "Ordinal: %Xh (%d)", ordinal, ordinal);
printf("Function: %s\n", buffer);
}
pThunkData++;
}
pImportDesc++;
}
}