RCE Endeavors 😅

May 25, 2023

Function Hooking: Import Address Table Hooks (6/7)

Filed under: Programming,Reverse Engineering — admin @ 11:44 PM

Table of Contents:

The import address table (IAT) is a table that holds pointers to functions imported from other dynamic link libraries (DLLs). When the Windows loader is loading an executable (image), this table will be filled with absolute addresses to imported functions that the image will use. This address table provides another good opportunity to place function hooks; to do so is simply a matter of overwriting the function pointer at the desired location in the import address table.

Getting the imports directory

To get the import address table, you first need to get the base of the imports directory. This imports directory will be an array of structures, with the structure containing import information on each module that is being imported. The imports directory can be obtained with a call to ImageDirectoryEntryToData, passing the IMAGE_DIRECTORY_ENTRY_IMPORT value as the directory entry parameter. The code below shows how to obtain the imports directory.

IMAGE_IMPORT_DESCRIPTOR* GetImportsDirectory(void* const moduleBaseAddress) {
    
    ULONG size{};
    auto* importsDirectoryBaseAddress{ ImageDirectoryEntryToData(
        moduleBaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size) };

    return reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(
        importsDirectoryBaseAddress);
}

Code to obtain the base address of the import address table.

Traversing the import tables

After obtaining the base of the imports directory, each import can be traversed. Each import entry contains information about the module name, and offsets into the import name table and import address table. Finding the desired function to hook is just a matter of traversing the imports directory, finding the target module, then traversing the target module’s import name and address tables for the name of the function to hook. The code below shows how to traverse the various import tables.

IMAGE_THUNK_DATA* GetIATEntryByName(void* const moduleBaseAddress,
    const std::string& targetModuleName,
    const std::string& targetFunctionName) {

    auto* importsDirectory{
        GetImportsDirectory(moduleBaseAddress) };
    if (importsDirectory == nullptr) {
        std::cerr << "Could not get base address of imports directory"
            << std::endl;
        return nullptr;
    }

    for (size_t index{}; importsDirectory[index].Characteristics != 0; index++) {

        auto moduleName{ std::string {
            RvaToPointer<char>(moduleBaseAddress,
            importsDirectory[index].Name) } };

        std::transform(moduleName.begin(), moduleName.end(), moduleName.begin(),
            [](unsigned char letter) { return std::tolower(letter); });

        // Skip modules that are not the target module
        if (moduleName != targetModuleName)
            continue;

        auto* addressTableEntry{ RvaToPointer<IMAGE_THUNK_DATA>(
            moduleBaseAddress, importsDirectory[index].FirstThunk) };
        auto* nameTableEntry{ RvaToPointer<IMAGE_THUNK_DATA>(
            moduleBaseAddress, importsDirectory[index].OriginalFirstThunk) };

        for (; nameTableEntry->u1.Function != 0;
            nameTableEntry++, addressTableEntry++) {

            // Skip functions exported by ordinal
            if (nameTableEntry->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
                continue;
            }

            auto* importedFunction{ RvaToPointer<IMAGE_IMPORT_BY_NAME>(
                moduleBaseAddress, nameTableEntry->u1.AddressOfData) };

            auto importedFunctionName{ std::string { 
                importedFunction->Name } };
            if (importedFunctionName == targetFunctionName) {
                return addressTableEntry;
            }
        }
    }

    return nullptr;
}

Traversing the import directory to find a target module and target function.

The GetIATEntryByName function implements the steps to get an import address table entry for a provided import. This function begins by obtaining a pointer to the start of the import directory. Once the import directory is found, the function iterates over all valid import directory entries. If the Name field of the current IMAGE_IMPORT_DESCRIPTOR entry matches the module that is being targeted, then the import address and import name tables are iterated over in parallel. When iterating over the import name table, the function checks to see if the imported function name matches the target function name; if so then the function returns the corresponding IMAGE_THUNK_DATA pointer that will point to the entry in the import address table. If no entries are found then the function returns a nullptr.

Installing the hook

