基于异常处理的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下的调用约定不同,所以参数不在栈里(取参数和恢复栈时都要注意这一点)。