Import Address Table Hooking

Import Address Table hooking is a method to redirect execution of a function.

I’ve previously covered inline hooking, so be sure to read that as well.

PE format

Portable Executable is the file format used on Windows for executables, libraries, and certainly more things I can’t remember right now.

It’s an SVG, so open it in a new page and zoom in if needed.

This format contains all the information Windows needs to successfully load an image (an executable) into memory.

Import Address Table

On Windows, our software can make use of functions that are part of a shared library (dynamic link library).

The advantage of dynamic libraries is that they can be loaded into memory only once, saving memory.

However, when mapping these libraries onto a process, the same address can not always be used. This is where the Import Address Table comes in.

You can see the IAT on the image above close to the bottom left.

The IAT is a lookup table, and when loading an image into memory, the loader will fill in the IAT with the correct addresses.

Therefore, instead of the program using addresses to call a function directly, it will call function printf on the IAT and it will perform an indirect call.

Describing the the process of import resolution in great detail is not my intention here, but please read the following article to learn more

If we overwrite the function pointers on the IAT with a pointer to our own function, we can redirect execution whenever the original function is called.

Viewing imports

Here is some example code.

1#include <Windows.h>
3void a() {}
5int main()
7    MessageBox(nullptr, L"Hello, world", L"Hello, world", MB_OK);

Yes, I am aware of how creative I am.

Visualizing the imports on PE-bear, I can see MessageBoxW is imported from USER32.dll.

You can also notice that libraries from dynamic libraries and functions that parte part of the program are called differently.

 1_main   PROC
 2        push    ebp
 3        mov     ebp, esp
 4        push    0
 5        push    OFFSET $SG89621
 6        push    OFFSET $SG89622
 7        push    0
 8        call    DWORD PTR __imp__MessageBoxW@16
 9        call    void a(void)                         ; a
10        xor     eax, eax
11        pop     ebp
12        ret     0
13_main   ENDP


We will hook a process IAT by injecting a DLL onto it. After injecting, we will parse the PE header to find our target function in the IAT, and then overwrite the address with our own function.

We need a way to retrieve the original function, so let’s save it before we do any hooking and then have our new function call the old one with modified parameters.

 1typedef int(__stdcall *MessageBoxFuncPtr)(
 2    HWND    hWnd,
 3    LPCTSTR lpText,
 4    LPCTSTR lpCaption,
 5    UINT    uType
 8MessageBoxFuncPtr MessageBoxOriginal = MessageBoxW;
10int __stdcall MessageBoxNew(HWND    hWnd,
11    LPCTSTR lpText,
12    LPCTSTR lpCaption,
13    UINT    uType
16    return MessageBoxOriginal(hWnd, L"Hooked", L"Hooked", uType);

Parsing the PE is not the easiest thing in the world since it’s a complex format, but it can be done with a little bit of patience.

The best way to learn is to look at the above picture while either reading the code or looking through it manually on an hex editor. We will need the base address of the module before anything, which can be obtained using GetModuleHandle.

At the base address we have the DOS header, and we can find the PE header by following e_lfanew.

1auto dosHeader = (PIMAGE_DOS_HEADER)baseAddress;
2auto peHeader = (PIMAGE_NT_HEADERS)(baseAddress + dosHeader->e_lfanew);

Next we need the optional header and the Import directory:

1auto optionalHeader = peHeader->OptionalHeader;
2auto importDirectory = optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

We get the address of the import descriptor by adding the RVA to the base address:

1auto descriptorRVA = importDirectory.VirtualAddress;
2auto importDescriptor = baseAddress + descriptorRVA;

Next we will iterate over the import descriptor until we find our DLL.

Then, we will iterate over each entry in originalFirstThunk, check whether the function is imported by name, and if it matches the function we want. We look at its address on firstThunk at the same position, change the memory protection so we can write to it, write the address of our new function, and then restore the old protection.

From then on, each call to MessageBoxW will call our hooked function.

 1while (importDescriptor->Name != NULL) {
 2    auto dllName = baseAddress + importDescriptor->Name;
 3    if (!strcmp((const char*)dllName, "USER32.dll")) {
 4        auto firstThunk = (PIMAGE_THUNK_DATA)(baseAddress + importDescriptor->FirstThunk);
 5        auto originalFirstThunk = (PIMAGE_THUNK_DATA)(baseAddress + importDescriptor->OriginalFirstThunk);
 6        while (originalFirstThunk->u1.AddressOfData != 0) {
 7            if (originalFirstThunk->u1.Ordinal == IMAGE_ORDINAL_FLAG) {
 8                originalFirstThunk++;
 9                firstThunk++;
10                continue;
11            }
12            auto function = (PIMAGE_IMPORT_BY_NAME)(baseAddress + originalFirstThunk->u1.AddressOfData);
13            if (!strcmp(function->Name, "MessageBoxW")) {
14                DWORD oldProtect;
15                VirtualProtect((LPVOID)&(firstThunk->u1.Function), 4, PAGE_READWRITE, &oldProtect);
16                firstThunk->u1.Function = (DWORD)MessageBoxNew;
17                VirtualProtect((LPVOID)(&firstThunk->u1.Function), 4, oldProtect, &oldProtect);
18            }
19            originalFirstThunk++;
20            firstThunk++;
21        }
22    }
23    importDescriptor++;

Calling the function now:

1MessageBoxW(nullptr, L"Hello world", L"Hello world", MB_OK);

Results in our hooked function being called:

Built with Hugo
Theme Stack designed by Jimmy