Skip to content

Instantly share code, notes, and snippets.

@ghorsington
Last active May 3, 2024 22:14
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save ghorsington/93ea22c1f4e79e68466a26cbfc58af05 to your computer and use it in GitHub Desktop.
Save ghorsington/93ea22c1f4e79e68466a26cbfc58af05 to your computer and use it in GitHub Desktop.
EAT and IAT hook
/*
* 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
#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.
This is a simplified version that doesn't does lookup directly in the initialized IAT.
This is usable to hook system DLLs like kernel32.dll assuming the process wasn't already hooked.
* \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);
for (; thunk; 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment