测试环境:Windows 10 21H2 32位,开启全局DEP。这里来看如何绕过DEP和ASLR。
没得说,肯定是先看需要多少字节能够Crash程序。简单的步骤就不说了,讲一下我遇到的一些小坑。
参考exploit-db:https://www.exploit-db.com/exploits/14191。这里选择字节长度为`50000`。
1 | #!/usr/bin/python3 |
程序Crash的时候,发现把SEH链给覆盖了:

所以,这里应该是基于SEH的栈溢出,需要绕过DEP和ASLR。
在寻找Offset的时候,遇到一个小坑。这里可以用msf-pattern_create -l 50000生成,或者windbg加载mona之后,使用!py mona pc 50000生成,生成没有问题,主要是在确定具体的Offset的时候,出现了一点小问题。
注意看windbg中,通过mona查找到的具体Offset:

看一下msf-pattern_offset中查找到的具体Offset:

最后的正确Offset为43474。个人感觉是因为pattern太长了,里面的字符串出现了重复,而windbg中mona没有搜索完,只搜索到第一个就停止了,所以,后续查找Offset的时候,最好两边一起验证下,如果有多个值符合条件,每个值最好也试一下。

Offset偏移是正确的,注意标红的框,这里有个小细节,42424242之后被覆盖了ffffffff,这个ffffffff应该是SEH链的尾部。后续在查找坏字符的时候,会发现04030201没找到,原因是因为被ffffffff覆盖了。基于这一点,后续在写利用代码的时候,可以在Offset之后先跟一段\x90,然后再接shellcode,以防shellcode被覆盖导致不能执行。
注意,在查找坏字符的时候,需要定位坏字符串的位置,这里可以利用windbg中的搜索功能,需要先用!teb获取当前内存的空间大小及地址,然后再查找:
1 | 0:000> !teb |
搜索的命令为:
1 | s -a 000e0000 00150000 BBBB |

显然,第一个坏字符为09,后续发现0a也是坏字符:

最终坏字符为\x00\x09\x0a。
讲一点小知识:对于开启DEP且基于SEH的栈溢出,不能使用之前讨论的未开启DEP的SEH栈溢出利用方法。因为开启DEP之后,栈空间是不可执行的,而之前SEH利用方式中组合P/P/R和JMP的方式,在P/P/R执行完之后,后续跳转到JMP指令上,因为该指令在栈上面导致无法执行。正确的方法是直接用能跳转到ROP Chain上的指令覆盖SEH Handler。
考虑到Bypass ASLR,在构建ROP Chain的时候,如果程序运行时没能泄漏出某个函数的地址,则需要选择没有开启ASLR的DLL,不然可以先计算出基址,然后ROP Gadget都选择相对地址构建ROP Chain即可。这里选择没开启ASLR的DLL:

在用mona自动生成ROP Chain的时候,发现生成不成功,如下所示:
1 | *** [ Python ] *** |
注意看,里面说没有找到设置EDX的ROP Gadgets。这里在构造设置EDX的ROP Gadgets的时候,因为牵涉到EBX,需要将设置EBX值的ROP Gadgets放到设置EDX的ROP Gadgets后面去。来看看我构造的设置EDX的ROP Gadget:
1 | #[---INFO:gadgets_to_set_edx:---] |
因为RETN 0x10指令的存在,这里需要把设置ECX的其中一条ROP Gadget放到设置EDX的ROP Gadgets中间。
修改之后的ROP Gadgets如下:
1 | def create_rop_chain(): |
小知识点:RETN操作:先EIP=ESP,然后ESP=ESP+4;RETN N操作:先EIP=ESP,然后ESP=ESP+4+N。
ROP Gadgets构造完成之后,现在就是如何让SEH Handler执行的时候,能够跳转到ROP Gadgets的第一条指令,这里一定要跳转到ROP Gadgets的第一条指令,前面不能加\x90进行填充,这里和shellcode之前的\x90填充不一样,这里因为有RETN指令,如果用\x90填充,会把\x90\x90\x90\x90设置为EIP的值,而这会指向一个无效的地址。shellcode之前的\x90填充,因为使用的JMP ESP指令,就算跳转到\x90所在区域,并不会把\x90\x90\x90\x90设置为EIP的值,EIP的值指向\x90\x90\x90\x90,只会不断的跳过这些\x90空指令,直到遇到shellcode第一条指令。
暂时设置SEH Handler的值为\xcc\xcc\xcc\xcc,然后重新运行,当溢出发生的时候,来看一下此刻ROP Chain和ESP之间的距离:
1 | (1c78.1948): Access violation - code c0000005 (first chance) |
可以看到两者之间的距离在18aach,现在需要找到能够跳转大于等于18aach的ROP Gadget来替换SEH Handler中的值。使用如下命令:
1 | !py mona stackpivot -n -m MSA2Mfilter03.dll -distance 101036 -cpb '\x00\x09\x0a' |

最终的结果会保存到stackpivot.txt文件里面。来看一下文件里面的部分内容:

这里选择的0x1001f35f地址所在的ROP Gadget。但是这里跳转了19000h,会跳到ROP Chain里面去,我们需要在ROP Chain前面增加一些\x90,让ESP正好能够指向ROP Chain的第一条指令。具体的值需要不断的调试才能确定,主要是因为添加的\x90也会占用空间,会让ESP与ROP Chain之间的距离变长。最终得到的\x90长度为57424。这里我还考虑了一下,这个值会不会让最终的PAYLOAD指到栈空间以外的地方。验证了一下:

空间足够大,没问题。
最终的利用代码如下:
1 | #!/usr/bin/python3 |
成功反弹shell:
