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.