#ifndef EASYHOOK_HPP #define EASYHOOK_HPP #include "main.h" /* This macro creates the type, an instance of the type, and a prologue object specific to that function */ #define HOOKINIT(functor_type, function, trampoline_name, prologue_name) \ using functor_type = decltype(&function); \ functor_type trampoline_name = NULL; \ EasyHook::prologue prologue_name; namespace EasyHook { /* I googled this... it's supposedly 'equivalent' to __attribute__((packed)) in GCC */ # define PACKED(s) __pragma(pack(push, 1)) s __pragma(pack(pop)) # define FUNCTOR(function) decltype(&function) union prologue { PACKED(struct { CHAR jmp; ULONG addr; }) parts; CHAR bytes[sizeof(ULONGLONG)]; ULONGLONG full = 0; }; enum opcode { LONGJUMP_SIZE = 0x05, RELJUMP = 0xe9, NOP = 0x90, }; // TODO: Hook64 at some distant future time when I become smart enough to learn about 64-bit functions... class Hook32 { static const SIZE_T jumpSize = 5; public: Hook32() {}; LPVOID hook(LPVOID target, prologue &original, LPVOID hook) const { LPVOID trampoline = NULL; DWORD oldProtection = 0; if (jumpSize < 5) return 0; // minimum jump size of 5. Necessary for 32-bit programs if (!VirtualProtect((LPVOID)target, 1, PAGE_EXECUTE_READWRITE, &oldProtection)) return NULL; if (!(trampoline = VirtualAlloc(NULL, 1, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE))) return VirtualProtect((LPVOID)target, 1, oldProtection, &oldProtection), NULL; // set up the trampoline first so by the time the atomic function is called, it's already set up. memcpy(trampoline, target, jumpSize); // save the first 5 bytes of the original function in the trampoline *((PCHAR)((ULONG)trampoline + jumpSize)) = opcode::RELJUMP; // relative jump on the 6th byte... *((PULONG)((ULONG)trampoline + jumpSize + 1)) = (ULONG)target - (ULONG)trampoline - opcode::LONGJUMP_SIZE; // return back to the target memcpy(&original, target, sizeof original); // save the full original first 8 bytes from the function in a member variable prologue tmp = original; // copy the original 8 bytes so we don't overwrite anything important. tmp.parts.jmp = opcode::RELJUMP; // set the first byte to a relative jmp *(PULONG)(tmp.bytes + 1) = (ULONG)hook - (ULONG)target - opcode::LONGJUMP_SIZE; // jmp to the trampoline InterlockedExchange64((PLONGLONG)target, tmp.full); // atomically exchange the first 8 bytes of the original instruction VirtualProtect((LPVOID)target, 1, oldProtection, &oldProtection); // reset the target permissions return trampoline; } bool unhook(LPVOID trampoline, prologue original) const { DWORD oldProtection = 0; prologue origFunc; // a place to restore the original function to retrieve the address memcpy(&origFunc, (LPVOID)((ULONG)trampoline + jumpSize), sizeof origFunc); // get the 8 bytes from the trampoline PULONG target = (PULONG)(origFunc.parts.addr + opcode::LONGJUMP_SIZE + (ULONG)trampoline); // get the address and offset it to get the original if (!VirtualProtect((LPVOID)target, 1, PAGE_EXECUTE_READWRITE, &oldProtection)) // give RWX permissions to the target function return false; InterlockedExchange64((PLONGLONG)target, original.full); // replace the 8 bytes of the original function with the original bytes. VirtualProtect((LPVOID)target, 1, oldProtection, &oldProtection); // restore the original function permissions VirtualFree(trampoline, 0, MEM_RELEASE); // release the memory return true; } }; } #endif