A hook on a function call via modifying an import table

hook on a function call via IATThere are situations in terms of large projects when it is necessary to correct the work of one or more third-party components (such as libraries as a part of an application). The source code is rarely available in these cases and we have to use hacker approaches. I am going to consider one of the simplest approaches to implementation of a hook on a function call via modifying an import table (IAT) in this article.

This method is based on the fact that a call from one PE (Portable Executable) module to another one is a call to an address in a connection table (IAT). The table is filled with actual function addresses by a system loader during the module initialization stage. Thus, address substitution in this table leads to calling our handler instead of the original one until the moment of completion of the process or unloading and reloading the module. In addition to that, it is important that each PE module calling library A functions has its own IAT table, and if we modify it for only one module – B – all the rest (C, D, …) will continue to call the original function. This makes the hook installation method a selective one.


So, our task is to implement a set of functions that will accept as parameters:

    Module Name (B): we're going to intercept calls from it to module (A)
    Module name (A): we are going to intercept the call of its functions
    Module (A) function name that is being imported by module (B) for interception
    Our handler’s address

Top-level function of the hook installation will return a status (successful / not successful) and the original handler’s address, since the hook procedure needs to call it in most cases.

bool PatchImportTable (LPCSTR lpszModule, // B
                       LPCSTR lpszDLL, // A
                       LPCSTR lpszEntry, // Name the imported function
                       PVOID pHook, // Our handler
                       PVOID * ppOriFunc); // Return the original address here

The first stage is definition of a module B load address, and search for an element corresponding to the function named lpszEntry of module A in the IAT table. The second stage is the actual modification of the table data. The method described in one of my previous articles (self-modifying program) is used in this case (Self-modifying programs).

The easiest way to determine the module load address is to use PSAPI function.

  EnumProcessModules
  GetModuleInformation

The first one builds a list of all modules loaded for the application. The second can actually help to get the download address of the module that interests us:

//----------------------------- Open process ------------------------------

    HANDLE hProcess = 
      OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());

    if (!hProcess)
        return false;

//------------------------ Retrieve module (B) handle ---------------------

    DWORD   cbNeeded;
    HMODULE hMods[1024];
    if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
    {
        HMODULE hModule = NULL;
        for (ULONG uModule = 0; uModule < 
            (cbNeeded / sizeof(HMODULE)); uModule++)
        {
            char szModName[MAX_PATH];

            // Get the full path to the module's file.
            if (GetModuleFileNameExA(hProcess,
                                     hMods[uModule],
                                     szModName,
                                     sizeof(szModName)))
            {
                _strlwr(szModName);
                if (strstr(szModName, lpszModule))
                {
                    hModule = hMods[uModule];
                    break;
                }
            }
        }

// -------------------- Get base address of module (B) -----------------------

        if (hModule)
        {
            MODULEINFO mi;
            memset(&mi, 0, sizeof(mi));

            BOOL f = 
              GetModuleInformation(hProcess, hModule, &mi, sizeof(mi));
            ...
        }
    }

IAT

Let’s proceed to the stage of search of the IAT table element. We are getting the PE header address:

  PIMAGE_NT_HEADERS pPEHdr; // PE header address
  pPEHdr = GetNtHeader (pImage);

and offsets / sizes of import tables and IAT (import address table):

  ULONG uDirBaseRVA = GetDirBaseRVA(pPEHdr, IMAGE_DIRECTORY_ENTRY_IMPORT);
  ULONG uDirSize    = GetDirSize   (pPEHdr, IMAGE_DIRECTORY_ENTRY_IMPORT);
  ULONG uIATDirBase = GetDirBaseRVA(pPEHdr, IMAGE_DIRECTORY_ENTRY_IAT);
  ULONG uIATDirSize = GetDirSize   (pPEHdr, IMAGE_DIRECTORY_ENTRY_IAT);

After that we find IMAGE_IMPORT_DESCRIPTOR that corresponds to library A. Then we find an import descriptor by means of enumeration search (IMAGE_IMPORT_BY_NAME) for lpszEntry function and an entrance in IAT that corresponds it:

//------------ Look for descriptor which match to module name ----------------

PIMAGE_IMPORT_DESCRIPTOR pid;       // Import directory
pid = PIMAGE_IMPORT_DESCRIPTOR(uDirBaseRVA + uBase);

for (; !pdwRet; pid++)
{
    if (!pid->OriginalFirstThunk)
        break;                      // No more descriptors

    if (!_stricmp(LPCSTR(uBase + pid->Name), pszModuleName))
    {
//---------------------- Look for specified ordinal --------------------------

        DWORD * pdwThunk = (DWORD *)(pid->OriginalFirstThunk + uBase);
        DWORD   dwIdx    = 0;

        for (; *pdwThunk; pdwThunk++, dwIdx++)
        {
            if (*pdwThunk & IMAGE_ORDINAL_FLAG)
                continue;

            PIMAGE_IMPORT_BY_NAME p = PIMAGE_IMPORT_BY_NAME(*pdwThunk + uBase);
            if (IsBadReadPtr(p, sizeof(IMAGE_IMPORT_BY_NAME)))
                break;

            if (!_stricmp((LPCSTR)p->Name, pszFunction))
            {
                pdwThunk = (DWORD *)(pid->FirstThunk + uBase) + dwIdx;

//------------- Check if Thunk is lying inside of IAT bounds -----------------

                if ((DWORD)pdwThunk >= uIATDirBase + uBase &&
                    (DWORD)pdwThunk <  uIATDirBase + uBase + uIATDirSize)
                {
                    pdwRet = pdwThunk;
                }

                break;              // Assumes, only one match
            }
        }
    }
}

If the entrance to the IAT table is found, we save the original address and set the path there for the hook.

I would like to draw your attention to the fact that the number of accepted parameters of the hook procedure and its calling convention have to be exactly the same as in the original procedure. Otherwise it will lead to an inevitable application collapse.

Conclusion

All of the above is implemented in a test application that you can download via the link at the bottom of the page. The application intercepts MessageBoxA function calls from the main application module (from EXE), and modifies call flags. As a result a message box appears always with two buttons (OK and Cancel), no matter what flags MessageBoxA is called with.

bpimp.zip

Tags:, , ,

Leave a Reply