前言
在上两个Hook文章中,我实现了虚函数表钩子(VMT Hook) 和导入地址表(IAT)Hook 。
VMT Hook和IAT Hook的本质上都修改了内存(重定向了函数指针),所以也比较容易被检测
今天介绍一个不需要修改内存且几乎不会被检测的Hook,也就是向量化异常处理(VEH) Hook
什么是VEH?
在Windows上有着许多的异常处理方法,帮进程在遇到异常时免于崩溃。比如C++原生异常、结构化异常处理SEH(__try
,__finally
,__except
就是VC++对SEH的封装)以及本文中要使用的向量化异常处理(VEH)
Windwos中的异常处理机制,当程序执行过程中发生异常时,ring0会使用KiDispatchException
判断是否有内核调试器。如果没有调试器,ring0转交给ring3的KiDispatchException
,开始找是否有异常处理程序(SEH、VEH)。如果已安装,会调用异常处理程序进行处理
异常处理流程:异常产生->调试器->VEH->SEH->TopLevelEH->调试器
由于SEH有很多缺点(基于线程、基于栈、处理优先级低),而VEH弥补了这些缺点(基于进程、且优先级高于SEH)
而我们可以用AddVectoredExceptionHandler
函数注册自定义的VEH。当捕获到异常时,我们会拿到一个_EXCEPTION_POINTERS 类型的参数。而这个参数的成员ContextRecord
保存着异常触发时CPU各种寄存器的值,这就意味着我们可以直接修改EIP/RIP寄存器,从而达到指令跳转的目的
如何引发异常?
PAGE_GUARD :有PAGE_GUARD
标志的内存页面,如果程序尝试访问保护页内的地址,则引发STATUS_GUARD_PAGE_VIOLATION异常。且系统还清除PAGE_GUARD标志,从而删除内存页的保护页状态。
NO_ACCESS: 有NO_ACCESS
标志的内存页面,如果程序尝试访问保护页内的地址,则引发STATUS_ACCESS_VIOLATION的异常。
INT3断点:也可以使用INT3断点并捕获BREAKPOINT异常。但INT3断点需要修改指令,不够隐蔽
硬件断点:最隐蔽。但只作用于单个线程,且需要线程的句柄
这四种都能可靠的引发异常并被VEH捕获,但使用前两种异常的同时也必须使用STATUS_SINGLE_STEP(单步异常)
如果你熟悉断点的原理,那么会很容易理解。STATUS_SINGLE_STEP实际上并不是严格意义上的异常,而是一种检测CPU的EFlags寄存器中的TF(跟踪标志位)的机制。当TF为1时,CPU每次仅执行一条指令,然后就引发一个STATUS_SINGLE_STEP异常
上图所示TF
在Flags寄存器16位中的第9个bit,所以我们可以通过将ContextRecord
的EFlag
按位或上二进制000100000000也就是0x100实现
为什么需要单步异常?
Windows系统在保护模式下使用内存分页机制。而一页内存是可以物理分配的最小内存量,通常是4K大小。
当我们用VirtualProtect
在页面上设置保护标志时,不能作用于页面中某1个字节,而是只能作用于整个页面。
而绝大多数情况下,目标函数起始地址位于页面的中间。所以我们要想在这个函数起始地址触发异常,就必须使用单步异常
这样就可以一次在页中一条一条的执行指令,直到异常触发到目标函数起始地址的地址为止,然后修改EIP/RIP来跳转到我们的Hook函数
假设红色地址是目标函数的起始地址。STATUS_SINGLE_STEP允许我们一步步的到达那里。而如果执行了另一个函数,但和目标函数在同一个页中,那么STATUS_SINGLE_STEP会慢慢单步直到另一个函数结束,跳出该页,且不会到达红色地址,可以有效避免钩错函数
实现步骤
注册VEH
使用VirtualProtect
将PAGE_GUARD标志添加到目标函数的地址页
等待目标函数被调用,从而引发PAGE_GUARD_VIOLATION异常
VEH捕获异常,并跳转到Hook函数
封装
声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <stdexcept> #include <Windows.h> #ifdef _WIN64 #define IP Rip #else #define IP Eip #endif template <typename F>class veh_hook { static_assert (std ::is_function<F>::value, "veh hook require the function" ); public : veh_hook(F* original_func, F* hook_func); static void enable () ; static void disable () ; private : static bool are_in_same_page () ; static LONG WINAPI handler (EXCEPTION_POINTERS* exception_info) ; static F* m_original_func; static F* m_hook_func; static void * m_veh_handle; static DWORD m_old_protection; }; template <typename F>F* veh_hook<F>::m_original_func = nullptr ; template <typename F>F* veh_hook<F>::m_hook_func = nullptr ; template <typename F>void * veh_hook<F>::m_veh_handle = nullptr ;template <typename F>DWORD veh_hook<F>::m_old_protection = 0 ; template <typename F>veh_hook<F> make_veh_hook (F* original_func, F* hook_func) { return veh_hook<F>(original_func, hook_func); }
实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 template <typename F>veh_hook<F>::veh_hook(F* original_func, F* hook_func) { m_original_func = original_func; m_hook_func = hook_func; } template <typename F>void veh_hook<F>::enable() { if (!m_veh_handle) { if (!m_original_func || !m_hook_func) throw std ::runtime_error("Failed to enable veh hook : original/hook function can't be nullptr!" ); if (are_in_same_page()) throw std ::runtime_error("Failed to enable veh hook : two functions in the same page!" ); m_veh_handle = AddVectoredExceptionHandler(true , handler); if (!m_veh_handle) throw std ::runtime_error("Failed to enable veh hook : add veh failed!" ); if (!VirtualProtect(m_original_func, 1 , PAGE_EXECUTE_READ | PAGE_GUARD, &m_old_protection)) throw std ::runtime_error("Failed to enable veh hook : modify memory protection failed!" ); } } template <typename F>void veh_hook<F>::disable() { if (m_veh_handle) { DWORD _; if (!VirtualProtect(m_original_func, 1 , m_old_protection, &_)) throw std ::runtime_error("Failed to disable veh hook : modify memory protection failed!" ); if (!RemoveVectoredExceptionHandler(m_veh_handle)) throw std ::runtime_error("Failed to disable veh hook : remove veh failed!" ); m_veh_handle = nullptr ; } } template <typename F>bool veh_hook<F>::are_in_same_page() { MEMORY_BASIC_INFORMATION mbi1; if (!VirtualQuery(m_original_func, &mbi1, sizeof (mbi1))) return true ; MEMORY_BASIC_INFORMATION mbi2; if (!VirtualQuery(m_hook_func, &mbi2, sizeof (mbi2))) return true ; if (mbi1.BaseAddress == mbi2.BaseAddress) return true ; return false ; } template <typename F>LONG WINAPI veh_hook<F>::handler(EXCEPTION_POINTERS* exception_info) { if (exception_info->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) { if (exception_info->ContextRecord->IP == reinterpret_cast <uintptr_t >(m_original_func)) { exception_info->ContextRecord->IP = reinterpret_cast <uintptr_t >(m_hook_func); } exception_info->ContextRecord->EFlags |= 0x100 ; return EXCEPTION_CONTINUE_EXECUTION; } else if (exception_info->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) { DWORD _; VirtualProtect(m_original_func, 1 , PAGE_EXECUTE_READ | PAGE_GUARD, &_); return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; }
用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void WINAPI my_sleep (DWORD ms) { std ::cout << "[?] Hooked Sleep Function Called!" << std ::endl ; std ::cout << "Sleeping for: " << ms << std ::endl ; } int main () { std ::cout << "VEH Hook Example by AmazingPP\n" << std ::endl ; auto hooking = make_veh_hook(Sleep, my_sleep); hooking.enable(); Sleep(1000 ); hooking.disable(); Sleep(1000 ); return 0 ; }
输出:
注意:由于VEH Hook并没有修改目标函数,所以如果想在Hook函数里调用原函数需要先disable(),否则会无限循环调用
总结
VEH Hook 不需要修改内存,所以非常隐蔽,而且几乎没有反作弊引擎可以检测到它
当然有小部分反作弊引擎用VirtualQuery
检查PAGE_GUARD标志来检测VEH Hook,解决方法也很简单,先用VEH Hook把VirtualQuery
钩上,筛掉PAGE_GUARD标志即可
但缺点也很明显,实现起来比较复杂,而且执行速度非常非常慢
如果对Windows的异常处理机制有更深入的了解,甚至可以钩上异常处理ring0返回ring3后调用的第一个用户态函数,也就是ntdll里的KiUserExceptionDispatcher
,这样就可以比VEH更早的捕获到异常
引用
Vectored Exception Handling, Hooking Via Forced Exception