Having obtained the import address table entry, you can now begin the process of installing the hook. The function that will be hooked in this example is the MessageBoxA function from user32.dll. The hook definition for the function is shown below:

using MessageBoxAPtr = int(__stdcall*)(HWND, LPCSTR, LPCSTR, UINT);
static MessageBoxAPtr OriginalMessageBoxA{};

int HookMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {
    return OriginalMessageBoxA(hWnd, "Hooked Hello World!", lpCaption, uType);
}

The hook function that will be called instead of MessageBoxA.

As before, this hook function will change a parameter and invoke the original function. Having defined the hook function, all that remains is to install the hook and overwrite the import address table to point to it.

template <typename OriginalFunctionPtr>
void InstallIATHook(const std::string& targetModuleName,
    const std::string& targetFunctionName, void* const hookAddress,
    OriginalFunctionPtr& originalFunction) {

    auto* moduleBaseAddress{ GetModuleHandle(nullptr) };
    auto* iatEntry{ GetIATEntryByName(moduleBaseAddress,
        targetModuleName, targetFunctionName) };
    if (iatEntry == nullptr) {
        std::cerr << std::format("Import address table entry "
            "for {}:{} not found", targetModuleName, targetFunctionName)
            << std::endl;
        return;
    }

    originalFunction = reinterpret_cast<OriginalFunctionPtr>(
        iatEntry->u1.Function);

    auto oldProtections{ ChangeMemoryPermissions(iatEntry,
        sizeof(IMAGE_THUNK_DATA), PAGE_EXECUTE_READWRITE) };
    iatEntry->u1.Function = reinterpret_cast<ULONGLONG>(hookAddress);
    ChangeMemoryPermissions(iatEntry, sizeof(IMAGE_THUNK_DATA), oldProtections);
}

int main(int argc, char* argv[]) {

    MessageBoxA(nullptr, "Hello World!", nullptr, 0);

    InstallIATHook<MessageBoxAPtr>("user32.dll", "MessageBoxA",
        HookMessageBoxA, OriginalMessageBoxA);

    MessageBoxA(nullptr, "Hello World!", nullptr, 0);

    return 0;
}

The InstallIATHook function overwrites an import address table entry for a specified module and function.

Overwriting an import address table entry is as simple as it sounds; simply write the address to your function in place of the one that is there. This is what the code above is doing: the import address table entry for the target function is retrieved, the old address is saved in originalFunction, and the address to the hook function is written in its place. After installing the hook and invoking MessageBoxA, the HookMessageBoxA function will be invoked, at which point it will change the third parameter to “Hooked Hello World!” and forward the call to the original MessageBoxA function.

Running the demo

The ImportAddressTableHook project provides the full implementation that was presented in this section. The best way to see the import address table hook in action is to watch the entry be overwritten. Begin by setting a breakpoint on the two MessageBoxA call and launching the application.

Setting breakpoints on the MessageBoxA calls.

When the first breakpoint gets hit, switch to the Disassembly window in Visual Studio. Note the address that will be called that corresponds to __imp_MessageBoxA. In the screenshot below it is 0x07FF7B8BB0248, though it will be different on your machine.

The instructions for the MessageBoxA call.

Take this address and put it into Visual Studio’s Memory window. View the memory content as 8-byte integers by right-clicking the address in the window and selecting 8-byte integer. After doing so, your window should look similar to below, though with different addresses present.

The import address entry displayed in the Memory window.

The value at address 0x07FF7B8BB0248 is shown to be 0x00007FF8FFDBA010, which corresponds to the address of the MessageBoxA function. Continue execution of the application. After dismissing the message box that pops up, the second breakpoint will get hit. In the Memory window, you should see that the address at 0x07FF7B8BB0248 has been overwritten, as shown below.

The import address table entry being overwritten.

If you follow the new address in the Disassembly window, you should see that it corresponds to the HookMessageBoxA function. After continuing execution, a new message box will pop up, this time with a different message. This shows that the import address table entry overwrite was successful and that the MessageBoxA function has been hooked.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

 

Powered by WordPress