Diverting functions in Windows with IAT patching
Recently I was stuck with a blocking issue that was putting a feature I worked on in jeopardy. To summarize the problem, the entire feature relied on a specific way a library is initialized, the library in question providing two different functions (that we will call Init
and InitExtended
) to initialize itself.
The code responsible for this initialization hardcodes calling one of the variant and is in a DLL that is not modifiable. To make things harder, the two initialization functions I mentioned have slightly different parameters which complicated things as it can’t be a simple function pointer swap.
For exploitation purposes, an important aspect is that the initialization function is not actually referenced directly in the DLL but instead it is located dynamically using the standard LoadLibrary/GetProcAddress combo.
When something is using LoadLibrary
, the obvious thing to try first is to take advantage of the DLL loader lookup mechanism and provide a modified DLL that can be loaded instead of the original one. In this case though, LoadLibrary
was used with a hardcoded path in a system directory which made this option impossible.
Which leaves trying to use GetProcAddress
to masquerade things up. To do this there is a more exotic technique called IAT patching that relies on the way the PE (Portable Executable) format work for dynamic libraries.
What’s in a DLL
Like most other dynamically loadable libraries format, a DLL contains more than just the code that’s executed. The file is split between different sections that references several other type of data in addition to the code section.
For instance the data section will contain a bunch of interesting tables, things like strings literals, the constant data in the program, thread local storage and so on.
When a DLL is loaded, those tables and sections are mapped into the calling process memory space and are accessible from user code although in practice you will always be using some system API instead of querying those parts of the memory yourself.
The Import Table
The table we are going to be interested in here is the Import Address Table (abbreviated IAT). The role of this table is to list all the external functions a DLL references from other libraries in a dictionary form keyed by library name (e.g. “user32.dll”) and values as function pointer thunks.
For instance, here is a snippet of the content of this table from a random DLL (the hint/ordinal values are used by the loader for faster processing):
$ pedump -I freetype.dll
=== IMPORTS ===
MODULE_NAME HINT ORD FUNCTION_NAME
KERNEL32.dll 1cb GetCurrentThreadId
KERNEL32.dll 15b FlsSetValue
KERNEL32.dll 18c GetCommandLineA
KERNEL32.dll 425 RtlUnwindEx
KERNEL32.dll 2d3 HeapAlloc
KERNEL32.dll 208 GetLastError
KERNEL32.dll 2da HeapReAlloc
KERNEL32.dll 2d7 HeapFree
KERNEL32.dll ee EncodePointer
KERNEL32.dll 15a FlsGetValue
KERNEL32.dll 159 FlsFree
KERNEL32.dll 480 SetLastError
KERNEL32.dll 158 FlsAlloc
KERNEL32.dll 4c0 Sleep
In our case, both of LoadLibrary
and GetProcAddress
would be in this table keyed under the library they come from aka kernel32.dll.
The reason for this table is explained by the way dynamic libraries and the Windows DLL loader works. When compiling code to a DLL that depends on some other libraries, the actual memory layout where the dependencies are going to be loaded is not known in advance. Maybe that dependency has already been loaded by the program, maybe the DLL will be the one to load it, in any case the real locations for all those functions will ultimately be determined by the running context.
Thus, when some code references an external function, what the compiler will do is insert a call
instruction that is not pointing directly to the real address of the function but instead will load the address pointed to by the thunk I mentioned earlier from the Import Address Table.
By doing so, the code contained inside the DLL is always valid and the Windows loader simply has to modify one piece of the overall loaded DLL (the IAT thunk) to point to the right function address at runtime instead of going throughout the entire code section and patch one-by-one every call
instructions.
Below is an example of how the same DLL we saw earlier is calling the GetCommandLineA
Win32 function contained in kernel32. Notice how the address for the call instruction is retrieved (qword
) from a location in the data segment (ds
) and how the disassembler is smart enough to name the thunk with a imp_
prefix:
And here is a view of the actual array containing all the thunks for kernel32 functions that will be patched by the Windows loader when the DLL is processed at run-time:
Patching the Import Address Table
Reminder: we are trying to influence a program execution by manipulating GetProcAddress
so that instead of calling an Init
function it calls a different InitExtended
function.
Now that we know how external functions are called it’s easy to see that taking control of this IAT indirection is the key to do what we want. By patching the IAT thunk for the function we want to modify, we ensure the relevant code will be redirected to use our modifications.
Provided you have already the ability to load custom code in the process via another mean (if not lookup DLL injection techniques), the recipe to hijack a IAT thunk is as follow:
- Create your custom function in your own code that mimics the prototype of the real one
- Prevent or delay the codepath you are trying to change from accessing the function before your patching is done
- Retrieve a pointer to the module (containing all the tables) of the DLL/executable that you are trying to hijack
- Locate the Import Address Table
- Iterate over it to find the right thunk
- Overwrite the value given by the DLL loader with your own custom function address
Below is the translated and annotated version of this recipe in C using the imagehlp library (that defines all the right C structs to modelize the IAT table layout):
// Our custom Init function that will now internally
// call the InitExtended function we wanted to use
static void MyCustomInitFunction (/* parameters */)
{
InitExtended (/* other parameters */);
}
// Our hooked version of GetProcAddress with the
// same prototype (and same calling convention)
static FARPROC MyGetProcAddress (HMODULE hModule, LPCSTR lpProcName)
{
if (strcmp (lpProcName, "Init") == 0)
return (FARPROC)MyCustomInitFunction;
return GetProcAddress (hModule, lpProcName);
}
static BOOL HackGetProcAddress ()
{
// Get the handle to the module we want to divert
HMODULE module = GetModuleHandle (L"TheDllToHijack.dll");
if (module == NULL)
return FALSE;
// Get a reference to the import table to locate the kernel32 entry
ULONG size;
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx (module, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size, NULL);
// In the import table find the entry that corresponds to kernel32
BOOL found = FALSE;
while (importDescriptor->Characteristics && importDescriptor->Name) {
PSTR importName = (PSTR)((PBYTE)module + importDescriptor->Name);
if (_stricmp (importName, "kernel32.dll") == 0) {
found = TRUE;
break;
}
importDescriptor++;
}
if (!found)
return FALSE;
// We use this value as a comparison
PROC baseGetProcAddress = (PROC)GetProcAddress (GetModuleHandle (L"kernel32.dll"), "GetProcAddress");
// From the kernel32 import descriptor, go over its IAT thunks to
// find the one used by the rest of the code to call GetProcAddress
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((PBYTE)module + importDescriptor->FirstThunk);
while (thunk->u1.Function) {
PROC* funcStorage = (PROC*)&thunk->u1.Function;
// Found it, now let's patch it
if (*funcStorage == baseGetProcAddress) {
// Get the memory page where the info is stored
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery (funcStorage, &mbi, sizeof (MEMORY_BASIC_INFORMATION));
// Try to change the page to be writable if it's not already
if (!VirtualProtect (mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect))
return FALSE;
// Store our hook
*funcStorage = (PROC)MyGetProcAddress;
// Restore the old flag on the page
DWORD dwOldProtect;
VirtualProtect (mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
// Profit
return TRUE;
}
thunk++;
}
return FALSE;
}
Conclusion
This was an example of how you can use IAT patching to circumvent legacy code without having to recompile or otherwise damage the original binary.
This technique is well known so if you want to do something more complicated/reliable you can always use an existing library like EasyHook or something as full-featured as Microsoft’s Detours.
References: