SEH异常之编译器原理探究
字数 1965 2025-08-07 00:34:54
SEH异常处理机制深入解析:编译器实现原理
0x00 前言
本文深入探究Windows结构化异常处理(SEH)中编译器对_try_except和_try_finally的实现原理,涵盖异常处理流程、数据结构、编译器扩展机制等核心内容。
0x01 _try_except原理
基本机制
- 编译器调用
_except_handle3作为异常处理函数(不同编译器可能不同) - 将ESP值赋给FS:[0],提升堆栈
- 每个使用
_try_except的函数只注册一个_EXCEPTION_REGISTRATION_RECORD结构到线程异常链表中
关键数据结构
原始结构
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;
编译器扩展结构
struct _EXCEPTION_REGISTRATION {
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
};
scopetable结构
struct scopetable_entry {
DWORD previousTryLevel; // 上一个try{}结构编号
PDWRD lpfnFilter; // 过滤函数的起始地址
PDWRD lpfnHandler; // 异常处理程序的地址
}
关键成员说明
-
previousTryLevel:
- 最外层为-1
- 第二层为0
- 第三层为1
- 以此类推
-
trylevel:
- 表示代码运行到哪个try结构中
- 进入try结构时加1
- try结构执行完成后减1
异常处理流程
- CPU检测到异常 → 查中断表执行处理函数 → CommonDispatchException → KiDispatchException → KiUserExceptionDispatcher → RtlDispatchException → VEH → SEH
- 执行
_except_handler3函数:- 根据trylevel选择scopetable数组
- 调用scopetable数组中对应的lpfnFilter函数
- EXCEPTION_EXECUTE_HANDLER(1):执行except代码
- EXCEPTION_CONTINUE_SEARCH(0):寻找下一个处理程序
- EXCEPTION_CONTINUE_EXECUTION(-1):重新执行
- 如果lpfnFilter返回0,向上遍历直到previousTryLevel=-1
处理示例
假设有两个异常点:
- 首先找到trylevel为0
- 找到异常过滤表达式为1
- 遍历数组的lpfnFilter:
- 返回1:调用异常处理函数
- 返回0:该异常函数不处理
- 返回-1:从原异常点继续执行
0x02 _try_finally原理
基本特性
无论try结构体中是什么代码(包括return、continue、break等),都会执行finally代码块。
局部展开
当try中没有异常而是控制流改变语句时:
- 不调用
_except_handle3 - 调用
_local_unwind2进行展开 - 调用
[ebx + esi*4 + 8]进入finally语句块
识别特征:lpfnFilter参数为0时表示finally语句块
全局展开
多层嵌套时的处理:
- 一层层向上查找异常处理函数
- finally模块照常执行
0x03 未处理异常机制
入口程序的最后防线
mainCRTStartup()是进程开始执行的地方- 内部修改FS:[0],注册异常处理函数
kernel32.dll中的BaseProcessStart注册SEH异常处理函数
线程的最后防线
- 线程从
kernel32.dll的BaseThreadStart开始 - 同样注册了SEH异常处理函数
UnhandledExceptionFilter流程
相当于编译器生成的伪代码:
__try{}__except(UnhandledExceptionFilter(GetExceptionInformation())) {
// 终止线程或进程
}
执行流程:
- 通过
NtQueryInformationProcess查询进程是否被调试- 如果是,返回
EXCEPTION_CONTINUE_SEARCH,进入第二轮分发
- 如果是,返回
- 如果没有被调试:
- 检查是否通过
SetUnhandledExceptionFilter注册处理函数- 有:调用注册函数
- 无:弹出窗口让用户选择终止程序或启动即时调试器
- 用户未启用即时调试器时返回
EXCEPTION_EXECUTE_HANDLER
- 检查是否通过
SetUnhandledExceptionFilter应用
可用于实现反调试:
long __stdcall callback(_EXCEPTION_POINTERS* excp) {
excp->ContextRecord->Ecx = 1; // 修复异常
return EXCEPTION_CONTINUE_EXECUTION;
}
int main() {
SetUnhandledExceptionFilter(callback);
_asm {
xor edx, edx
xor ecx, ecx
mov eax, 0x10
idiv ecx // 除0异常
}
printf("Run again!");
return 0;
}
特性:
- 直接运行:异常被修复,程序继续
- 调试运行:直接退出
KiUserExceptionDispatcher
仅当程序被调试时才可能产生未处理异常:
- 调用
RtlDispatchException查找并执行异常处理函数- 返回真:调用
ZwContinue再次进入0环,返回3环时从修正位置执行 - 返回假:调用
ZwRaiseException进行第二轮异常分发
- 返回真:调用
总结
本文详细解析了Windows SEH机制的编译器实现,包括:
_try_except的扩展结构和处理流程_try_finally的局部和全局展开机制- 未处理异常的最后防线及反调试应用
- 关键数据结构和系统调用流程
理解这些底层原理对于开发健壮的异常处理系统和实现安全防护机制至关重要。