在Windows下,有两种异常:
1.结构化异常 SEH
2.向量化异常 VEH
接下来我们学习这两种异常
结构化异常
接下来我们演示除0异常的处理:
#include <iostream>
int main()
{
__try//用于包含触发异常的代码
{
int a = 0;
int b = 0;
int c = a / b;//除0异常
}
//__except参数有如下几种情况
//EXCEPTION_CONTINUE_EXECUTION,即-1:异常已消除,从出现异常的点继续执行。
//EXCEPTION_CONTINUE_SEARCH,即0:无法识别异常,继续向上搜索堆栈查找处理程序,首先是所在的 try-except 语句,然后是具有下一个最高优先级的处理程序。
//EXCEPTION_EXECUTE_HANDLER,即1:异常可识别
__except (EXCEPTION_EXECUTE_HANDLER)//如下是出现异常时的处理
{
std::cout << "int c = a / b" << std::endl;
}
system("pause");
return 0;
}
接下来我们演示除0异常退出:
#include <iostream>
int main()
{
__try//用于包含触发异常的代码
{
int a = 0;
int b = 0;
goto exitcode;//跳出__try块,会被AbnormalTermination()认为异常终止
}
__finally
{
if(AbnormalTermination())//异常终止
{
std::cout << "异常退出" << std::endl;
}
else
{
std::cout << "正常退出" << std::endl;
}
}
exitcode;
system("pause");
return 0;
}
向量化异常
向量化异常的基本思想:注册回调函数来接收和处理异常,并将该函数添加到异常处理程序链表中。当程序运行时发生异常,操作系统会在异常处理程序链表中按照头到尾的顺序依次比对每个异常处理程序,直到匹配对应异常的异常处理程序
通过AddVectoredExceptionHandler可以注册一个全局的异常处理程序
PVOID AddVectoredExceptionHandler
(
ULONG First, //异常处理程序在链表中的顺序:当非零值时,则将异常处理程序插入到向量化异常处理程序列表的开头,否则插入末尾
PVECTORED_EXCEPTION_HANDLER Handler//指向异常处理函数
);
我们了解一下向量化异常的结构:_EXCEPTION_POINTERS,该结构对异常进行了一些记录
typedef struct _EXCEPTION_POINTERS
{
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
接下来我们了解该结构中的EXCEPTION_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;
我们再来了解CONTEXT结构,该结构用于保存线程的执行上下文信息,即线程执行时的各种寄存器状态和其他相关信息。由于该结构较为复杂,此处不再给出结构组成
了解完以后,我们开始代码学习向量化异常:
#include <iostream>
#include <Windows.h>
LONG Handler(
struct _EXCEPTION_POINTERS *ExceptionInfo
) {
//向量化异常需要我们自己选择处理方式,以及是否回去
DWORD dwCode = ExceptionInfo->ExceptionRecord->ExceptionCode;//获取异常信息的错误码
if (dwCode == EXCEPTION_BREAKPOINT) //判断接收到的是什么异常,此处为int3异常
{
//异常处理
std::cout << "This is Int3" << std::endl;
system("pause");
//EIP:指令指针寄存器,保存了下一行代码要执行的位置
//此处将执行的地址向后移动1,跳过int 3指令,从下一句开始执行
ExceptionInfo->ContextRecord->Eip += 1;
return EXCEPTION_CONTINUE_EXECUTION;//告诉操作系统异常已经得到处理,返回触发异常的位置重新执行异常代码
//由于此处的异常是int3,我们的异常处理程序也没有修复异常,等到返回触发异常代码处重新执行异常代码时就会重新触发异常重新执行异常处理程序,这会造成死循环。因此我们需要EIP+=1,从而跳过异常
}
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
//注册向量异常处理程序
AddVectoredExceptionHandler(
0,
(PVECTORED_EXCEPTION_HANDLER)Handler
);
//int3:软件断点,也就是我们调试程序的断点,它是一个断点异常
//实现形式:内存中写入0xCC
//接下来我们汇编实现软件断点
_asm
{
int 3//触发断点异常,无法正常执行该代码,暂停当前程序执行,转向异常处理程序。
}
std::cout << "异常之后的代码" << std::endl;
system("pause");
return 0;
}
软件断点HOOK
我们在HotFix HOOK中了解到,Win32 API的第一个指令是无效指令,不影响API的实现,因此我们可以修改第一个指令为int 3,触发异常,然后跳转异常处理程序,完成HOOK。
我们仍然看着MessageBoxA内部实现的反汇编图来完成这个异常HOOK
如下是调用MessageBoxA前的反汇编代码:
注意此时压栈的四个参数
如下是MessageBoxA内部实现的代码
我们要修改的指令就是上图的8BFF move edi, edi,将其修改为int 3,当执行到int 3触发异常,从而转向异常处理程序,完成HOOK
接下来我们要实现的异常HOOK内容为修改MessageBoxA的第一个参数
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<Windows.h>
size_t MessageBoxAddr = (size_t)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxA");//获取要HOOK的函数:MessageBoxA的地址
struct EXECEPTION_HOOK
{
//保存要Hook的地址
ULONG_PTR ExceptionAddress;
//原来位置的代码/硬编码
UCHAR OldCode;
};
EXECEPTION_HOOK HookInfo;
//在要HOOK的函数起始地址处设置int3断点
VOID SetHook(ULONG_PTR Address)
{
DWORD dwProtect = 0;
//修改函数起始处第一个字节内存属性,int3一字节大小
VirtualProtect((LPVOID)Address, 1, PAGE_EXECUTE_READWRITE, &dwProtect);
//保存要Hook的地址,即函数地址
HookInfo.ExceptionAddress = Address;
//保存函数地址头一字节的硬编码
HookInfo.OldCode = *(UCHAR*)Address;
//将函数内部第一个字节修改为int3
*(UCHAR*)Address = 0xCC;//int 3的硬编码为CC
//修复内存保护属性
VirtualProtect((LPVOID)Address, 1, dwProtect, &dwProtect);
}
//异常处理程序
LONG Hander(
struct _EXCEPTION_POINTERS* ExceptionInfo
)
{
//判断异常触发类型
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
//程序中会有很多int 3异常,因此需要判断这里触发的异常是不是我们写的int3异常
if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == HookInfo.ExceptionAddress)
{
//这是修改MessageboxA的第二个参数
const WCHAR* szStr = L"Hook";
//开始修改MessageboxA的第二个参数
//ESP:栈顶指针寄存器
//当压栈数据类型为整数型时,直接压栈
//当压栈数据类型为指针型时,地址压栈
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 0x8) = (DWORD)szStr;
//当栈中某位置已经有参数时,后续正常压栈碰到该位置时不会再次覆盖此位置
//跳过int 3异常
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)Hander);
SetHook(MessageBoxAddr);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
硬件断点HOOK
如图是intel操作手册中有关调试寄存器的内容:
从图中我们发现有八个寄存器:DR0-DR7,其中有用的是DR0,DR1,DR2,DR3,DR6和DR7
DE6和DR7是调试寄存器,DR4和DR5是DR6和DR7的影子寄存器(别名),但是当调试扩展开启时,调用DR4和DR5会造成异常
DR0-DR3是地址寄存器,用于保存断点地址。
接下来我们分析DR7各个位的含义:
L0和G0对应DR0,L0为局部断点的开启关闭(L0 == 1时开启,L0 == 0时关闭),G0为全局断点的开启关闭。其余L1,G1等等不再赘述,同一个道理
LE和GE废弃了
灰色的0和1是保留位
GD:访问检测位,当GD = 1时,开启访问检测功能,此时修改DR寄存器的指令时CPU会触发异常
R/W:读写位,R/W0-R/W3对应DR0-DR3,每个R/W占两位长度,因此存在四种状态:
00:在断点地址处执行指令时中断(硬件执行断点)
01:在断点地址处写数据时中断(硬件写入断点)
10:I/O中断,该中断需要开启调试拓展
11:在断点地址处读写数据时中断,但是读写指令不会中断(硬件访问断点)
LEN:长度域,LEN0-LEN3对应DR0-DR3,每个LEN占两位长度,因此对应四种状态:
00:断点触发长度范围1字节
01:断点触发长度范围2字节
10:断点触发长度范围8字节
11:断点触发长度范围4字节
当设置4字节长执行断点时,首先将断点地址写入DR0,DR7的L0位设置1,RW位设置00,LEN位设置11,此时便开启断点
接下来我们分析DR6各个位的含义:
B0-B3:例如B0被设置为1,那么DR0地址处,R/W,LEN条件满足
接下来我们演示硬件断点的使用:
每个线程都有一套调试寄存器,因此单次修改调试寄存器只能适用于单个线程
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <TlHelp32.h>
//获取要HOOK函数的地址
size_t MessageBoxAddr = (size_t)GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxA");
LONG Handler(
struct _EXCEPTION_POINTERS *ExceptionInfo
)
{
//判断触发的地址是不是属于我们自己
if ((ULONG_PTR)ExceptionInfo->ExceptionRecord->ExceptionAddress == MessageBoxAddr)
{
const char * szStr = "rkvir";
//修改了函数的第二个参数
*(DWORD *)(ExceptionInfo->ContextRecord->Esp + 0x8) = (DWORD)szStr;
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
else//加固断点,防止由于各种意外导致断点消失
{
ExceptionInfo->ContextRecord->Dr0 = MessageBoxAddr;
ExceptionInfo->ContextRecord->Dr7 = 0x405;
return EXCEPTION_CONTINUE_SEARCH;
}
}
//单线程HOOK
VOID SetThreadHook(HANDLE hThread)
{
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;//获取所有寄存器
GetThreadContext(hThread, &ctx);///获取当前线程上下文
ctx.Dr0 = MessageBoxAddr;//设置断点地址
//DR0 1字节 硬件执行断点
ctx.Dr7 = 1;
SetThreadContext(hThread, &ctx);//设置线程上下文
}
VOID SetHook()//遍历线程使得每个线程都能硬件断点
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
HANDLE hThread = NULL;
while (Thread32Next(hSnap,&te32))//遍历进程的所有线程
{
if (GetCurrentProcessId() == te32.th32OwnerProcessID)//线程遍历会遍历系统所有的线程,此处我们只要在当前进程进行硬件断点
{
hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
SetThreadHook(hThread);//硬件断点HOOK
CloseHandle(hThread);
}
}
CloseHandle(hThread);
}
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)Handler);
SetHook();
break;
case DLL_THREAD_ATTACH:
SetThreadHook(GetCurrentThread());//当起始HOOK完以后,如果进程创建了一个新的线程时再次HOOK
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
作业
自行实现以上两种HOOK
软件断点HOOK
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<Windows.h>
#include "dllmain.h"
ULONG MessageBoxAddrress = (ULONG)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxA");
UCHAR OldCode = { 0 };
LONG Header(
_EXCEPTION_POINTERS* ExceptionInfo
)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
if (ExceptionInfo->ExceptionRecord->ExceptionAddress == (PVOID)MessageBoxAddrress)
{
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 8) = (DWORD)"HOOK";
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 0xC) = (DWORD)"HOOK";
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return -1;
}
BOOL HOOK(ULONG HookAddress)
{
DWORD flOldProtect = 0;
VirtualProtect((LPVOID)MessageBoxAddrress, 1, PAGE_EXECUTE_READWRITE, &flOldProtect);
OldCode = *(UCHAR*)(MessageBoxAddrress);
*(UCHAR*)(MessageBoxAddrress) = 0xCC;
if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)MessageBoxAddrress, (LPVOID)L"\xCC", 1, NULL))
{
VirtualProtect((LPVOID)MessageBoxAddrress, 1, flOldProtect, &flOldProtect);
return TRUE;
}
return FALSE;
}
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)Header);
HOOK(MessageBoxAddrress);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
硬件断点HOOK
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<Windows.h>
#include <TlHelp32.h>
#include "dllmain.h"
ULONG MessageBoxAddrress = (ULONG)GetProcAddress(GetModuleHandle(L"User32.dll"), "MessageBoxA");
UCHAR OldCode = { 0 };
LONG Header(
_EXCEPTION_POINTERS* ExceptionInfo
)
{
if (ExceptionInfo->ExceptionRecord->ExceptionAddress == (PVOID)MessageBoxAddrress)
{
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 8) = (DWORD)"HOOK";
*(DWORD*)(ExceptionInfo->ContextRecord->Esp + 0xC) = (DWORD)"HOOK";
ExceptionInfo->ContextRecord->Eip += 2;
return EXCEPTION_CONTINUE_EXECUTION;
}
else
{
ExceptionInfo->ContextRecord->Dr0 = MessageBoxAddrress;
ExceptionInfo->ContextRecord->Dr7 = 0x405;
return EXCEPTION_CONTINUE_EXECUTION;
}
return -1;
}
VOID Hook()
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
BOOL Success = Thread32First(hSnap, &te32);
while (Success)
{
if (GetCurrentProcessId() == te32.th32OwnerProcessID)
{
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(hThread, &ctx);
ctx.Dr0 = MessageBoxAddrress;
ctx.Dr7 = 1;
SetThreadContext(hThread, &ctx);
CloseHandle(hThread);
}
Success = Thread32Next(hSnap, &te32);
}
}
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)Header);
Hook();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}