VehGuardHook
PUBLISHED
FILE_ID: VEHGUARDHOOK

VehGuardHook

Willian Frantz
Feb 21, 2026
PerformanceOptimizationWeb VitalsMonitoring

VehGuardHook.hpp

1#pragma once 2#define WIN32_LEAN_AND_MEAN 3#include <Windows.h> 4#include <cstdint> 5 6#ifdef _WIN64 7 #define XIP Rip 8#else 9 #define XIP Eip 10#endif 11 12namespace rubyeex { 13 14// PAGE_GUARD + VEH redirect hook. 15// Notes: 16// - This is a "best-effort" usermode hook; it is noisy (exceptions) and can be impacted by debuggers. 17// - Only supports 1 active hook instance in this minimal version. 18class VehGuardHook { 19public: 20 static bool Install(void* target, void* detour); 21 static bool Remove(); 22 static bool IsInstalled() { return s_active; } 23 24private: 25 struct HookState { 26 void* target = nullptr; 27 void* detour = nullptr; 28 void* pageBase = nullptr; 29 SIZE_T pageSize = 0; 30 DWORD origProt = 0; 31 PVOID vehHandle = nullptr; 32 }; 33 34 static HookState s; 35 36 static bool QueryPage(void* addr, void*& base, SIZE_T& size, DWORD& prot); 37 static bool IsExecutableProt(DWORD prot); 38 static LONG WINAPI Handler(EXCEPTION_POINTERS* info); 39 40 // Per-thread flag: did THIS thread just hit our guard page? 41 static DWORD s_tlsIndex; 42 static bool s_active; 43}; 44 45inline VehGuardHook::HookState VehGuardHook::s{}; 46inline DWORD VehGuardHook::s_tlsIndex = TLS_OUT_OF_INDEXES; 47inline bool VehGuardHook::s_active = false; 48 49inline bool VehGuardHook::IsExecutableProt(DWORD prot) 50{ 51 prot &= 0xFF; // strip guard/nocache/wc bits 52 return prot == PAGE_EXECUTE || 53 prot == PAGE_EXECUTE_READ || 54 prot == PAGE_EXECUTE_READWRITE || 55 prot == PAGE_EXECUTE_WRITECOPY; 56} 57 58inline bool VehGuardHook::QueryPage(void* addr, void*& base, SIZE_T& size, DWORD& prot) 59{ 60 MEMORY_BASIC_INFORMATION mbi{}; 61 if (!VirtualQuery(addr, &mbi, sizeof(mbi))) 62 return false; 63 64 if (mbi.State != MEM_COMMIT) 65 return false; 66 67 base = mbi.BaseAddress; 68 size = mbi.RegionSize; 69 prot = mbi.Protect; 70 return true; 71} 72 73inline bool VehGuardHook::Install(void* target, void* detour) 74{ 75 if (!target || !detour) 76 return false; 77 78 if (s_active) 79 return false; 80 81 void* base = nullptr; 82 SIZE_T size = 0; 83 DWORD prot = 0; 84 85 if (!QueryPage(target, base, size, prot)) 86 return false; 87 88 if (!IsExecutableProt(prot)) 89 return false; 90 91 // TLS for per-thread single-step gating 92 s_tlsIndex = TlsAlloc(); 93 if (s_tlsIndex == TLS_OUT_OF_INDEXES) 94 return false; 95 96 s.target = target; 97 s.detour = detour; 98 s.pageBase = base; 99 s.pageSize = size; 100 s.origProt = prot; 101 102 s.vehHandle = AddVectoredExceptionHandler(/*first=*/1, Handler); 103 if (!s.vehHandle) { 104 TlsFree(s_tlsIndex); 105 s_tlsIndex = TLS_OUT_OF_INDEXES; 106 s = {}; 107 return false; 108 } 109 110 DWORD oldProt = 0; 111 if (!VirtualProtect(s.pageBase, s.pageSize, s.origProt | PAGE_GUARD, &oldProt)) { 112 RemoveVectoredExceptionHandler(s.vehHandle); 113 TlsFree(s_tlsIndex); 114 s_tlsIndex = TLS_OUT_OF_INDEXES; 115 s = {}; 116 return false; 117 } 118 119 s_active = true; 120 return true; 121} 122 123inline bool VehGuardHook::Remove() 124{ 125 if (!s_active) 126 return false; 127 128 // Best-effort restore 129 DWORD tmp = 0; 130 VirtualProtect(s.pageBase, s.pageSize, s.origProt, &tmp); 131 132 if (s.vehHandle) 133 RemoveVectoredExceptionHandler(s.vehHandle); 134 135 if (s_tlsIndex != TLS_OUT_OF_INDEXES) { 136 TlsFree(s_tlsIndex); 137 s_tlsIndex = TLS_OUT_OF_INDEXES; 138 } 139 140 s = {}; 141 s_active = false; 142 return true; 143} 144 145inline LONG WINAPI VehGuardHook::Handler(EXCEPTION_POINTERS* info) 146{ 147 const auto code = info->ExceptionRecord->ExceptionCode; 148 149 if (code == STATUS_GUARD_PAGE_VIOLATION) 150 { 151 // Only handle exceptions originating from our guarded page. 152 // (Guard-page violations include the faulting address in ExceptionInformation[1] sometimes, 153 // but it's not consistently useful; using RIP/EIP check is simple and deterministic.) 154 void* ip = (void*)info->ContextRecord->XIP; 155 156 if (ip == s.target) { 157 // mark this thread so SINGLE_STEP knows it's ours 158 if (s_tlsIndex != TLS_OUT_OF_INDEXES) 159 TlsSetValue(s_tlsIndex, (LPVOID)1); 160 161 info->ContextRecord->XIP = (uintptr_t)s.detour; 162 } 163 164 // ensure we get a SINGLE_STEP after next instruction to re-arm PAGE_GUARD 165 info->ContextRecord->EFlags |= 0x100; 166 return EXCEPTION_CONTINUE_EXECUTION; 167 } 168 169 if (code == STATUS_SINGLE_STEP) 170 { 171 // Only re-guard if THIS thread previously hit our guard-page 172 if (s_active && s_tlsIndex != TLS_OUT_OF_INDEXES) { 173 if (TlsGetValue(s_tlsIndex)) { 174 TlsSetValue(s_tlsIndex, nullptr); 175 176 DWORD tmp = 0; 177 VirtualProtect(s.pageBase, s.pageSize, s.origProt | PAGE_GUARD, &tmp); 178 } 179 } 180 return EXCEPTION_CONTINUE_EXECUTION; 181 } 182 183 return EXCEPTION_CONTINUE_SEARCH; 184} 185 186} 187

example.cpp

1#include <Windows.h> 2#include <cstdio> 3#include "veh_guard_hook.hpp" 4 5static int __stdcall Target(int a, int b) 6{ 7 return a + b; 8} 9 10static int __stdcall Detour(int a, int b) 11{ 12 std::printf("Detour called! a=%d b=%d\n", a, b); 13 // You can call original only if you have another path to it (this technique redirects execution). 14 return a + b + 1000; 15} 16 17int main() 18{ 19 std::printf("Target(2,3) before hook = %d\n", Target(2,3)); 20 21 if (!rubyeex::VehGuardHook::Install((void*)&Target, (void*)&Detour)) { 22 std::printf("Hook install failed\n"); 23 return 1; 24 } 25 26 std::printf("Target(2,3) after hook = %d\n", Target(2,3)); 27 28 rubyeex::VehGuardHook::Remove(); 29 30 std::printf("Target(2,3) after remove= %d\n", Target(2,3)); 31 return 0; 32} 33
END_OF_FILE • ARTICLE_COMPLETE