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 of an 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.
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.
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.