基于异常处理的Hook函数(VEH Hook)

c++

Posted by YiMiTuMi on May 13, 2020

基于异常处理的Hook函数

向量化异常处理(Vectored Exception Handler, VEH)

结构化异常处理(Structure Exception Handler, SEH)

原理: 参考

在用户模式下发生异常时,异常处理分发函数在内部会先调用遍历 VEH 记录链表的函数, 如果没有找到可以处理异常的注册函数,再开始遍历 SEH 注册链表。 二者之间具体联系:VEH优先权高于SHE,只有所有VEH全不处理某个异常的时候,异常处理权才会到达SHE。只要目标程序中没有利用VEH,那么,你所设计的VEH将是第一个得到控制者。现在采用SEH作为异常处理的普通C/C++程序对你将不会再有干扰,可以通过使用VEH来进行hook处理了。 如果存在调试器,那么控制权转向将会发生新的变化。当异常发生后,首先通知的将会是调试器,调试器不处理才会再返回控制权给VEH;如果VEH不处理,再返回给SHE;若SEH不处理,再给调试器一个机会,如果还不处理,则交由系统处理。

VEH通过使用 Win32 API 函数 AddVectoredExceptionHandler可注册新的异常处理函数,函数的参数就是指向 EXCEPTION_POINTERS 结构的指针。同时,增加了函数地址的注册处理程序链表。由于系统中使用一个链表来存储矢量异常处理程序,程序可以安装尽可能多的向量处理器,只要有必要。

异常处理函数的初步处理步骤:

0.判断是否是int3异常
1.修改EIP为异常发生处地址(再执行一次)
2.判断异常发生地址不等于0(无意义)
3.修改堆栈的参数
4.修改寄存器上下文
5.备份CPU控制寄存器
6.CPU控制寄存器设置为单步模式
7.恢复先前备份的MessageBoxA第一个字节的代码
8.返回-1 让其继续运行

首先准备一个VectoredHandler函数:

原型:

LONG CALLBACK VectoredHandler(__in PEXCEPTION_POINTERS ExceptionInfo);

ExceptionInfo的结构:

typedef struct _EXCEPTION_POINTERS {
	PEXCEPTION_RECORD ExceptionRecord;  //记录了本次异常的相关信息
	PCONTEXT ContextRecord;				//记录了异常发生时的线程上下文 会保存寄存器等,主要修改的对象
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

typedef struct _EXCEPTION_RECORD {
	DWORD    ExceptionCode;                        //异常代码,表明了异常的相关信息
	DWORD ExceptionFlags;
	struct _EXCEPTION_RECORD *ExceptionRecord;
	PVOID ExceptionAddress;                        //发生异常时当前的eip 
	DWORD NumberParameters;
	ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

然后实现VectoredHandler函数。

最后安装VectoredHandler函数。

32位函数调用:

0012FF40    004010C4   call到MessageBoxA的地址,VEHHook.004010BE
0012FF44    00000000   hOwner = NULL
0012FF48    00407050   TEXT = "VEH Hook Test"
0012FF4C    00407104   Title = "Test"
0012FF50    00000000   Style = MB_OK|MB_APPLMODAL

int 3中断修改完数据后,会再次回到文件头进行执行,会再次执行int 3, 所以要还原MessageBoxA函数的第一个字节,如果不进行还原程序会发生死循环一直调用VectoredHandler函数,或者添加一个TrampoLine。

如果进行还原势必导致下次调用被Hook函数时,不会进行Hook,需要再次安装,解决:

1)修改Eip(Rip)使其去调用一个TrampoLine函数,使程序继续运行。(相当于InlineHook)

2)再程序修改完Hook函数后,添加一个单步中断,然后将被Hook函数我们修改的指令还原回去,此时,函数会执行正确的指令,之后触发单步中断,再次进去我们的VectoredHandler函数,在VectoredHandler函数中通过判断单步中断再次将被Hook函数的第一个字节改为int 3。

注意:程序测试的时候最好以Release模式生成exe进行测试。

通过TrampoLine函数进行的Hook

#include "stdafx.h"
#include <Windows.h>
#include <string>

using namespace std;

