在微博看到 k0shl 师傅说 HITB GSEC 的两道 WIN PWN 题 BABYSHELLCODE 和 BABYSTACK 不错, 就下载下来自己学习分析了下, 分析完后感觉对学习利用 SEH 很有帮助, 学到不少, 这里发下自己分析 BABYSTACK 的流程总结. 菜鸡一个, 刚开始学习漏洞分析, 错误之处还请各位师傅不吝赐教.
k0shl 师傅关于两道题的分析: https://whereisk0shl.top/post/hitb_gsec_ctf_babystack_writeup
附件会把两道题上传, 有兴趣的可以看看.
用 IDA 打开程序, 找到 main 函数, 开始分析. 通过分析, 可以知道整个程序比较简单, 基本实现都在 main 函数中了, 其大概代码如下:
首先程序程序会输出 main 函数的地址和 buff 的地址, 这样我们就不用考虑 ASLR 和 栈地址随机的问题了. 然后有个循环, 次数为 10 次. 当输入 yes 时, 会调用 GetTargetAddress 函数, 这个函数把输入的一个十进制数字符串通过 atoi 函数转换成整数并返回, 返回后把该整数当作地址并读取该地址的值输出显示. 通过这里, 我们可以知道一些地址里的值.
当输入 no 时, 会跳出循环, 进而执行到下面的 if 处, 这里的 v1 和 v2 被初始化为 1 后就再也没修改过, 所以这里是不会进入 if 的, 不过这里可以看到有个 system("cmd"). 然后程序就退出了.
当输入的不是 yes 也不是 no 时, 会再次要求输入, 这次的输入长度为 256, 但 buff 只有 128 字节, 这里会发生缓冲区溢出. 看代码可以知道, 这里有个 SEH, 首先会想到用 system("cmd") 的地址通过缓冲区溢出覆盖 SEH 的 Handler, 然后通过在 GetTargetAddress 输入一个大数, 导致其访问无效内存引发异常, 进而执行 Handler. 但是这里是行不通的, 因为 BABYSTACK 是开启了 SafeSEH 的, 根据其原理(详见附 A)可知, 当我们用 systme("cmd") 的地址覆盖 Handler 后, 在检查时, 因为地址是在主程序映像范围, 主程序又开了 SafeSEH, 会有 SafeSEH Table, 而 systme("cmd") 的地址肯定不在表中, 必定是要失败的, 开始想其它方法. 这里想到 k0shl 师傅在微博有说到这题要分析 _except_handler4, 那就开始分析看看.
在 main 开头, 可以看到构造的 SEH
VC 的 SEH 布局大致如下(参考: SEH 机制探索)
这里的 Handler 函数就是 _except_handler4, 里面只是添加 CookieCheckFunc 和 SecurityCookie 两个参数, 然后调用了 VCRUNTIME140!_except_handler4_common. 下面是 VCRUNTIME140!_except_handler4_common(Sanos 实现的 _except_handler3 的代码也可以参考其整体流程except.c) 的伪码:
可以看到, 里面有两个地方, 一个地方调用了 scope table 里的 FilterFunc 函数, 一个地方调用了 HandlerFunc 函数. scope table 是被放在 SEH Handler 后面的, 这里我们可以伪造一个 scope table, 把里面的 FilterFunc 或者 HandlerFunc 函数改为 system("cmd'") 的地址, 然后把这个伪造的 scope table 通过溢出覆盖掉原 scope table.
接下来是 shellcode 的布局, buff 离 SEH 的位置有 0x8C 字节, 布局如下
上面之所以要留出 4 字节, 是因为在溢出了缓冲区之后, 再次输入 yes 引发异常时, 会用到该缓冲区, yes 会覆盖前 4 字节.
FilterFunc 填写 system("cmd") 的地址, 这里可以通过输出的 main 地址减去 main 函数的偏移算出 base, 然后加上 system 处的偏移就行. 因为这里利用的是 FilterFunc 函数, 所以 HandlerFunc 可以随意, 如果要用 HandlerFunc, 需要通过上面泄露信息的地方泄露处原 FilterFunc.
缓冲区的位置是 ebp - 9c, 而 GS 的位置在 ebp - 1c 处, 会被覆盖掉, 但是在 _except_handler4_common 中的 ValidateLocalCookies 会验证此处的值, 所以也要泄露出 GS, GS 的位置在 stack + 0x80 的位置.
接下来是 SEH 的 Next, 因为之前分析过 BABYSHELLCODE, 知道 Win10 下会多检查 SEH Chain(详见附 A), 所以也要泄露出 SEH 的 Next, 位置在 stack + 0x8c. 因为要利用 _except_handler4_common, Handler 还需要是 _except_handler4, 地址是 base + 函数的偏移.
最后就是伪造的 scope table 的地址了, 这里是 stack + 4, 不过需要和 Security Cookie 经过异或, Security Cookie 的值可以通过 base 加上偏移泄露出来, 然后和 stack + 4 的值异或一下就可以了. 下面是在 Win10 1703 x86 下的测试输出(这里因为是在命令行下, 地址是不可打印字符没法输入, 写了个辅助输入的脚本, 见附 B)
参考1: SafeSEH原理及绕过技术浅析
参考2: SEH和SafeSEH
当异常发生时,异常处理过程 RtlDispatchException 首先检查异常处理节点是否在栈上, 如果不在栈上程序将终止异常处理, 其次检查异常处理 Handler 是否在栈上, 如果在栈上程序将止异常处理. 最后检测调用 RtlIsValidHandler 检测 Handler 有效性
上面伪码里的 ExecuteDispatchEnable 和 ImageDispatchEnable 标志用来控制 Handler 在不可执行内存或者不在异常模块的映像内时, 是否可以执行. 默认情况下, 如果进程 DEP 开启, 两位为 0, DEP 关闭, 两位为 1.
Win10 在检查 SEH 时, 还会检查 SEH 链, 也就是说, 我们覆盖后的 Next 需要指向一个正常的 SEH, 保证 SEH 链的正常.
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!