Note
Search…
Windows_SEH利用
windows栈溢出部分关于SEH的利用 (本文不探究safeSEH相关机制,不绕过safeSEH机制对SEH异常处理函数指针的检查,也就是不讨论覆盖SEH函数指针的情况;而是去绕过__except_handler4异常处理函数内部的检测,实现伪造__except__finally函数)
先看一个编译好的程序main函数入口处代码。SEH 的使用范围是线程相关的,每个线程都有自己的函数(SEH链表是局部链表,在堆栈中)
1
.text:004010B0 push ebp
2
.text:004010B1 mov ebp, esp
3
.text:004010B3 push 0FFFFFFFEh // ebp-4
4
.text:004010B5 push offset _EH4_SCOPETABLE_addr // ebp-8
5
.text:004010BA push offset __except_handler4 // ebp-c
6
.text:004010BF mov eax, large fs:0
7
.text:004010C5 push eax // ebp-10
8
.text:004010C6 add esp, 0FFFFFF40h
9
.text:004010CC mov eax, ___security_cookie
10
.text:004010D1 xor [ebp-8], eax // 加密scopeTable
11
.text:004010D4 xor eax, ebp
12
.text:004010D6 mov [ebp-1Ch], eax // 放入GS
13
.text:004010D9 push ebx
14
.text:004010DA push esi
15
.text:004010DB push edi
16
.text:004010DC push eax
17
.text:004010DD lea eax, [ebp-10h]
18
.text:004010E0 mov large fs:0, eax
Copied!
先看push offset _EH4_SCOPETABLE_addr这条指令,在main函数入口处被压入栈中。_EH4_SCOPETABLE为SEH的scope table结构,它保存了当前函数__try块相匹配的 __except__finally的地址值.
_EH4_SCOPETABLE通常被保存在程序的.rdata段。
1
.rdata:00403688 _EH4_SCOPETABLE_addr dd 0FFFFFFE4h ; GSCookieOffset
2
.rdata:00403688 ; DATA XREF: _main+5↑o
3
.rdata:00403688 dd 0 ; GSCookieXOROffset ; SEH scope table for function 4010B0
4
.rdata:00403688 dd 0FFFFFF20h ; EHCookieOffset
5
.rdata:00403688 dd 0 ; EHCookieXOROffset
6
.rdata:00403688 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel
7
.rdata:00403688 dd offset loc_401348 ; ScopeRecord.FilterFunc
8
.rdata:00403688 dd offset loc_40134E ; ScopeRecord.HandlerFunc
9
.rdata:004036A4 align 8
Copied!
细看_EH4_SCOPETABLE,它的C结构如下:
1
struct _EH4_SCOPETABLE {
2
DWORD GSCookieOffset;
3
DWORD GSCookieXOROffset;
4
DWORD EHCookieOffset;
5
DWORD EHCookieXOROffset;
6
_EH4_SCOPETABLE_RECORD ScopeRecord[1];
7
};
8
9
struct _EH4_SCOPETABLE_RECORD {
10
DWORD EnclosingLevel;
11
long (*FilterFunc)();
12
union {
13
void (*HandlerAddress)();
14
void (*FinallyFunc)();
15
};
16
};
Copied!
其中FilterFuncFinallyFunc就是我们自定义的__except__finally函数的地址。
紧接着下面三条指令,作用是在栈中为当前线程添加异常处理。
1
push offset __except_handler4 // ebp-c
2
mov eax, large fs:0
3
push eax // ebp-0x10
4
5
......
6
7
lea eax, [ebp-10h]
8
mov large fs:0, eax
Copied!
科普
1.TIB结构 TIB,又称线程信息块,是保存线程基本信息的数据结构,它位于TEB的头部。TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB。
TIB结构如下:
1
typedef struct _NT_TIB{
2
struct _EXCEPTION_REGISTRATION_RECORD *Exceptionlist;//指向异常处理链表
3
PVOID StackBase;//当前进程所使用的栈的栈底
4
PVOID StackLimit;//当前进程所使用的栈的栈顶
5
PVOID SubSystemTib;
6
union {
7
PVOID FiberData;
8
ULONG Version;
9
};
10
PVOID ArbitraryUserPointer;
11
struct _NT_TIB *Self;//指向TIB结构自身
12
} NT_TIB;
Copied!
在这个结构中与异常处理有关的第一个成员:指向_EXCEPTION_REGISTRATION_RECORD结构的Exceptionlist指针
2.EXCEPTION_REGISTRATION_RECORD 结构
该结构主要用于描述线程异常处理过程的地址,多个该结构的链表描述了多个线程异常处理过程的嵌套层次关系
结构如下:
1
typedef struct _EXCEPTION_REGISTRATION_RECORD{
2
struct _EXCEPTION_REGISTRATION_RECORD *Next;//指向下一个结构的指针
3
PEXCEPTION_ROUTINE Handler;//当前异常处理回调函数的地址
4
}EXCEPTION_REGISTRATION_RECORD;
Copied!
结构如图所示:
异常处理过程
fs寄存器指向TEB结构
所以 上面lea eax, [ebp-10h]mov large fs:0, eax指令也就是在栈中插入一个SEH异常处理结构体到TIB顶部, __except_handler4就是添加的系统默认异常处理回调函数,当发生异常时会首先执行它。
我们跟进__except_handler4
1
int __cdecl _except_handler4(int a1, int a2, int a3, int a4)
2
{
3
return except_handler4_common((int)&__security_cookie, (int)__security_check_cookie, a1, a2, a3, a4);
4
}
Copied!
里面又嵌套调用了except_handler4_common函数
1
void __cdecl ValidateLocalCookies(void (__fastcall *cookieCheckFunction)(unsigned int), _EH4_SCOPETABLE *scopeTable, char *framePointer)
2
{
3
unsigned int v3; // [email protected]
4
unsigned int v4; // [email protected]
5
6
if ( scopeTable->GSCookieOffset != -2 )
7
{
8
v3 = *(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset];
9
__guard_check_icall_fptr(cookieCheckFunction);
10
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v3);
11
}
12
v4 = *(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset];
13
__guard_check_icall_fptr(cookieCheckFunction);
14
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v4);
15
}
16
17
int __cdecl _except_handler4_common(unsigned int *securityCookies, void (__fastcall *cookieCheckFunction)(unsigned int), _EXCEPTION_RECORD *exceptionRecord, unsigned __int32 sehFrame, _CONTEXT *context)
18
{
19
// 异或解密 scope table
20
scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8));
21
22
// sehFrame 等于 主函数 ebp - 10h 位置, framePointer 等于主函数 ebp 的位置
23
framePointer = (char *)(sehFrame + 16);
24
scopeTable = scopeTable_1;
25
26
// 验证 GS
27
ValidateLocalCookies(cookieCheckFunction, scopeTable_1, (char *)(sehFrame + 16));
28
__except_validate_context_record(context);
29
30
if ( exceptionRecord->ExceptionFlags & 0x66 )
31
{
32
......
33
}
34
else
35
{
36
exceptionPointers.ExceptionRecord = exceptionRecord;
37
exceptionPointers.ContextRecord = context;
38
tryLevel = *(_DWORD *)(sehFrame + 12);
39
*(_DWORD *)(sehFrame - 4) = &exceptionPointers;
40
if ( tryLevel != -2 )
41
{
42
while ( 1 )
43
{
44
v8 = tryLevel + 2 * (tryLevel + 2);
45
filterFunc = (int (__fastcall *)(_DWORD, _DWORD))*(&scopeTable_1->GSCookieXOROffset + v8);
46
scopeTableRecord = (_EH4_SCOPETABLE_RECORD *)((char *)scopeTable_1 + 4 * v8);
47
encloseingLevel = scopeTableRecord->EnclosingLevel;
48
scopeTableRecord_1 = scopeTableRecord;
49
if ( filterFunc )
50
{
51
// 调用 FilterFunc
52
filterFuncRet = _EH4_CallFilterFunc(filterFunc);
53
......
54
if ( filterFuncRet > 0 )
55
{
56
......
57
// 调用 FilterFunc
58
_EH4_TransferToHandler(scopeTableRecord_1->HandlerFunc, v5 + 16);
59
......
60
}
61
}
62
......
63
tryLevel = encloseingLevel;
64
if ( encloseingLevel == -2 )
65
break;
66
scopeTable_1 = scopeTable;
67
}
68
......
69
}
70
}
71
......
72
}
Copied!
里面会检查栈中放入的GS值,会根据securityCookies解密_EH4_SCOPETABLE的地址,最终会调用到_EH4_SCOPETABLE里面的FilterFunc与FilterFunc函数,也就是我们自定义的__except__finally函数的地址。
如果我们能够查询伪造一个_EH4_SCOPETABLE结构,里面的FilterFunc函数指针写成自己的,其他字段不改变,覆盖栈中的_EH4_SCOPETABLE_addr为伪造地址,就能实现任意地址函数调用。
不过由于
1
mov eax, ___security_cookie
2
xor [ebp-8], eax
Copied!
指令中的[ebp-8]是_EH4_SCOPETABLE_addr, 所以我们还需要计算new_EH4_SCOPETABLE_addr=fake__EH4_SCOPETABLE_addr ^ ___security_cookie才行,___security_cookie的实际值需要leak。
由于
1
mov eax, ___security_cookie
2
xor eax, ebp
3
mov [ebp-1Ch], eax //GS
Copied!
覆盖存入栈ebp-0x1c的GS值时也应该注意这点, 也需要先leak出ebp与___security_cookie值后 再计算new_GS=___security_cookie ^ ebp的值 再进行覆盖。
所以要实现这种SEH利用,要泄露的地方其实挺多的。
Copy link