VEH介绍

全称 vector exception handle,向量化异常处理;

和SEH类似的东西,SEH存放于线程的栈上;

而VEH存放于进程的堆上,且是以双链表的形式,而SEH是单链表;

异常处理顺序为 : 调试器 -> VEH -> SEH;

添加VEH异常处理可以用如下API:

1
2
3
4
PVOID AddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler
);

第一个参数非0则添加到第一个处理,否则添加到末尾;

Handler是函数指针,原型如下:

1
2
3
4
LONG PvectoredExceptionHandler(
[in] _EXCEPTION_POINTERS *ExceptionInfo
)
{...}

返回值可以是0和-1,返回0代表继续处理,返回-1代表返回原本触发异常处继续执行;

其中参数是一个结构体,结构如下所示:

1
2
3
4
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

第一个参数记录的是异常信息结构体;

第二个参数保存的是异常发生时,线程处理器状态信息(寄存器环境值);

那么当异常发生时,被VEH捕获后,就可以改动寄存器的值来进行异常处理;

如下例子:

当除零时会发生异常,此时如果添加了VEH处理,可以通过更改环境值进行异常绕过,或者处理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream> 
#include <windows.h>
using namespace std;

LONG WINAPI PvectoredExceptionHandler(
_EXCEPTION_POINTERS* ExceptionInfo
)
{
cout << "触发VEH异常处理." << endl;
//跳过
ExceptionInfo->ContextRecord->Eip += 4;
return -1;
}

int main() {

AddVectoredExceptionHandler(1, PvectoredExceptionHandler);
int a = 0;
a /= 0;
printf("123\n");
return 0;
}

结果如下图所示:

result

hook原理

因为VEH处理函数可以拿寄存器,也就可以拿目标api的输入输出,只需要在目标api内触发一个异常,就可以使用ebp以及其他寄存器拿到其输入参数以及修改输出内容;

SEH HOOK原理也如此,它们的核心思想是利用了异常处理的框架,不用自己去构造;

触发断点应选择硬件断点,避免修改代码int3绕过大量检测;

缺陷是只能hook4个地址,因为硬件断点就这么多;

前置知识:

调试寄存器

register

DR0 ~ DR7

DR0 ~ DR3存放的是硬件断点的断点地址;

DR6存放的是异常信息;

DR7则是控制作用;

其中DR7里, L0-L3对应DR0-DR3的断点是否有效,局部断点;

G0-G3同上,全局断点(Windows没用);

LEN0 - LEN3 对应DR0 - DR3的断点长度,不同类型断点,长度不同,比如执行断点长度为1;

00对应1,01对应2,11对应4;

RW0 - RW3 对应断点类型,00对应执行断点,01对应写入断点,11对应读写断点;

要下断点那么就需要修改调试寄存器,如何修改呢?

1
2
3
4
BOOL SetThreadContext(
[in] HANDLE hThread,
[in] const CONTEXT *lpContext
);

用以上函数设置,自定义context结构和数值,第一个参数用GetCurrentThread来获取句柄;

设置context结构的时候要注意它有一个字段为 contextFlags,标识context哪些属性有效;

1
CONTEXT_DEBUG_REGISTERS		//表明调试寄存器有效

例子:

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
#include <iostream> 
#include <windows.h>
using namespace std;

HANDLE main_thread = 0;

LONG WINAPI PvectoredExceptionHandler(
_EXCEPTION_POINTERS* ExceptionInfo
)
{
cout << "触发VEH异常处理." << endl;
//处理对应
if (ExceptionInfo->ExceptionRecord->ExceptionAddress == MessageBoxA)
{
cout << "执行hook." << endl;
//修改字符串输出 此时刚刚进函数还没进行栈平衡
DWORD arg1Addr = ExceptionInfo->ContextRecord->Esp + 4;
DWORD arg2Addr = ExceptionInfo->ContextRecord->Esp + 8;
DWORD arg3Addr = ExceptionInfo->ContextRecord->Esp + 12;
DWORD arg4Addr = ExceptionInfo->ContextRecord->Esp + 16;
//原api执行
CONTEXT context = { 0 };
CONTEXT oldcontext = { 0 };
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SetThreadContext(main_thread, &context);
int result = MessageBoxA(*(HWND*)arg1Addr, *(LPCSTR*)arg2Addr, *(LPCSTR*)arg3Addr, *(UINT*)arg4Addr);
ExceptionInfo->ContextRecord->Eax = result;

//修改
LPCSTR re = "你是黑矮星.";
*(LPCSTR*)arg2Addr = re;
result = MessageBoxA(*(HWND*)arg1Addr, *(LPCSTR*)arg2Addr, *(LPCSTR*)arg3Addr, *(UINT*)arg4Addr);
ExceptionInfo->ContextRecord->Eax = result;

//直接返回 eip + 70 == ret
ExceptionInfo->ContextRecord->Eip += 70;
return -1;
}

return 0;
}

int main()
{
AddVectoredExceptionHandler(1, PvectoredExceptionHandler);

//hook api address
DWORD breakPoint0 = 0;
HMODULE user32 = LoadLibraryA("user32.dll");
breakPoint0 = (DWORD)GetProcAddress(user32, "MessageBoxA");

//设置断点 局部有效
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
context.Dr7 = 1;
context.Dr0 = breakPoint0;
main_thread = GetCurrentThread();
SetThreadContext(main_thread, &context);

//调用 触发断点处理异常
if (MessageBoxA(0, "我是谁?", 0, 0))
cout << "成功执行..." << endl;

return 0;
}

以上代码hook了messageBoxA这个函数,hook的时候执行了两次,一次原函数,一次修改输出后的函数,可以正确返回;