Deep into Windows SEH

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

SEH Stack Layout

img

存储在栈上的结构体是_EH3_EXCEPTION_REGISTRATION

_EH3_EXCEPTION_REGISTRATION

1
2
3
4
5
6
7
struct _EH3_EXCEPTION_REGISTRATION
{
struct _EH3_EXCEPTION_REGISTRATION *Next;
PVOID ExceptionHandler;
PSCOPETABLE_ENTRY ScopeTable;
DWORD TryLevel;
};

我们通过汇编来观察程序是如何加载这个结构体到栈上的。

程序源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int main()
{
unsigned char buf[0x100] = { '\x00' };
__try {
int num = 0;
scanf("%s", buf);
scanf("%d", &num);
int c = 5 / num;
}
__except (EXCEPTION_EXECUTE_HANDLER) {
printf("exception trigger");
}
return 0;
}

image-20240328200239243

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

ScopeTable

image-20240328200714687

对应的结构体为**_EH4_SCOPETABLE**,也说明了PSCOPETABLE_ENTRY_EH4_SCOPETABLE的指针。

1
2
3
4
5
6
7
8
struct _EH4_SCOPETABLE
{
DWORD GSCookieOffset;
DWORD GSCookieXOROffset;
DWORD EHCookieOffset;
DWORD EHCookieXOROffset;
struct _EH4_SCOPETABLE_RECORD ScopeRecord[1];
};

_EH4_SCOPETABLE_RECORD

1
2
3
4
5
6
struct _EH4_SCOPETABLE_RECORD
{
int EnclosingLevel;
void *FilterFunc;
void *HandlerFunc;
};

_EH4_SCOPETABLE_RECORD中的FilterFunc对应着__except(...)里小括号的代码,并且FilterFunc的返回值必须为如下的值,是windows用来判断是否要执行__except(...) {...}大括号里的代码,也就是HandlerFunc、还是已经处理完异常选择继续执行发生异常后的代码、还是这个SEH无法处理传递到下一个SEH来处理

1
2
3
4
// Defined values for the exception filter expression
#define EXCEPTION_EXECUTE_HANDLER 1
#define EXCEPTION_CONTINUE_SEARCH 0
#define EXCEPTION_CONTINUE_EXECUTION (-1)

对应的FilterFuncHandlerFunc

image-20240328202123634

ExceptionHandler

image-20240328202828166

_EXCEPTION_RECORD

1
2
3
4
5
6
7
8
9
struct _EXCEPTION_RECORD
{
unsigned int ExceptionCode;
unsigned int ExceptionFlags;
_EXCEPTION_RECORD *ExceptionRecord;
void *ExceptionAddress;
unsigned int NumberParameters;
unsigned int ExceptionInformation[15];
};

_EXCEPTION_REGISTRATION_RECORD

1
2
3
4
5
struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD* Next; //0x0
enum _EXCEPTION_DISPOSITION (*Handler)(struct _EXCEPTION_RECORD* arg1, VOID* arg2, struct _CONTEXT* arg3, VOID* arg4); //0x4
};

细心点就会发现这个_EXCEPTION_REGISTRATION_RECORD就是_EH3_EXCEPTION_REGISTRATION的前两个成员变量。

_except_handler4_common

