|
本帖最后由 wangqiang 于 2022-6-22 18:13 编辑
EXP编写学习之绕过SafeSEH
yumoqaq 看雪学苑
2022-06-21 18:20 发表于上海
转载地址:EXP编写学习之绕过SafeSEH
看雪论坛作者ID:yumoqaq
1、亡羊补牢 :SafeSEH
SafeSEH对异常处理的保护原理:编译选项/SafeSEH启动,VS2003以后默认启用。生成SafeSEH表,放在PE文件中,调用异常处理函数的时候,
将地址与SafeSEH表中的地址比较。检查异常处理链是否位于当前程序的栈中,如果不在栈中,则程序终止异常处理函数的调用。检查异常处理函数
指针是否在程序的栈中,如果指向当前栈中,则终止异常处理函数的调用。前面两项检查都通过后,程序调用一个全新的函数 RtlIsValidHandler ,对异常处理函数的有效性进行验证。
RtlIsValidHandler检测原理:
首先,该函数判断异常处理函数地址是不是在加载模块的内存空间,如果属于加载模块的内存空间,校验函数将依次进行如下校验:
(1)判断程序是否设置了IMAGE_DLLCHARACTERISTICS_NO_SEH 标识。如果设置了这个标识,这个程序内的异常会被忽略。所以当这个标志被设置时,函数直接返回校验失败。
(2)检测程序是否包含安全S.E.H 表。如果程序包含安全S.E.H 表,则将当前的异常处理函数地址与该表进行匹配,匹配成功则返回校验成功,匹配失败则返回校验失败。
(3)判断程序是否设置ILonly 标识。如果设置了这个标识,说明该程序只包含.NET 编译人中间语言,函数直接返回校验失败。
(4)判断异常处理函数地址是否位于不可执行页(non-executable page)上。当异常处理函数地址位于不可执行页上时,校验函数将检测DEP 是否开启,如果系统未开启DEP 则返回校验成功,否则程序抛出访问违例的异常。
如果异常处理函数的地址没有包含在加载模块的内存空间,校验函数将直接进行DEP 相关检测,函数依次进行如下校验:
(1)判断异常处理函数地址是否位于不可执行页(non-executable page)上。当异常处理函数地址位于不可执行页上时,校验函数将检测DEP 是否开启,如果系统未开启DEP 则返回校验成功,否则程序抛出访问违例的异常。
(2)判断系统是否允许跳转到加载模块的内存空间外执行,如果允许则返回校验成功,否则返回校验失败。
RtlIsValidHandler允许异常函数执行的情况
1)异常处理函数位于加载模块内存范围之外,DEP 关闭。
2)异常处理函数位于加载模块内存范围之内,相应模块未启用SafeSEH(安全S.E.H 表为空),同时相应模块不是纯IL。
3)异常处理函数位于加载模块内存范围之内,相应模块启用SafeSEH(安全S.E.H 表不为空),异常处理函数地址包含在安全SEH表中。
分析一下这三种情况的可行性:
(1)现在我们只考虑SafeSEH,不考虑DEP。排除DEP 干扰后,我们只需在加载模块内存范围之外找到一个跳板指令就可以转入shellcode 执行,这点还是比较容易实现的。
(2)在第二种情况中,我们可以利用未启用SafeSEH 模块中的指令作为跳板,转入shellcode执行,这也是为什么我们说SafeSEH 需要操作系统与编译器的双重支持。
在加载模块中找到一个未启用的SafeSEH 模块也不是一件很困难的事情。
(3)这种情况下我们有两种思路可以考虑,一是清空安全S.E.H 表,造成该模块未启用SafeSEH 的假象;二是将我们的指令注册到安全S.E.H 表中。由于安全S.E.H 表
的信息在内存中是加密存放的,所以突破它的可能性也不大,这条路我们就先放弃吧。
利用SafeSEH的缺陷
利用S.E.H 的终极特权!这种安全校验存在一个严重的缺陷——如果S.E.H 中的异常函数指针指向堆区,即使安全校验发现了S.E.H 已经不可信,仍然会调用其已被
修改过的异常处理函数,因此只要将shellcode 布置到堆区就可以直接跳转执行!
绕过SafeSEH
1.攻击返回地址绕过
2.虚函数绕过
3.从堆中绕过 :shellcode布置在堆中 ,SEH处理函数指向这个地址即可
4.利用未启用SafeSEH模块绕过 :可以把这个模块的指令作为跳板,去执行shellcode
5.加载模块之外的地址绕过 :内存中有一些Map类型的映射文件,在这些文件中找到跳板指令覆盖SEH处理函数地址即可绕过
6.利用未启用SafeSEH的控件,且控件包含溢出漏洞可以被触发(IE浏览器控件)
2、实践利用加载模块之外的地址
1.我们使用上一篇中的代码,稍微修改来测试,关闭GS DEP ASLR, 开启 SafeSEH ,如果你有VC6 ,最好使用它来编译。
- #include <stdio.h>
- #include <Windows.h>
-
- int zero = 0;
-
- int MyException()
- {
- printf("Error OverFlow %d\n", zero);
- return 1;
- }
-
- void __stdcall test(char* str, char* out)
- {
- char buf[0x500] = { 0 };
-
- __try
- {
- strcpy(buf, str);
- zero = 1 / zero;
-
- }
- __except (MyException())
- {
-
- }
- }
-
-
- int main(int arc, char** argv)
- {
- char buf1[200];
- test(argv[1], buf1);
- return 0;
- }
复制代码
2.先用IDA查看一下代码,因为我用VS2019编译, 编译器会扩展SEH。
可以看到, 这里使用了第3代的异常处理模型 ,往栈中放入了不少东西,会影响我们的偏移。
用od插件搜索一下,都开启了SafeSEH保护。
3.调试一下看看,可以看到,输入0x500个字节的A后, 还差12个字节才可以覆盖到Handler。
修改参数 ,再次调试查看。
好的,现在可以看到,Handler已经被覆盖为 C , 那么现在需要找到跳板地址来跳到shellcode。
之前已经看过,所有模块都启用了SafeSEH,那么我们需要找到加载模块之外的跳板地址,内存映射查看 MAP类型的地址。
那么我们需要什么样的跳板指令呢?
观察寄存器,发现eax指向我们溢出的缓冲区,那么是否可以利用 jmp eax , call eax,来跳转到shellcode执行(答案是不行,eax是一个易失寄存器,在转到异常处理函数的过程中会被修改)。
好的, Next先不管,Handler需要什么样的跳板指令呢,按照之前利用SEH的总结, 我们需要 pop pop ret指令。
随便填写一个地址测试是否成功转到该地址 ,我们在MAP类型内存映射中,找到了 0x7FFA5BE8地址,7FFA2017 它的指令是 jmp eax。
好的,修改Handler为这个地址, 看一下是否可以转到这个地址执行 ,答案是可以,但是无法下断跟踪(且提示访问0地址)。
之后我又选择了一个 pop ret指令的地址, 没办法,只能找到这个指令了,推算一下, 也就是 jmp [esp+4]。
根据微软的解释 EstablisherFrame 是此函数的固定堆栈分配的基地址 ,也就是我们得到的地址是 系统设置的异常处理函数的ebp(好吧,日后详细研究一下)。
好的好的,可以看到程序已经转到栈上执行,如果有合适的跳板指令可以利用(没办法了,我使用win10进行测试)。
3、结语
1.可以看到限制我们进行漏洞利用的因素有很多,我们不得不研究新的手段来对抗微软的保护机制。
2.经过测试,如果你不是使用加载模块地址之外的地址,确实会与safeSEH表来进行对比,会提示异常 无效的异常处理程序。
3.经过这次实践,碰到了各种各样的问题,此时才能理解前人的智慧,不得不佩服。
|
|