PVOID g_AddrofMessageBoxA; //MessageBoxA函数的地址
BYTE AddrMessageBoxA_Old; //保存之前的值
ULONG g_jmpBackAddr = 0;

_declspec(naked) int WINAPI OriginalMessageBox()
{
	_asm
	{
		//写入的int 3指令破坏了原来的指令,因此在这里执行原函数的指令
		mov edi, edi
		jmp g_jmpBackAddr  //跳回原函数被Hook指令之后的位置,绕过自己安装的Hook
	}
}

//安装Hook--int 3
void HookMessageBoxA()
{
	PBYTE AddrMessageBoxA = (PBYTE)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
	g_AddrofMessageBoxA = AddrMessageBoxA;
	//保存之前的值
	AddrMessageBoxA_Old = (BYTE)*AddrMessageBoxA;
	g_jmpBackAddr = (ULONG)g_AddrofMessageBoxA + 2; //之前指令2个字节

	//向原函数添加跳转到DetourFun的jmp
	BYTE newEntry[1] = { 0 };
	newEntry[0] = 0xcc; //int 3 的机器码 ,此处MessageBox第一行指令占2个字节最后加个nop
	//修改MessageBoxA函数开头,写入我们的 int 3
	DWORD dwOldProtect;
	MEMORY_BASIC_INFORMATION mbi = { 0 };
	VirtualQuery(AddrMessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
	VirtualProtect(mbi.BaseAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	memcpy(AddrMessageBoxA, newEntry, 1);   //写入int 3指令的机器码
	VirtualProtect(mbi.BaseAddress, 5, dwOldProtect, &dwOldProtect);
	
	
	/*
	VirtualProtect((void*)AddrMessageBoxA, MAX_PATH,dwOldProtect,&dwOldProtect);
	*(BYTE*)(AddrMessageBoxA)=0xcc;
	VirtualProtect((void*)AddrMessageBoxA, MAX_PATH,dwOldProtect,&dwOldProtect);
	*/
}


//要实现的这个VectoredHandler函数,在捕获异常后,会通过异常代码和发生异常的地址来
//判断这里是不是预先埋伏的断点
LONG WINAPI VectoredHandlerNew(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
	char szNewMsg[1024] = { 0 };
	LONG lResult = EXCEPTION_CONTINUE_SEARCH;  //在不处理该异常时默认的返回值 继续执行
	PEXCEPTION_RECORD pPexceptionRecord = ExceptionInfo->ExceptionRecord;
	PCONTEXT pContextRecord = ExceptionInfo->ContextRecord;
	int ret = 0;
	ULONG_PTR* uESP = 0;
	PVOID g_Trampoline;  
	DWORD EFlags = 0;

	g_Trampoline = pPexceptionRecord->ExceptionAddress;
	printf("进入VectoredHandlerNew函数\n");
	printf("Exception Address = %p\n", pPexceptionRecord->ExceptionAddress);
	
	//判断异常的类型和异常发生时的Eip
	if (pPexceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT && pPexceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
	{
		printf("int 3 BreakPoint Hited.\n");     //中断命中

#ifdef _WIN64

		//在x64下函数调用的前4个参数总是放在寄存器中传递,剩余的参数则压入堆栈中。
		//在x64中,前4个参数依次RCX、RDX、R8、R9 ,按照参数表声明的顺序,从左向右,前4个参数依次放入RCX,RDX,R8,R9中。
		printf("lpText = 0x%p   %s\n", pContextRecord->Rdx, (char*) pContextRecord->Rdx);

		//Rip x64体系
		pContextRecord->Rdx = (ULONG_PTR)szNewMsg;
		pContextRecord->Rip = (ULONG_PTR)g_Trampoline; //跳转到Trampoline继续执行

#else
		//86体系,会将函数参数放入栈中
		//修改参数
		printf("ESP = 0x%p\n", pContextRecord->Esp);
		uESP = (ULONG_PTR*) pContextRecord->Esp;
		lstrcpyA(szNewMsg, (LPSTR)uESP[2]); //移动2个字节是函数第二个参数
		lstrcatA(szNewMsg, "\n\n    Hacked by VectoredHandler.");
		uESP[2] = (ULONG_PTR)szNewMsg;
		pContextRecord->Eip = (ULONG_PTR)OriginalMessageBox;
		
#endif
		lResult = EXCEPTION_CONTINUE_EXECUTION;
	 }
	return lResult;
}

//安装Hook
void InstallHook()
{
	AddVectoredExceptionHandler(0, VectoredHandlerNew);
	HookMessageBoxA();
}


int _tmain(int argc, _TCHAR* argv[])
{

	MessageBoxA(NULL, "zhongchang", "Test", MB_OK);

	InstallHook();

	MessageBoxA(NULL, "zhongchang", "Test", MB_OK);
	
	MessageBoxA(NULL, "zhongchang22222", "Test", MB_OK);

	return 0;
}

通过单步中断进行的Hook(有问题)

CPU的寄存器为单步模式,也就是说CPU在运行一条汇编指令的时候会再次产生异常 而后借此机会我们将被恢复为正确代码的MessageBoxA重新改为int3 让其下次依然可以被我们拦截处理。

1.判断异常类型是否等于单步类型 2.恢复CPU控制寄存器 3.恢复MessageBoxA的代码 4.返回让其继续运行

#include "stdafx.h"
#include <Windows.h>
#include <string>

using namespace std;

PVOID g_AddrofMessageBoxA; //MessageBoxA函数的地址
BYTE AddrMessageBoxA_Old; //保存之前的值

//Hook--int 3
void HookMessageBoxA()
{
	PBYTE AddrMessageBoxA = (PBYTE)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
	g_AddrofMessageBoxA = AddrMessageBoxA;
	//保存之前的值
	AddrMessageBoxA_Old = (BYTE)*AddrMessageBoxA;

	//向原函数添加跳转到DetourFun的jmp
	BYTE newEntry[1] = { 0 };
	newEntry[0] = 0xcc; //int 3 的机器码
	//修改MessageBoxA函数开头,写入我们的 int 3
	DWORD dwOldProtect;
	MEMORY_BASIC_INFORMATION mbi = { 0 };
	VirtualQuery(AddrMessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
	VirtualProtect(mbi.BaseAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	memcpy(AddrMessageBoxA, newEntry, 1);   //写入int 3指令的机器码
	VirtualProtect(mbi.BaseAddress, 5, dwOldProtect, &dwOldProtect);
	
	
	/*
	VirtualProtect((void*)AddrMessageBoxA, MAX_PATH,dwOldProtect,&dwOldProtect);
	*(BYTE*)(AddrMessageBoxA)=0xcc;
	VirtualProtect((void*)AddrMessageBoxA, MAX_PATH,dwOldProtect,&dwOldProtect);
	*/
}

//要实现的这个VectoredHandler函数,在捕获异常后,会通过异常代码和发生异常的地址来
//判断这里是不是预先埋伏的断点
LONG WINAPI VectoredHandlerNew(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
	char szNewMsg[1024] = { 0 };
	LONG lResult = EXCEPTION_CONTINUE_SEARCH;  //在不处理该异常时默认的返回值 继续执行
	PEXCEPTION_RECORD pPexceptionRecord = ExceptionInfo->ExceptionRecord;
	PCONTEXT pContextRecord = ExceptionInfo->ContextRecord;
	int ret = 0;
	ULONG_PTR* uESP = 0;
	PVOID g_Trampoline;  
	DWORD EFlags = 0;

	g_Trampoline = pPexceptionRecord->ExceptionAddress;
	printf("进入VectoredHandlerNew函数\n");
	printf("Exception Address = %p\n", pPexceptionRecord->ExceptionAddress);
	
	//判断异常的类型和异常发生时的Eip
	if (pPexceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT && pPexceptionRecord->ExceptionAddress == g_AddrofMessageBoxA)
	{
		printf("int 3 BreakPoint Hited.\n");     //中断命中

#ifdef _WIN64

		//在x64下函数调用的前4个参数总是放在寄存器中传递,剩余的参数则压入堆栈中。
		//在x64中,前4个参数依次RCX、RDX、R8、R9 ,按照参数表声明的顺序,从左向右,前4个参数依次放入RCX,RDX,R8,R9中。
		printf("lpText = 0x%p   %s\n", pContextRecord->Rdx, (char*) pContextRecord->Rdx);

		//Rip x64体系
		pContextRecord->Rdx = (ULONG_PTR)szNewMsg;
		pContextRecord->Rip = (ULONG_PTR)g_Trampoline; //跳转到Trampoline继续执行

#else
		//86体系,会将函数参数放入栈中
		//修改参数
		printf("ESP = 0x%p\n", pContextRecord->Esp);
		uESP = (ULONG_PTR*) pContextRecord->Esp;
		lstrcpyA(szNewMsg, (LPSTR)uESP[2]); //移动2个字节是函数第二个参数
		lstrcatA(szNewMsg, "\n\n    Hacked by VectoredHandler.");
		uESP[2] = (ULONG_PTR)szNewMsg;
		pContextRecord->Eip = (ULONG_PTR)AddrMessageBoxA_Old;
		
		
		//将状态修改回来
		//设置CPU单步调试模式--即当前调用完后,下次会通过单步调试模式会再次进入我们的函数
		pContextRecord->ContextFlags = CONTEXT_CONTROL;
		EFlags = pContextRecord->EFlags;
		pContextRecord->EFlags |= 0x100;   //单步调试模式,设置标志寄存器第8位TF为1
	
		//还原MessageBoxA第一个字节
		DWORD dwOldProtect;
		MEMORY_BASIC_INFORMATION mbi = { 0 };
		VirtualQuery(g_AddrofMessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
		VirtualProtect(mbi.BaseAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		memcpy(g_AddrofMessageBoxA, &AddrMessageBoxA_Old, 1);   //写入int 3指令的机器码
		VirtualProtect(mbi.BaseAddress, 5, dwOldProtect, &dwOldProtect);
		
#endif
		lResult = EXCEPTION_CONTINUE_EXECUTION;
	}
	else if (pPexceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) //判断单步中断
   {
		printf("单步中断");
		//还原断点,标志位。
		ExceptionInfo->ContextRecord->EFlags =EFlags;

		BYTE newEntry[1] = { 0 };
		newEntry[0] = 0xcc; //int 3 的机器码

		//修改MessageBoxA函数开头,写入我们的 int 3
		DWORD dwOldProtect;
		MEMORY_BASIC_INFORMATION mbi = { 0 };

		VirtualQuery(g_AddrofMessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
		VirtualProtect(mbi.BaseAddress, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);

		memcpy(g_AddrofMessageBoxA, newEntry, 1);   //写入int 3指令的机器码

		VirtualProtect(mbi.BaseAddress, 5, dwOldProtect, &dwOldProtect);
		lResult = EXCEPTION_CONTINUE_EXECUTION;
	}

	return lResult;
}

//安装Hook
void InstallHook()
{
	AddVectoredExceptionHandler(0, VectoredHandlerNew);
	HookMessageBoxA();
}


int _tmain(int argc, _TCHAR* argv[])
{

	MessageBoxA(NULL, "zhongchang", "Test", MB_OK);

	InstallHook();

	MessageBoxA(NULL, "zhongchang", "Test", MB_OK);
	
	MessageBoxA(NULL, "zhongchang22222", "Test", MB_OK);

	return 0;
}

拦截调用(调用不下发,直接返回调用者)

设置Eip为返回地址。

注意:此时栈中还有这次调用压入的参数和返回地址,因此必须把栈还原到调用前的状态,也就是真正的MessageBoxA函数完成后的清栈和返回两个动作。

uESP = (ULONG*) pContextRecord->Esp;
PContextRecord->Eip = uESP[0];  //此时栈顶为返回地址,将其设置为新的Eip
pContexeRecord->Esp += (4 + 1)*sizeof(ULONG_PTR);//清理压入栈的4个参数和1个返回地址
lResult = EXCEPTION_CONTINUE_EXECUTION; //返回值一定是“继续执行”

注意:尽管该方法在x64下也可以正常工作,但因为在x64下的调用约定不同,所以参数不在栈里(取参数和恢复栈时都要注意这一点)。

爱丽丝 – 真的好想你