_except_handler4_commonvcruntime140.dll里,通过msdnpdb和逆向还原,大致函数流程如下,无法保证正确性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
int __cdecl _except_handler4_common(
unsigned int *CookiePointer,
void (__fastcall *CookieCheckFunction)(unsigned int),
_EXCEPTION_RECORD *ExceptionRecord,
_EH3_EXCEPTION_REGISTRATION *EstablisherFrame,
_CONTEXT *ContextRecord)
{
char *FramePointer1; // esi
DWORD TryLevel; // edi
DWORD v7; // eax
DWORD EnclosingLevel; // ebx
_EH4_SCOPETABLE_RECORD *ScopeRecord1; // eax
int (*FilterFunc)(void); // ecx
int v11; // eax
char v12; // cl
_EXCEPTION_RECORD *v14; // eax
_EH3_EXCEPTION_REGISTRATION *v15; // eax
_EH4_SCOPETABLE *ScopeTable1; // [esp-8h] [ebp-30h]
_EH4_SCOPETABLE *v17; // [esp-8h] [ebp-30h]
_EXCEPTION_POINTERS ExceptionPointers; // [esp+Ch] [ebp-1Ch] BYREF
_EH4_SCOPETABLE_RECORD *ScopeRecord2; // [esp+14h] [ebp-14h]
void *FramePointer2; // [esp+18h] [ebp-10h]
int v21; // [esp+1Ch] [ebp-Ch]
_EH4_SCOPETABLE *ScopeTable2; // [esp+20h] [ebp-8h]
char v23; // [esp+27h] [ebp-1h]

v23 = 0;
FramePointer1 = (char *)&EstablisherFrame[1];
ScopeTable1 = (_EH4_SCOPETABLE *)(*CookiePointer ^ (unsigned int)EstablisherFrame->ScopeTable); // decode ScopeTable
v21 = 1;
FramePointer2 = &EstablisherFrame[1];
ScopeTable2 = ScopeTable1;
ValidateLocalCookies(CookieCheckFunction, ScopeTable1, (char *)&EstablisherFrame[1]);
__except_validate_context_record(ContextRecord);
TryLevel = EstablisherFrame->TryLevel;
if ( (ExceptionRecord->ExceptionFlags & 0x66) != 0 )
{
if ( TryLevel != -2 )
{
_EH4_LocalUnwind(EstablisherFrame, -2u, (int)FramePointer1, CookiePointer);
LABEL_14:
ValidateLocalCookies(CookieCheckFunction, ScopeTable2, FramePointer1);
}
}
else
{
ExceptionPointers.ExceptionRecord = ExceptionRecord;
ExceptionPointers.ContextRecord = ContextRecord;
EstablisherFrame[-1].TryLevel = (DWORD)&ExceptionPointers;// update ExceptionPointers
if ( TryLevel != -2 )
{
do
{
v7 = TryLevel + 2 * (TryLevel + 2);
EnclosingLevel = *(&ScopeTable2->GSCookieOffset + v7);
ScopeRecord1 = (_EH4_SCOPETABLE_RECORD *)(&ScopeTable2->GSCookieOffset + v7);
FilterFunc = (int (*)(void))ScopeRecord1->FilterFunc;
ScopeRecord2 = ScopeRecord1;
if ( FilterFunc )
{
v11 = _EH4_CallFilterFunc(FilterFunc);// execute FilterFunc
v12 = 1;
v23 = 1;
if ( v11 < 0 ) // EXCEPTION_CONTINUE_EXECUTION
{
v21 = 0;
goto LABEL_14;
}
if ( v11 > 0 ) // EXCEPTION_EXECUTE_HANDLER
{
v14 = ExceptionRecord;
if ( ExceptionRecord->ExceptionCode == 0xE06D7363 && _pDestructExceptionObject )
{
if ( _IsNonwritableInCurrentImage(&_pDestructExceptionObject) )
{
_pDestructExceptionObject(ExceptionRecord, 1);
FramePointer1 = (char *)FramePointer2;
}
v14 = ExceptionRecord;
}
_EH4_GlobalUnwind2(EstablisherFrame, v14);
v15 = EstablisherFrame;
if ( EstablisherFrame->TryLevel != TryLevel )
{
_EH4_LocalUnwind(EstablisherFrame, TryLevel, (int)FramePointer1, CookiePointer);
v15 = EstablisherFrame;
}
v17 = ScopeTable2;
v15->TryLevel = EnclosingLevel;
ValidateLocalCookies(CookieCheckFunction, v17, FramePointer1);
_EH4_TransferToHandler((int (__fastcall *)(_DWORD, _DWORD))ScopeRecord2->HandlerFunc);// execute HandlerFunc
JUMPOUT(0x1000420A);
}
}
else
{
v12 = v23;
}
TryLevel = EnclosingLevel;
}
while ( EnclosingLevel != -2 );
if ( !v12 )
return v21;
goto LABEL_14;
}
}
return v21;
}

__exception_handler4_common最终会执行FilterFuncHandlerFunc,且没有验证这两个函数的合法性,所以我们可以通过伪造栈上的_EH3_EXCEPTION_REGISTRATION来实现任意函数执行。

异常处理流程

异常都是通过ntdll里的KiUserExceptionDispatcher函数来进行异常调度。

KiUserExceptionDispatcher

KiUserExceptionDispatcher调用RtlDispatchException来完成实际的调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID NTAPI KiUserExceptionDispatcher (EXCEPTION_RECORD *ExceptionRecord, CONTEXT *Context)
{
NTSTATUS Status;
if (RtlDispatchException(ExceptionRecord, Context)) {
/* Exception is handled, you can continue the execution */
Status = NtContinue(Context, FALSE);
}
else {
/* We are again raising an exception but, this time, without trying to find a handler */
Status = NtRaiseException(ExceptionRecord, Context, FALSE);
}
...
RtlRaiseException(&NestedException);
}

