Import Table in PE Files

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++;
    }
}

Tags: PEFiles ImportTable IAT int RVAConversion

Posted on Fri, 06 Nov 2026 16:41:07 +0000 by cage