Import Table

When you run a Portable Executable (PE), before the execution of our program, libraries will be loaded in memory and their function addresses will be referenced in the Import Address Table (IAT). When a program calls a function from a DLL, it calls the address located in the IAT wich point to the function in memory.

diagram of a normal api call :

diagram IAT hooked and not hooked

diagram of an hooked api call :

HOOKED API CALL

We want to change the address of this API call in the IAT to execute an non intended function. This technique can be useful for reverse engineer to change the result of a function or dump his parameter (for example if you have a runPE you can hook the WriteProcessMemory function and easily dump the PE.) and it can be useful for malware devellopers to hide some malicious code.

Import table structure

You can learn more about the import table here.

The import table is a null terminated array a null terminated array of IMAGE_IMPORT_DESCRIPTOR structure.

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    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;

Each dll in the Import Table as an IMAGE_IMPORT_DESCRIPTOR structure which contains information about it like its name, its functions, etc.

Here we will focus only on interesting things for us.

  • DWORD Name : RVA of an ASCII string that contains the name of the module (dll name)

  • DWORD OriginalFirstThunk : RVA of the Import Lookup Table (ILT)

  • DWORD FirstThunk : RVA of the Import Address Table (IAT)

Relative Virtual Address (RVA) means Relative to the base address.

Example : dll_name = PE_Base_Address + IMAGE_IMPORT_DESCRIPTOR.Name

OriginalFirstThunk and FirstThunk

The IAT and ILT are parallel’s NULL terminated arrays.

IAT and ILT

Their structure is defined as it :

typedef struct _IMAGE_THUNK_DATA {
    union {
        uint32_t* Function;             // address of imported function
        uint32_t  Ordinal;              // ordinal value of function
        PIMAGE_IMPORT_BY_NAME AddressOfData;        // RVA of imported name
        DWORD ForwarderStringl              // RVA to forwarder string
    } u1;
} IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;

Import Lookup Table

The ILT contain names and ordinals for each API call.

if the first bit of AddressOfData is 1, this DWORD is the ordinal of the function to import.

Else, the AddressOfData point to a IMAGE_IMPORT_BY_NAME structure which contains the API call name and looks like this :

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
    /*The BYTE designated at Name of course only marks the beginning 
    of the character array of the imported function name as the name 
    can be larger than one character.*/
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

Import Address Table

The IAT contain virtual address of these API calls.

Parsing the import table

Now, we have to parse our PE to find it’s Import Table. To do that we will use the winnt.h library in C.

The following diagram illustrate the simplified structure of a PE.

PE IAT format

We first have to retrieve the base address of our PE. To do it we can call the GetModuleHandle() API call.

GetModuleHandle(NULL)

Now, like my previous article about API Windows Hashing, we will parse the PE.

HANDLE PE_base = GetModuleHandle(NULL);
IMAGE_DOS_HEADER* p_DOS_HDR  = (IMAGE_DOS_HEADER*) PE_base; // convert your data into an IMAGE_DOS_HEADER* type defined in winnt.h
IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) ((LPBYTE)PE_base + p_DOS_HDR->e_lfanew ); // NT HEADERS are located at the raw offset defined in the e_lfanew header
// RVA of the import table can be found in the optional headers
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only
// https://0xrick.github.io/win-internals/pe6/#import-directory-table
IMAGE_IMPORT_DESCRIPTOR* import_table = (IMAGE_IMPORT_DESCRIPTOR*)((LPBYTE)PE_base + p_NT_HDR->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

Here we retrieve the address of the first IMAGE_IMPORT_DESCRIPTOR array.

we can easily iterate trought the null terminated array using a while loop :

while (import_table->Name != 0) {
    // print each dll loaded in the import table
    printf("%s\n", (char*)((LPBYTE)PE_base + import_table->Name));
}

Now, we want for each DLLs to retrieve their functions name and address. We first have to find our ILT and IAT and iterate throught it.

IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->OriginalFirstThunk);
IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->FirstThunk);

// it will print all function name of the module.
while(lookup_table->u1.AddressOfData != 0) {
    // check if its an import by ordinal
    if((lookup_table->u1.AddressOfData & IMAGE_ORDINAL_FLAG) == 0) {
        // retrieve the struct wich contains the function name
        IMAGE_IMPORT_BY_NAME* lookup_addr = (IMAGE_IMPORT_BY_NAME*)((LPBYTE)PE_base + lookup_table->u1.AddressOfData);
        printf("%s\n", (char*)lookup_addr->Name);
    }
    lookup_table++;
    address_table++;
}

Now we can print a function’s address from its name, like MessageBoxA as below:

// we want to print the address of the MessageBoxA API call.
if(strcmp(lookup_addr->Name, "MessageBoxA") == 0) {
    printf("%p\n", address_table->u1.Function); // print the function address
}

Okay, now we have the address of the API call we want to hook.

The complete function to print it looks like this :