RtlDispatchException

开启SEHOPSafeSEH会校验ExceptionChainExceptionHandler的合法性。

校验完后,最终执行ExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void RtlDispatchException(...) // NT 6.3.9600
{
/* Call the chain 'Vectored Exception Handlers' */
if (RtlpCallVectoredHandlers(exception, 1)) return 1;
ExceptionRegistration = RtlpGetRegistrationHead();
/* ECV (SEHOP) */
if (!DisableExceptionChainValidation &&
!RtlpIsValidExceptionChain(ExceptionRegistration, ...)) {
if (_RtlpProcessECVPolicy != 2)
goto final;
else
RtlReportException();
}
/* Go through the handler chain until you find the right handler */
while (ExceptionRegistration != EXCEPTION_CHAIN_END) {
/* Check the stack limits */
if (!STACK_LIMITS(ExceptionRegistration)) {
ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
goto final;
}
/* Validate the handler (SafeSEH)*/
if (!RtlIsValidHandler(ExceptionRegistration, ProcessFlags)) goto final;
/* Hand over control to handler */
RtlpExecuteHandlerForException(..., ExceptionRegistration->Handler);// execute handler
...
ExceptionRegistration = ExceptionRegistration->Next;
}
...
final:
/* Call the chain 'Vectored Continue Handlers' */
RtlpCallVectoredHandlers(exception, 1);
}

RtlIsValidHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
BOOL RtlIsValidHandler(Handler) // NT 6.3.9600
{
if (/* Handler within the image */) {
if (DllCharacteristics&IMAGE_DLLCHARACTERISTICS_NO_SEH)
goto InvalidHandler;
if (/* The image is .Net assembly, 'ILonly' flag is enabled */)
goto InvalidHandler;
if (/* Found 'SafeSEH' table */) {
if (/* The image is registered in 'LdrpInvertedFunctionTable' (or its cache), or the initialization of the process is not complete */) {
if (/* Handler found in 'SafeSEH' table */)
return TRUE;
else
goto InvalidHandler;
}
return TRUE;
} else {
if (/* 'ExecuteDispatchEnable' and 'ImageDispatchEnable' flags are enabled in 'ExecuteOptions' of the process */)
return TRUE;
if (/* Handler is in non-executable area of the memory */) {
if (ExecuteDispatchEnable) return TRUE;
}
else if (ImageDispatchEnable) return TRUE;
}
InvalidHandler:
RtlInvalidHandlerDetected(...);
return FALSE;
}

RtlpIsValidExceptionChain

大致意思是SEH的地址要在栈上,ExceptionHandler不能在栈上,且最后一个SEHHandler要为RtlpFinalExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
char __fastcall RtlpIsValidExceptionChain(
_EXCEPTION_REGISTRATION_RECORD *a1,
DWORD StackLimit,
DWORD StackBase,
int a4)
{
DWORD StackBase_1; // ebx
DWORD *StackLimit_1; // eax
_EXCEPTION_DISPOSITION (__stdcall *Handler)(_EXCEPTION_RECORD *, void *, _CONTEXT *, void *); // edx

StackBase_1 = StackBase;
StackLimit_1 = (DWORD *)StackLimit;
while ( a1 != (_EXCEPTION_REGISTRATION_RECORD *)-1 )
{
if ( StackLimit_1 > (DWORD *)a1 )
return 0;
if ( (unsigned int)a1 >= StackBase_1 - 8 )
return 0;
if ( ((unsigned __int8)a1 & 3) != 0 )
return 0;
Handler = a1->Handler;
if ( (unsigned int)Handler < StackBase_1 && StackLimit <= (unsigned int)Handler )
return 0;
if ( a1->Next == (_EXCEPTION_REGISTRATION_RECORD *)-1 )
{
StackBase_1 = StackBase;
if ( (NtCurrentTeb()->SameTebFlags & 0x200) != 0
&& Handler != (_EXCEPTION_DISPOSITION (__stdcall *)(_EXCEPTION_RECORD *, void *, _CONTEXT *, void *))RtlpFinalExceptionHandler )
{
return 0;
}
}
StackLimit_1 = (DWORD *)&a1[1];
a1 = a1->Next;
}
return 1;
}

Reference