Import Address Table Hooking

Date: 2026-06-13

Tags: #Coding

Introduction

Every windows exe has an Import Adress Table (IAT) that contains pointers to functions from required dlls. For example The function MessageBoxA is being imported from user32.dll

We can modify this Table to point functions to our own, so we can intercept or replace the functions.

IAT (Import Address Table) Hooking


void* IATHook(const char* moduleName, const char* functionName, void* hkFunc)
{
    // Get Base address of current proccess.
    void* base = reinterpret_cast(GetModuleHandleA(nullptr));

    IMAGE_DOS_HEADER* dos_header = reinterpret_cast(base);
    IMAGE_NT_HEADERS* ntHeaders = reinterpret_cast(
        reinterpret_cast(base) + dos_header->e_lfanew);

    IMAGE_DATA_DIRECTORY data_dir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    IMAGE_IMPORT_DESCRIPTOR* import_descriptor = reinterpret_cast(reinterpret_cast(base) + data_dir.VirtualAddress);

    // Loop until last module -> Name is gonna be Null.
    while (import_descriptor->Name)
    {
        // Check if we are at desired module.
        if (_stricmp(moduleName, reinterpret_cast(reinterpret_cast(base) + import_descriptor->Name)) == 0)
        {
            IMAGE_THUNK_DATA* original_thunk = reinterpret_cast(reinterpret_cast(base) + import_descriptor->OriginalFirstThunk);
            IMAGE_THUNK_DATA* first_thunk = reinterpret_cast(reinterpret_cast(base) + import_descriptor->FirstThunk);

            // Loop until no more imported function of module.
            while (original_thunk->u1.AddressOfData)
            {
                // Exculde imported functions just by ordinals, since we comapring names.
                if (original_thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
                {
                    original_thunk++;
                    first_thunk++;
                    continue;
                }

                IMAGE_IMPORT_BY_NAME* import_data = reinterpret_cast(reinterpret_cast(base) + original_thunk->u1.AddressOfData);

                if (_stricmp(functionName, import_data->Name) == 0)
                {
                    // Adjust protection so we can write.

                    DWORD old_protection = 0;
                    VirtualProtect(&first_thunk->u1.Function, sizeof(void*), PAGE_READWRITE, &old_protection);

                    void* old_function = reinterpret_cast(first_thunk->u1.Function); // Save Original pointer.
                    first_thunk->u1.Function = reinterpret_cast(hkFunc); // Set our own function.

                    VirtualProtect(&first_thunk->u1.Function, sizeof(void*), old_protection, &old_protection);

                    // Not necessery but recommended apperently.
                    FlushInstructionCache(GetCurrentProcess(), nullptr, 0);

                    return old_function;
                }

                original_thunk++;
                first_thunk++;
            }
        }

        import_descriptor++;
    }

    return nullptr;
}

In the first part of the hooking function you get the basic PE Data and the Import descriptor. With this you can get the name of the module which the import belongs to, for example MessageBoxA -> user32.dll

To traverse the IAT we can simply increment the descriptor and the thunks (like shown in the loop). Now here "original_thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG" since we are comparing functions by name we want to exclude ordinals.

The real hook is "first_thunk->u1.Function = reinterpret_cast(hkFunc);". Here you just change the pointer of the import to your own function and save the original so you can still use it.