|
/*
|
|
* 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;
|
|
}
|