void print_address_by_parsing_IAT(HMODULE PE_base, char* function_to_hook) {
    IMAGE_DOS_HEADER* p_DOS_HDR  = (IMAGE_DOS_HEADER*) PE_base; 
    IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) ((LPBYTE)PE_base + p_DOS_HDR->e_lfanew ); 
    IMAGE_IMPORT_DESCRIPTOR* import_table = (IMAGE_IMPORT_DESCRIPTOR*)((LPBYTE)PE_base + p_NT_HDR->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    // iterate throught the null terminated modules array
    while (import_table->Name != 0) {
        // retrieve ILT and IAT
        IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->OriginalFirstThunk);
        IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->FirstThunk);
        
        // iterate throught IAT and ILT
        while(lookup_table->u1.AddressOfData != 0) {
            // check if the function is called by ordinal
            if((lookup_table->u1.AddressOfData & IMAGE_ORDINAL_FLAG) == 0) { 
                // if it is not retrieve its IMAGE_IMPORT_BY_NAME structure
                IMAGE_IMPORT_BY_NAME* lookup_addr = (IMAGE_IMPORT_BY_NAME*)((LPBYTE)PE_base + lookup_table->u1.AddressOfData);
                
                // compare the function name with the function name to hook
                if(strcmp(lookup_addr->Name, function_to_hook) == 0) {
                    printf("%p\n", address_table->u1.Function);
                }
            
            }
            // next element of the IAT and the ILT
            lookup_table++;
            address_table++;
        }
        // next module
        import_table++;
    }
}

So, this function will just print the API call address. Now we have to change it.

Hooking the API call

Lets make a fake MessageBox :

INT WINAPI fake_messagebox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType){
    printf("I hooked your function :)\n");
    printf("you wanted to display %s\n", lpText);
    return 0;
}

Instead of printing the MessageBoxA address in the IAT, we will overwrite it by fake_messagebox address and save the real MessageBox address into an other variable.

address_table->u1.Function = (ULONGLONG)fake_messagebox;

Our final code look like this.

#include <stdio.h>
#include <windows.h>

typedef int(WINAPI* MESSAGE_BOX_TYPE)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
MESSAGE_BOX_TYPE MessageBoxSaved;

INT WINAPI fake_messagebox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType){
    printf("I hooked your function :)\n");
    printf("you wanted to display %s\n", lpText);
    return MessageBoxSaved(hWnd, lpText, lpCaption, uType);
}

void hook_IAT(HMODULE PE_base, char* function_to_hook) {
    IMAGE_DOS_HEADER* p_DOS_HDR  = (IMAGE_DOS_HEADER*) PE_base; 
    IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) ((LPBYTE)PE_base + p_DOS_HDR->e_lfanew ); 
    IMAGE_IMPORT_DESCRIPTOR* import_table = (IMAGE_IMPORT_DESCRIPTOR*)((LPBYTE)PE_base + p_NT_HDR->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    // iterate throught the null terminated modules array
    while (import_table->Name != 0) {
        // retrieve ILT and IAT
        IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->OriginalFirstThunk);
        IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*)((LPBYTE)PE_base + import_table->FirstThunk);
        
        // iterate throught IAT and ILT
        while(lookup_table->u1.AddressOfData != 0) {
            // check if the function is called by ordinal
            if((lookup_table->u1.AddressOfData & IMAGE_ORDINAL_FLAG) == 0) { 
                // if it is not retrieve its IMAGE_IMPORT_BY_NAME structure
                IMAGE_IMPORT_BY_NAME* lookup_addr = (IMAGE_IMPORT_BY_NAME*)((LPBYTE)PE_base + lookup_table->u1.AddressOfData);
                
                // compare the function name with the function name to hook
                if(strcmp(lookup_addr->Name, function_to_hook) == 0) {
                    // change its address
                    address_table->u1.Function = (ULONGLONG)fake_messagebox;
                }
            
            }
            // next element of the IAT and the ILT
            lookup_table++;
            address_table++;
        }
        // next module
        import_table++;
    }
}


int main(void) {
    // save MessageBox function
    MessageBoxSaved = (MESSAGE_BOX_TYPE)GetProcAddress(LoadLibrary("user32.dll"), "MessageBoxA");
    // hook the function
    hook_IAT(GetModuleHandle(NULL), "MessageBoxA");
    MessageBoxA(NULL, "hello world", "simple message box", MB_OK);
}
/* output :
I hooked your function :)
you wanted to display hello world
*/

To go further

you can hook any function present in the IAT of an other process using DLL injection to execute some code which will patch the import table. It’s a bit more difficult but it can be interesting for game hacking or reverse engineering.

Issue

This technic is useful, but it can be bypassed if the program retrieve its API call address from the Export Table using the GetProcAddress() function.

Conclusion

This example was basic, but I think with it you can make lot of funny things.

I hope you enjoyed to read it, have a nice day.