Structured Exception Handling(结构化异常处理)也就是**SEH,是 Microsoft 对 C 和 C++ 语言的一个扩展,用于适当地处理某些异常代码情况,包括软件异常和硬件异常。由于SEH是存储在栈帧内,当我们无法直接溢出控制eip/rip,我们就可以尝试通过控制SEH来劫持程序。下文都基于32位程序的SEH**。
SEH Stack Layout

存储在栈上的结构体是_EH3_EXCEPTION_REGISTRATION。
_EH3_EXCEPTION_REGISTRATION
1 | struct _EH3_EXCEPTION_REGISTRATION |
我们通过汇编来观察程序是如何加载这个结构体到栈上的。
程序源码
1 |
|

由于栈是从地址高处往低处增长,所以是倒着放入对应的成员变量。
ScopeTable

对应的结构体为**_EH4_SCOPETABLE**,也说明了PSCOPETABLE_ENTRY是_EH4_SCOPETABLE的指针。
1 | struct _EH4_SCOPETABLE |
_EH4_SCOPETABLE_RECORD
1 | struct _EH4_SCOPETABLE_RECORD |
_EH4_SCOPETABLE_RECORD中的FilterFunc对应着__except(...)里小括号的代码,并且FilterFunc的返回值必须为如下的值,是windows用来判断是否要执行__except(...) {...}大括号里的代码,也就是HandlerFunc、还是已经处理完异常选择继续执行发生异常后的代码、还是这个SEH无法处理传递到下一个SEH来处理
1 | // Defined values for the exception filter expression |
对应的FilterFunc和HandlerFunc

ExceptionHandler

_EXCEPTION_RECORD
1 | struct _EXCEPTION_RECORD |
_EXCEPTION_REGISTRATION_RECORD
1 | struct _EXCEPTION_REGISTRATION_RECORD |
细心点就会发现这个_EXCEPTION_REGISTRATION_RECORD就是_EH3_EXCEPTION_REGISTRATION的前两个成员变量。
_except_handler4_common
_except_handler4_common在vcruntime140.dll里,通过msdn的pdb和逆向还原,大致函数流程如下,无法保证正确性
1 | int __cdecl _except_handler4_common( |
__exception_handler4_common最终会执行FilterFunc和HandlerFunc,且没有验证这两个函数的合法性,所以我们可以通过伪造栈上的_EH3_EXCEPTION_REGISTRATION来实现任意函数执行。
异常处理流程
异常都是通过ntdll里的KiUserExceptionDispatcher函数来进行异常调度。
KiUserExceptionDispatcher
KiUserExceptionDispatcher调用RtlDispatchException来完成实际的调度。
1 | VOID NTAPI KiUserExceptionDispatcher (EXCEPTION_RECORD *ExceptionRecord, CONTEXT *Context) |
RtlDispatchException
开启SEHOP和SafeSEH会校验ExceptionChain和ExceptionHandler的合法性。
校验完后,最终执行ExceptionHandler。
1 | void RtlDispatchException(...) // NT 6.3.9600 |
RtlIsValidHandler
1 | BOOL RtlIsValidHandler(Handler) // NT 6.3.9600 |
RtlpIsValidExceptionChain
大致意思是SEH的地址要在栈上,ExceptionHandler不能在栈上,且最后一个SEH的Handler要为RtlpFinalExceptionHandler。
1 | char __fastcall RtlpIsValidExceptionChain( |