异常处理
1.结构化异常SEH
#include <iostream>
int main()
{
goto Exit;
__try {
//受保护节
int a = 0;
int b = 0;
int c = a / b;
std::cout << "触发异常" << std::endl;
}
/*
EXCEPTION_CONTINUE_EXECUTION (-1) 异常已消除。 从出现异常的点继续执行。
EXCEPTION_CONTINUE_SEARCH (0) 无法识别异常。 继续向上搜索堆栈查找处理程序,首先是所在的 try-except 语句,然后是具有下一个最高优先级的处理程序。
EXCEPTION_EXECUTE_HANDLER (1) 异常可识别
*/
//__except (1){
// std::cout << "SEH 异常" << std::endl;
//}
__finally {
//终止处理程序,如果程序异常退出,将会执行
std::cout << "异常退出" << std::endl;
}
std::cout << "程序即将退出" << std::endl;
Exit:
std::cout << "正常退出" << std::endl;
}
2.向量化异常VEH:
_EXCEPTION_POINTERS:包含一个异常记录,其中包含异常的计算机独立描述,以及一个上下文记录。
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
ExceptionRecord:
其中,ExceptionRecord指向了有关异常说明的PEXCEPTION_RECORD结构体指针:
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
其中,Exception:发生异常的原因,这是由硬件异常生成的代码,常见错误都有宏
ExceptionFlags:异常标志
ExceptionRecord:指向相关联的指针,这是一个链状结构
ExceptionAddress:发生异常的地址
NumberParameters:与一场关联的参数的个数
ExceptionInformation:描述异常的其他参数的数组
ContextRecord:
其中ContextRecord指向了发生异常时,处理器状态的说明(上下文)
#include <iostream>
#include <Windows.h>
LONG MyVEHCallBack(
struct _EXCEPTION_POINTERS *ExceptionInfo
) {
//向量化异常需要我们自己选择处理方式,以及是否回去
//异常代码
DWORD dwCode = ExceptionInfo->ExceptionRecord->ExceptionCode;
//判断接收到的是什么异常
if (dwCode == EXCEPTION_BREAKPOINT) {
//异常处理
std::cout << "This is Int3" << std::endl;
system("pause");
//我们这里想处理异常后,回去,继续执行后面的代码
//EIP 指令指针寄存器,保存了下一行要执行的位置
//我们将EIP+1,就会执行后面的代码了
ExceptionInfo->ContextRecord->Eip += 1;
//返回,继续执行
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_EXECUTION;
return 0;
}
int main()
{
/*
//线程上下文获取
CONTEXT ctx;
GetThreadContext(GetCurrentThread(), &ctx);
//设置线程上下文
ctx.Dr0 = 111;
SetThreadContext(GetCurrentThread(), &ctx);
*/
//注册向量异常处理程序
AddVectoredExceptionHandler(
0, //调用处理程序的顺序,程序第一个处理异常还是最后处理异常
(PVECTORED_EXCEPTION_HANDLER)MyVEHCallBack //指向要调用处理程序的指针,实际上就是我们定义的处理异常函数
);
//这里我们使用汇编,触发一个int3断点
//int3 软件断点 0xCC
_asm {
int 3
}
std::cout << "异常之后的代码" << std::endl;
system("pause");
return 0;
}
异常Hook:
会了异常处理,这里我们来看一种基于异常的Hook:
VEH软件断点HOOK
思路:我们将API实现的第一个字节改为int3断点,然后我们捕获异常,处理这个异常,将API参数修改掉
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
ULONG_PTR MessageBoxProc = (ULONG_PTR)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxW");
struct _EXECEPTION_HOOK {
//保存要Hook的地址
ULONG_PTR ExceptionAddress;
//原来位置的代码/硬编码
UCHAR OldCode;
};
_EXECEPTION_HOOK HookInfo;
//这里实际上是设置int3断点
VOID SetVEHHook(ULONG_PTR ProcAddress) {
DWORD dwProtect = 0;
VirtualProtect((LPVOID)ProcAddress, 1, PAGE_EXECUTE_READWRITE, &dwProtect);
//要Hook的地址,就是我们获取的API函数地址
HookInfo.ExceptionAddress = ProcAddress;
//函数地址上取一字节,就是原来的硬编码
HookInfo.OldCode = *(UCHAR*)ProcAddress;
//将函数内部第一个字节修改位int3
*(UCHAR*)ProcAddress = 0xCC;
//修复内存保护属性
VirtualProtect((LPVOID)ProcAddress, 1, dwProtect, &dwProtect);
}
//这里才是真正的Hook(修改API参数)
LONG VEHHook(
struct _EXCEPTION_POINTERS *ExceptionInfo
) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookInfo.ExceptionAddress) {
const WCHAR* szStr = L"我被Hook了";
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 8) = (DWORD)szStr;
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)VEHHook);
SetVEHHook(MessageBoxProc);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Hook测试:
先运行目标程序(前面讲InlineHook的时候有源码)
注入我们刚生成的dll:
Hook成功:
VEH硬件断点HOOK
下断点需要保存地址:保存到DR0~DR3(断点地址寄存器)
开关:DR7 : 07(L:局部,G:全局)03对应DR0~3
类型:R/W读写域 0~3 (DR0~DR3)占两位
四种状态:
00:执行时断下来(硬件执行断点)
01:写数据的时候断下来(硬件写入断点)
10:I/O终端
11:读写数据的时候都断,但是读指令不断(硬件访问断点)
长度LEN域03(DR0DR3)
四种状态
00:断点一字节长
01:断点2字节长
10:断点8字节长
11:断点4字节长
GD:启用访问检测功能:
如果开启了此位,当修改DR系列寄存器的时候,CPU会触发异常
DR6:
B0~B3:某一组
如果B0被设置为1,说明DR0的R/W,LEN位都被满足条件了
VEH硬件断点优势:不修改内存,如果有内存检测,就不会触发
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
ULONG_PTR MessageBoxProc = (ULONG_PTR)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxW");
struct _EXECEPTION_HOOK {
//保存要Hook的地址
ULONG_PTR ExceptionAddress;
//原来位置的代码/硬编码
UCHAR OldCode;
};
_EXECEPTION_HOOK HookInfo;
//这里实际上是设置int3断点
VOID SetVEHHook(ULONG_PTR ProcAddress) {
DWORD dwProtect = 0;
VirtualProtect((LPVOID)ProcAddress, 1, PAGE_EXECUTE_READWRITE, &dwProtect);
//要Hook的地址,就是我们获取的API函数地址
HookInfo.ExceptionAddress = ProcAddress;
//函数地址上取一字节,就是原来的硬编码
HookInfo.OldCode = *(UCHAR*)ProcAddress;
//将函数内部第一个字节修改位int3
*(UCHAR*)ProcAddress = 0xCC;
//修复内存保护属性
VirtualProtect((LPVOID)ProcAddress, 1, dwProtect, &dwProtect);
}
//这里才是真正的Hook(修改API参数)
LONG VEHHook(
struct _EXCEPTION_POINTERS *ExceptionInfo
) {
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookInfo.ExceptionAddress) {
const WCHAR* szStr = L"我被Hook了";
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 8) = (DWORD)szStr;
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)VEHHook);
SetVEHHook(MessageBoxProc);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Hook测试:
先运行目标程序
注入dll:
Hook成功: