以栈作为基础的SEH本身具有很大的危险性,我们可以利用各种手段对栈上SEH节点进行覆盖重写,再次执行异常处理操作时就会将执行权给到了我们用来覆盖的函数上,这实际上在以前是很常见的windows栈溢出手段,当然,除了这种方法外还有许许多多的利用手段,可见这样的异常处理机制还是不够完善的。为了解决这些问题,微软逐步加入了Safe SEH、SEHOP、VCH等来弥补。
Safe SEH
SafeSEH又叫做软件DEP,是一种在软件层面实现的对SEH的保护机制,它需要操作系统和编译器的双重支持,在vs2013及以后的版本中会自动启用 /SafeSEH 链接选项来使用SafeSEH。也正是因为该项技术使得以往简单的覆盖异常处理句柄的漏洞利用几乎失效了
在加载PE文件时,SafeSEH将定位合法的SEH表的地址(如果该映像不支持SafeSEH的话则地址为0),然后是用共享内存中的一个随机数进行加密处理,程序中所有的异常处理函数的地址提取出来汇总放入SEH表,并将该表放入程序映像中,还会将将加密后的SEH函数表地址,IMAGE的开始地址,IMAGE的长度,合法SEH函数的个数,作为一条记录放入ntdll(ntdll模块是进行异常分发的模块)的加载模块数据内存中,每次调用异常处理函数时都会进行校验,只有二者一致才能够正常进行,该处理由RtlDispatchException() 开始,首先会经历两次检查,分别是:
- 检查异常处理链是否在当前的栈中,不是则终止
- 检查异常处理函数的指针是否指向栈,是则终止
通过两次检查后会调用RtlIsValidHandler() 来进行异常的有效性检查,08年的black hat给出了该函数的细节
BOOL RtlIsValidHandler( handler ) { if (handler is in the loaded image) // 是否在loaded的空间内 { if (image has set the IMAGE_DLLCHARACTERISTICS_NO_SEH flag) //是否设置了忽略异常 return FALSE; if (image has a SafeSEH table) // 是否含有SEH表 if (handler found in the table) // 异常处理函数地址是否表中 return TRUE; else return FALSE; if (image is a .NET assembly with the ILonl y flag set) return FALSE; } if (handler is on non-executable page) // handler是否在不可执行页上 { if (ExecuteDispatchEnable bit set in the process flags) //DEP是否开启 return TRUE; else raise ACCESS_VIOLATION; } if (handler is not in an image) // handler是否在未加载空间 { if (ImageDispatchEnable bit set in the process flags) //设置的标志位是否允许 return TRUE; else return FALSE; } return TRUE; /s/ 允许执行异常处理函数 }
代码中的ExecuteDispatchEnable和ImageDispatchEnable位标志是内核KPROCESS结构的一部分,这两个位用来控制当异常处理函数在不可以执行内存或者不在异常模块的映像(IMAGE)内时,是否执行异常处理函数。这两个位的值可以在运行时修改,不过默认情况下如果进程的DEP被关闭,则这两个位置1,如果进程的DEP是开启状态,则这两个位被置0。
通过源码我们可以看出,RtlIsValidHandler() 函数只会在以下几种情况执行异常处理函数
- 在进程的DEP是开启的情况下
- 异常处理函数和进程映像的SafeSEH表匹配且没有NO_SEH标志。
- 异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。
- 在进程的DEP关闭的情况下
- 异常处理函数和进程映像的SafeSEH表匹配没有NO_SEH标志。
- 异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。
- 异常处理函数不在当前进程的映像里面,但是不在当前线程的堆栈上。
SEHOP
全称为Structured Exception Handler Overwrite Protection(结构化异常处理覆盖保护),这是专门用来检测SEH是否被劫持的一项技术,我们在上面的RltDispatchExeption实际上已经提到过一些SEHOP的检测过程了,这里我们来具体说一说
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession Managerkernel
你可以在该表项下找到DisableExceptionChainValidation的键,它标示着你的计算机是否开启了该功能。
我们再次回到RltDispatchExeption来看看它的具体操作,代码来自Vistasp1
// Skip the chain validation if the DisableExceptionChainValidation bit is set if (process_flags & 0x40 == 0) { // Skip the validation if there are no SEH records on the linked list if (record != 0xFFFFFFFF) { // Walk the SEH linked list do { // 1、The record must be on the stack if (record < stack_bottom || record > stack_top) goto corruption; // 2、The end of the record must be on the stack if ((char*)record + sizeof(EXCEPTION_REGISTRATION) > stack_top) goto corruption; // 3、The record must be 4 byte aligned if ((record & 3) != 0) goto corruption; handler = record->handler; // 4、The handler must not be on the stack if (handler >= stack_bottom && handler < stack_top) goto corruption; record = record->next; } while (record != 0xFFFFFFFF); // End of chain reached // Is bit 9 set in the TEB->SameTebFlags field? This bit is set in // ntdll!RtlInitializeExceptionChain, which registers // FinalExceptionHandler as an SEH handler when a new thread starts. if ((TEB->word_at_offset_0xFCA & 0x200) != 0) { // 5、The final handler must be ntdll!FinalExceptionHandler if (handler != &FinalExceptionHandler) goto corruption; } } // end if (record != 0xFFFFFFFF) }
大家可自行阅读代码,概括来说主要是对以下几点的检测:
- SEH节点必须在栈上
- SEH节点的Handle必须不在栈上
- 最后的SEH节点的Handle必须是ntdll!FinalExceptionHandler,也就是咱们上面说的异常的最后一站
- 最后的SEH节点的Next指针必须为0xffffffff