学一学 hook 专题,今天是 VEH hook。

学习笔记

今天学了比较新的一个 hook 的姿势:利用异常处理去hook。

前置芝士

SEH

SEH 即 结构化异常处理(Structure Exception Handler),是Windows操作系统提供的强大异常处理功能。当程序执行过程中发生异常时,零环会使用KiDispatchException判断是否有内核调试器。如果没有调试器,零环转交给三环的KiDispatchException,开始找是否有异常处理程序(SEH、VEH)。如果已安装,会调用异常处理程序进行处理

异常处理流程:异常产生->调试器->VEH->SEH->TopLevelEH->调试器

SEH有很多缺点(基于线程、基于栈、处理优先级低)等等。

VEH

向量化异常处理(Vectored Exception Handler),我们可以用AddVectoredExceptionHandler函数注册自定义的VEH。当捕获到异常时,我们会拿到一个_EXCEPTION_POINTERS类型的参数。而这个参数的成员ContextRecord保存着异常触发时CPU各种寄存器的值,这就意味着我们可以直接修改EIP/RIP寄存器,从而达到指令跳转的目的。

VEH hook

之前用的 inline hook,IAT hook,virtual hook 以及 hotfix hook,都是通过修改内存来劫持的控制流,因为都对内存进行了直接修改所以很容易被检测到。

而在 VEH hook 中,我们可以使用硬件断点搞出一个异常来,然后转入我们的异常处理函数,这个是后话了,我们先学 int 3 断点去进行 VEH hook。

之前看 《加密与解密》 的书也讲到了,int 3就是一个断点指令,在我们打普通断点的时候,会把指令的第一个字节改为 0xCC 也就是 int 3指令的字节码,继续执行的时候会把 0xCC 替换回原指令字节。原理就是 0xCC 指令在执行的时候会引发一个 BREAKPOINT_EXCEPTION,被调试器接收到了,调试器暂时挂起了这个程序然后把当前程序的状态展示给我们看。若没有调试器,则我们需要自己写处理函数去处理这个异常。

创建一个dll

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
#include<Windows.h>

struct HOOK {
BYTE OldByte;
}HookInfo;


size_t HookAddr = (size_t)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
LONG NTAPI Handler(struct _EXCEPTION_POINTERS *ExceptionInfo) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookAddr) {

const char* str = "xia0ji233";
*(DWORD *)(ExceptionInfo->ContextRecord->Esp + 0x8) = (DWORD)str;
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
}
VOID SetHook(size_t FunctionAddr)
{
DWORD old;
VirtualProtect((LPVOID)FunctionAddr, 1, PAGE_EXECUTE_READWRITE, &old);
HookInfo.OldByte = *(char*)FunctionAddr;
*(char*)FunctionAddr = 0xCC;
VirtualProtect((LPVOID)FunctionAddr, 1, old, &old);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
SetHook(HookAddr);
AddVectoredExceptionHandler(1,(PVECTORED_EXCEPTION_HANDLER)Handler);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

在这里

  • EXCEPTION_EXECUTE_HANDLER(1):表示该异常被处理,从异常处下一条指令继续执行
  • EXCEPTION_CONTINUE_SEARCH(0):表示异常不能被处理,交给下一个SEH
  • EXCEPTION_CONTINUE_EXECUTION(-1):表示异常被忽略,从异常处继续执行

这里还有一点需要注意一下,就是 程序的指令指针 eip 需要后移两个字节,不然它会一直循环被自己断住的。

运行程序,注入,成功!

在这里需要注意一点就是,几乎每个 winapi 开头都是 mov edi,edi,这个是为了进行一个 hotfix 的。没错,是专门为了这个而生的,而且附近一定有空内存可以让我们进行修改大跳转指令的内存。