|
|
- /*
- * EAT-based hooking for x86/x64.
- *
- * Big thanks to ez (https://github.com/ezdiy/) for making this!
- *
- * Creates "hooks" by modifying the module's export address table.
- * The procedure works in three main parts:
- *
- * 1. Reading the module's PE file and getting all exported functions.
- * 2. Finding the right function to "hook" by simple address lookup
- * 3. Modify the entry to point to the hook.
- *
- * The idea is based on the fact that the export table allows forwarding imports:
- *
- * https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Forwarding
- *
- * You can also find some reference material on the same page to understand this code better.
- *
- */
-
- #pragma once
-
- #pragma warning( disable : 4267 90 )
-
- #include <windows.h>
-
- // PE format uses RVAs (Relative Virtual Addresses) to save addresses relative to the base of the module
- // More info: https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Relative_Virtual_Addressing_(RVA)
- //
- // This helper macro converts the saved RVA to a fully valid pointer to the data in the PE file
- #define RVA2PTR(t,base,rva) ((t)(((PCHAR) base) + rva))
-
- // A helper function to write into protected memory
- inline int vpmemcpy(void *dst, void *src, size_t sz)
- {
- DWORD oldp;
- // Make the memory page writeable
- if (!VirtualProtect(dst, sz, PAGE_READWRITE, &oldp))
- return 1;
- memcpy(dst, src, sz);
- // Restore the protection level
- VirtualProtect(dst, sz, oldp, &oldp);
- return 0;
- }
-
-
- /**
- * \brief Replaces the specified function entry in the EAT with a forward to a custom one, thus creating the "hook" effect.
- * \param hostDll The address of the module to hook.
- * \param originalFunction Address of the original function.
- * \param forwardFunctionEntry Name of the function to add a forward to. Must be of form `dll.API`.
- * \return TRUE, if hooking succeeded, otherwise, FALSE.
- */
- inline BOOL ezHook(HMODULE hostDll, void *originalFunction, char *forwardFunctionEntry)
- {
- /*
- * Note that we are not doing any trampoline magic or editing the assembly!
- *
- * Instead, we are reading the module's PE file, find the original function's entry in the export address table (EAT),
- * and replace it with a forward import.
- *
- * This ultimately will fool the game/executable to call our hook, while keeping original function intact.
- *
- * Thus, in order to work, the proxy DLL has to export the hook, because we are essentially
- * asking the game to call our hook without ever going to the original function (unlike with trampolines).
- */
-
- size_t fwdlen = strlen(forwardFunctionEntry);
-
- // The module always starts with a DOS (or "MZ") header
- IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)hostDll;
-
- // Next, get the NT headers. The offset to them is saved in e_lfanew
- IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
-
- // Get the pointer to the data directory of the exports
- IMAGE_DATA_DIRECTORY *edirp = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
- IMAGE_DATA_DIRECTORY edir = *edirp;
-
- // Finally, the virtual address in the data direcotry tells the location of the exports table
- IMAGE_EXPORT_DIRECTORY *exports = RVA2PTR(PIMAGE_EXPORT_DIRECTORY, mz, edir.VirtualAddress);
-
- // Read the addrress of the function list and the address of function names
- DWORD *addrs = RVA2PTR(DWORD*, mz, exports->AddressOfFunctions);
- // DWORD* names = RVA2PTR(DWORD*, mz, exports->AddressOfNames);
-
- // Iterate through all functions
- for (unsigned i = 0; i < exports->NumberOfFunctions; i++)
- {
- //char* name = RVA2PTR(char*, mz, names[i]); // Name of the exported function
- void *addr = RVA2PTR(void*, mz, addrs[i]); // Address of the exported function
-
- // Check if we have the function we need to modify
- if (addr == originalFunction)
- {
- DWORD fptr = edir.VirtualAddress + edir.Size;
- int err = 0;
-
- // Update the entry to go the the last entry (which we will populate in the next memcpy)
- err |= vpmemcpy(&addrs[i], &fptr, sizeof(fptr));
-
- // Add the forwarding import to our function at the end of the EAT
- err |= vpmemcpy(((char*)exports + edir.Size), forwardFunctionEntry, fwdlen);
-
- // Increment the size of the export data directory
- // and write the new export data directory
- edir.Size += fwdlen + 1;
- err |= vpmemcpy(edirp, &edir, sizeof(edir));
- return err == 0;
- }
- }
- return FALSE;
- }
-
-
- /**
- * \brief Hooks the given function through the Import Address Table
- * \param dll Module to hook
- * \param targetFunction Address of the target function to hook
- * \param detourFunction Address of the detour function
- * \return TRUE if successful, otherwise FALSE
- */
- inline BOOL iat_hook(HMODULE dll, char const* targetDLL, void *targetFunction, void *detourFunction)
- {
- IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)dll;
-
- IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);
-
- IMAGE_IMPORT_DESCRIPTOR *imports = RVA2PTR(PIMAGE_IMPORT_DESCRIPTOR, mz, nt->OptionalHeader.DataDirectory[
- IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
-
- for (int i = 0; imports[i].Characteristics; i++)
- {
- char *name = RVA2PTR(char*, mz, imports[i].Name);
-
- if(lstrcmpiA(name, targetDLL) != 0)
- continue;
-
- void **thunk = RVA2PTR(void**, mz, imports[i].FirstThunk);
- void **nextThunk = RVA2PTR(void**, mz, imports[i+1].FirstThunk);
-
- for (; thunk && thunk < nextThunk; thunk++)
- {
- void *import = *thunk;
-
- if (import != targetFunction)
- continue;
-
- DWORD oldState;
- if (!VirtualProtect(thunk, sizeof(void*), PAGE_READWRITE, &oldState))
- return FALSE;
-
- *thunk = (void*)detourFunction;
-
- VirtualProtect(thunk, sizeof(void*), oldState, &oldState);
-
- return TRUE;
- }
- }
-
- return FALSE;
- }
|