VEH

VEH

0x00-VEH

VEH的中文名字为:向量化异常处理 (Vectored Exception Handling) ,是为操作系统提供的异常处理机制,类似于SEH,VEH的优先级高于SEH

应用层异常处理结构图

0x01-VEH回调函数详解

VEH由AddVectorExceptionHandler添加处理函数,处理函数有一个参数

参数类型为PEXCEPTION_POINTERS结构体

结构PEXCEPTION_POINTERS保存着当前异常的各个寄存器,堆栈,地址等多种信息

pEXCEPTION_POINTER

0x02-VEH Hook原理

1、异常处理结构中,VEH是唯一一个可以接收到所有异常信息的处理。换句话说:所有的异常信息都会经过VEH

2、异常信息通常是由数组越界、内存访问出错、无效参数、int 3等造成的

3、一旦发生异常,操作系统会立即遍历VEH,如果有处理函数,中断线程,并由处理函数处理

思路

如果我们要Hook消息框,首先要给API的首地址写入int 3断点,当执行时会产生异常,线程暂停,转交给异常处理函数处理,此时我们可以在处理函数中修改堆栈参数等

相应操作完成后将int 3(0xCC)修改回源代码 修改EIP=addr(异常地址),然后让此处的代码重新执行一次正确的代码

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
#include<Windows.h>
#include<stdio.h>
#pragma data_seg("ldata")

char trapcode[] = "*";
unsigned long long llcode = 204;//CC 00 00 00INT3
#pragma data_seg()
#pragma comment(linker,"/SECTION:ldata,RWE")
void Decshellcode()
{

llcode = 0xF333333333333333;
llcode <<= 2;
char* data = (char*)&llcode;
/*
* 解密shellcode 并且执行
* len 解密后的指令长度
* data 把解密后的指令写入data
* 比如 push ebp 就向data写入0x55
* 每次解密一条指令 不要用超过7字节的指令和跳转指令
*/
MessageBoxA(0, "不要再打辣", "停下", MB_OK);
return ;
}
long _stdcall ExceptionHandle(PEXCEPTION_POINTERS val)
{
//判断是否为int3断点
if (val->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT)
{
Decshellcode();//执行shellcode
//修改EIP
val->ContextRecord->Eip = (DWORD)&llcode;
//表示成功处理,让程序继续执行
return EXCEPTION_CONTINUE_EXECUTION;
}
//继续往下搜索异常处理函数
else
return EXCEPTION_CONTINUE_SEARCH;
}

#pragma optimize("",off)
int main()
{
//创建VEH,当第一个参数不为0,则异常处理函数是第一个要调用的处理程序。如果参数为0,则处理程序是要调用的最后一个处理程序
//第二个参数是一个异常处理函数
AddVectoredExceptionHandler(1, ExceptionHandle);
int bilibili = 2020;
int arr[2] = { 1,2 };
int ti = 23;
arr[5] = (int)&llcode;
return 0;
}
#pragma optimize("",on)

这里修改EIP为我们的Print()函数,因为函数名表示的就是函数地址,所以就是让他执行我们的Print函数

0x03-VEH实现隐藏函数调用

我们知道函数调用时,会先把call的地址压入栈中,而在函数内部的返回ret指令就是从栈中取出call指令的下一条地址

所以我们可以对栈中的地址进行修改让其调用我们的函数

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

void showflag()
{
MessageBoxA(0, "this is flag", "flag", MB_OK);
return ;
}

long _stdcall ExceptionHandler(PEXCEPTION_POINTERS val)
{
//将EIP也就是下一条执行的指令修改为ret
val->ContextRecord->Eip += 6;
//降低栈顶
val->ContextRecord->Esp -= 4;
//入栈,将ret下一条指令压入栈中
*(int*)val->ContextRecord->Esp = val->ContextRecord->Eip + 1;
//和上面一样,这次修改为我们想要执行的地址
val->ContextRecord->Esp -= 4;
*(int*)val->ContextRecord->Esp=*(int*)(val->ContextRecord->Ebp-8);
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
AddVectoredExceptionHandler(1, ExceptionHandler);
DWORD a = (DWORD)showflag;
int k = 0;
int c;
c=a / k;//构造除0异常
__asm
{
ret
}
printf("Get flag!!!");
return 0;
}

具体的可以先看汇编

触发除零异常后,PEXCEPTION_POINTERS val会接收此时的信息

我们先将EIP修改为ret指令,然后将ret下一条指令压入栈中,再把我们要调用的函数压入栈,这时候栈的结构为

返回值为EXCEPTION_CONTINUE_EXECUTION,表示继续往下执行,这时候EIP是ret指令,取出栈顶元素,跳转过去,执行完我们的shellcode后也存在ret,此时的栈为

shellcode尾部的ret取出栈顶的值,跳转过去,回到我们正常的程序

修改EIP的时候也可以

1
val->ContextRecord->Eip = (DWORD)val->ExceptionRecord->ExceptionAddress+6;

ExceptionRecord->ExceptionAddress是触发异常的地址

ExceptionRecord->ExceptionCode表示异常的类型

异常类型