SafeSEH原理及绕过技术浅析
SafeSEH原理及绕过技术浅析
作者:magictong
时间:2012年3月16日星期五
摘要:主要介绍SafeSEH的基本原理和SafeSEH的绕过技术,重点在原理介绍。
关键词:SafeSEH;绕过技术;异常处理
目录
前言
SafeSEH的保护原理
(1) 二进制层面
(2) 系统层面
怎么关掉编译器的SafeSEH支持
怎样检测一个PE文件是否启用了SafeSEH
绕过方法简介
参考文献
前言
设计SafeSEH保护机制的目的,以为了防止那种攻击者通过覆盖堆栈上的异常处理函数句柄,从而控制程序执行流程的攻击。
自Windwos XP SP2之后,微软就已经引入了SafeSEH技术。不过由于SafeSEH需要编译器在编译PE文件时进行特殊支持才能发挥作用,而xpsp2下的系统文件基本都是不支持SafeSEH的编译器编译的,因此在xpsp2下,SafeSEH还没有发挥作用(VS2003及更高版本的编译器中已经开始支持)。
从Vista开始,由于系统PE文件基本都是由支持SafeSEH的编译器编译的,因此从Vista开始,SafeSEH开始发挥他强大的作用,对于以前那种简单的通过覆盖异常处理句柄的漏洞利用技术,也就基本失效了。
SafeSEH的保护原理
SafeSEH的基本原理很简单,即在调用异常处理函数之前,对要调用的异常处理函数进行一系列的有效性校验,如果发现异常处理函数不可靠(被覆盖了,被篡改了),立即终止异常处理函数的调用。不过SafeSEH需要编译器和系统双重支持,缺少一个则保护能力基本就丧失了。下面从两个方面来阐述怎样来实现SafeSEH。
(1)二进制层面
首先我们先看看编译器做了些什么事情(通过启用链接选项/SafeSEH即可使编译出来的二进制文件具备SafeSEH功能,微软VS2003及以后的编译器已经默认支持)。在编译器生成二进制IMAGE的时候,把所有合法的SEH函数的地址解析出来,在IMAGE里生成一张合法的SEH函数表,用于异常处理时候进行严格的匹配检查。可以使用VC下面的dumpbin工具查看一个二进制文件的config信息,这样调用dumpbin /loadconfig file_all_path_filename。

输出的可能是下面这样(注:tttt.exe使用vs2005编译):
Dump of file H:\Prj_N\tttt\Release\tttt.exe
File Type: EXECUTABLE IMAGE
Section contains the following load config:
00000048 size
0 time date stamp
0.00 Version
0 GlobalFlags Clear
0 GlobalFlags Set
0 Critical Section Default Timeout
0 Decommit Free Block Threshold
0 Decommit Total Free Threshold
00000000 Lock Prefix Table
0 Maximum Allocation Size
0 Virtual Memory Threshold
0 Process Heap Flags
0 Process Affinity Mask
0 CSD Version
0000 Reserved
00000000 Edit list
00403018 Security Cookie
00402360 Safe Exception Handler Table
1 Safe Exception Handler Count
Safe Exception Handler Table
Address
--------
004018A1 __except_handler4
Summary
1000 .data
1000 .rdata
1000 .rsrc
1000 .text
注意里面加粗标红的部分,这就是该二进制文件里面的SEH异常处理函数地址表。上面的输出实际上涉及如下的一个结构,是保存在二进制文件里面的一份配置表:
#include <windows.h>
extern DWORD_PTR __security_cookie; /* /GS security cookie */
/*
* The following two names are automatically created by the linker for any
* image that has the safe exception table present.
*/
extern PVOID __safe_se_handler_table[]; /* base of safe handler entry table */
extern BYTE __safe_se_handler_count; /* absolute symbol whose address is
the count of table entries */
typedef struct {
DWORD Size;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
DWORD DeCommitFreeBlockThreshold;
DWORD DeCommitTotalFreeThreshold;
DWORD LockPrefixTable; // VA
DWORD MaximumAllocationSize;
DWORD VirtualMemoryThreshold;
DWORD ProcessHeapFlags;
DWORD ProcessAffinityMask;
WORD CSDVersion;
WORD Reserved1;
DWORD EditList; // VA
DWORD_PTR *SecurityCookie;
PVOID *SEHandlerTable;
DWORD SEHandlerCount;
} IMAGE_LOAD_CONFIG_DIRECTORY32_2;
const IMAGE_LOAD_CONFIG_DIRECTORY32_2 _load_config_used = {
sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_2),
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
&__security_cookie,
__safe_se_handler_table,
(DWORD)(DWORD_PTR) &__safe_se_handler_count
};
(2)系统层面
基本过程如下(XP SP2和VISTA一样)。
加载准备过程:
加载PE文件时,定位和读出合法SEH函数表的地址(如果该IMAGE是不支持SafeSEH的,则这个SEH函数表的地址为0),并使用共享内存中的一个随机数加密。将加密后的SEH函数表地址,IMAGE的开始地址,IMAGE的长度,合法SEH函数的个数,作为一条记录放入ntdll(ntdll模块是进行异常分发的模块)的加载模块数据内存中。
异常发生后,异常处理过程如下(RtlDispatchException框架伪码):
void RtlDispatchException(...)
{
if (exception record is not on the stack)
goto corruption;
if (handler is on the stack)
goto corruption;
if (RtlIsValidHandler(handler, process_flags) == FALSE)
goto corruption;
// execute handler
RtlpExecuteHandlerForException(handler, ...)
...
}
RtlDispatchException()这个函数的检测主要分三步,首先检查异常处理节点是否在栈上,如果不在栈上程序将终止异常处理,其次检查异常处理句柄是否在栈上,如果在栈上程序将止异常处理,这两个检测可以防止那种在堆上伪造异常链和把shellcode放置在栈上的情况。最后检测handler的有效性,这才是SafeSEH的重点。
下面看一下RtlIsValidHandler的伪码(vista sp1):
BOOL RtlIsValidHandler(handler)
{
if (handler is in an image)
{
// 在加载模块的进程空间
if (image has the IMAGE_DLLCHARACTERISTICS_NO_SEH flag set)
return FALSE; // 该标志设置,忽略异常处理,直接返回FALSE
if (image has a SafeSEH table) // 是否含有SEH表
if (handler found in the table)
return TRUE; // 异常处理handle在表中,返回TRUE
else
return FALSE; // 异常处理handle不在表中,返回FALSE
if (image is a .NET assembly with the ILonly flag set)
return FALSE; // .NET 返回FALSE
// fall through
}
if (handler is on a non-executable page)
{
// handle在不可执行页上面
if (ExecuteDispatchEnable bit set in the process flags)
return TRUE; // DEP关闭,返回TRUE;否则抛出异常
else
raise ACCESS_VIOLATION; // enforce DEP even if we have no hardware NX
}
if (handler is not in an image)
{
// 在加载模块内存之外,并且是可执行页
if (ImageDispatchEnable bit set in the process flags)
return TRUE; // 允许在加载模块内存空间外执行,返回验证成功
else
return FALSE; // don't allow handlers outside of images
}
// everything else is allowed
return TRUE;
}
对上面的伪码的理解,请看代码注释和流程图:

伪码里面的ExecuteDispatchEnable和ImageDispatchEnable位标志是内核KPROCESS结构的一部分,这两个位用来控制当异常处理函数在不可以执行内存或者不在异常模块的映像(IMAGE)内时,是否执行异常处理函数。这两个位的值可以在运行时修改,不过默认情况下如果进程的DEP被关闭,则这两个位置1,如果进程的DEP是开启状态,则这两个位被置0。
在进程的DEP是开启的情况,有两种异常处理函数被异常分发器认为是有效的:
(a)异常处理函数在进程映像的SafeSEH表中,并且没有NO_SEH标志。
(b)异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。
在进程的DEP关闭的情况下,有三种情况异常处理函数被异常分发器认为是有效的:
(a)异常处理函数在进程映像的SafeSEH表中,并且没有NO_SEH标志。
(b)异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。
(c)异常处理函数不在当前进程的映像里面,但是不在当前线程的堆栈上。
这里的伪码是非常简单的,还有很多值得探讨的问题,譬如ntdll里面,当前异常处理句柄是怎么和SEH表进行对比的等等,不过这暂时不列入讨论。
怎么关掉编译器的SafeSEH支持
虽然我不知道你为什么要这么做,而且我觉得很疯狂,但是方法还是有的,在编译器的属性框Liker|CommandLine的Additional options 加入/SAFESEH:NO即可,见下图。

怎样检测一个PE文件是否启用了SafeSEH
前面介绍过数据目录里面的一个结构(IMAGE_LOAD_CONFIG_DIRECTORY),该结构的成员SEHandlerTable是指向合法SEH处理程序地址列表的指针,成员SEHandlerCount是数目。而IMAGE_LOAD_CONFIG_DIRECTORY这个结构体只有/SAFESEH选项设置了才存在,因此,就可以根据它来判断PE文件是否加了/SAFESEH链接选项。这个结构在PE中的偏移由PE附加头IMAGE_DATA_DIRECTORY 数组的第11项指定。
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
绕过方法简介
(1)利用堆地址覆盖SEH结构绕过SafeSEH
上面讲过,在禁用DEP的进程中,异常分发器允许SEH handler位于除栈空间之外的非映像页面。也就是说我们可以把shellcode放置在堆中,然后通过覆盖SEH跳至堆空间以执行shellcode,这样即可绕过SafeSEH保护。
(2)利用没有启用SafeSEH保护的模块绕过SafeSEH
在介绍原理时讲过,在国内,目前大部分的PC都是安装的Windows XP,也就是说对于大部分Windows操作系统,其系统模块都没有受到SafeSEH保护,可以选用未开启SafeSEH保护的模块来利用,另外,现在还有很多VC6编译的软件,这些软件本身和自带的dll文件,都是可能没有SafeSEH保护的。这时就可以使用它里面的指令作为跳板来绕过SafeSEH。
(3)利用加载模块之外的地址绕过SafeSEH
同样是根据SafeSEH的原理可知,对于加载模块之外的地址,SafeSEH同样是不进行有效性检测的(当然假设是DEP是关闭的,或者DEP已经被绕过)。
注:绕过方法这里没有细讲,原因是没有找到很好的例子,在《0day安全:软件漏洞分析技术》上面有自己书籍作者自己写的例子。以后这块再详说。
参考文献
[1] Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP
[2] SafeSEH笔记http://pstgroup.blogspot.com/2007/08/tipssafeseh.html
[3] /SAFESEH (Image has Safe Exception Handlers)
http://msdn.microsoft.com/en-us/library/9a89h429(VS.80).aspx
[4] 0day安全:软件漏洞分析技术(第二版)
[5] Bypassing Browser Memory Protections
[6] pecoff_v8
https://blog.csdn.net/magictong/article/details/7517630
写的挺好,但IMAGE_LOAD_CONFIG_DIRECTORY这个结构体并不是只有/SAFESEH选项设置了才存在,更准确的检查是检测这个结构不存在,或才存在时SEHandlerTable是否为0
SafeSEH原理及绕过技术浅析的更多相关文章
- [转]STUN和TURN技术浅析
[转]STUN和TURN技术浅析 http://www.h3c.com.cn/MiniSite/Technology_Circle/Net_Reptile/The_Five/Home/Catalog/ ...
- Linux_x86下NX与ASLR绕过技术
本文介绍Linux_x86下NX与ASLR绕过技术,并对GCC的Stack Canaries保护技术进行原理分析. 本文使用存在漏洞代码如下: /* filename : sof.c */ #incl ...
- UAC 实现原理及绕过方法-打洞专用
首页 新随笔 订阅 管理 随笔 - 7 文章 - 0 评论 - 0 UAC 实现原理及绕过方法 目录 0x01 UAC 实现方法(用户登陆过程)0x02 UAC 架构0x03 触发UAC0x0 ...
- 《Python3反爬虫原理与绕过实战》作者韦世东
可以用(k1,k2)-k1来设置,如果有重复的key,则保留key1,舍弃key2/打印appleMap{1=Apple{id=1,name=苹果1,money=3.25,num=10},2=Appl ...
- Web安全--XSS现代WAF规则探测及绕过技术
XSS现代WAF规则探测及绕过技术初始测试 1.使用无害的payload,类似<b>,<i>,<u>观察响应,判断应用程序是否被HTML编码,是否标签被过滤,是否过 ...
- typecho流程原理和插件机制浅析(第二弹)
typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...
- typecho流程原理和插件机制浅析(第一弹)
typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...
- 爬虫技术浅析 | WooYun知识库
爬虫技术浅析 | WooYun知识库 爬虫技术浅析 好房通ERP | 房产中介软件最高水准领导者 undefined
- 爬虫技术浅析 | z7y Blog
爬虫技术浅析 | z7y Blog 爬虫技术浅析
随机推荐
- 学习《零基础入门学习Python》电子书PDF+笔记+课后题及答案
初学python入门建议学习<零基础入门学习Python>.适合新手入门,很简单很易懂.前一半将语法,后一半讲了实际的应用. Python3入门必备,小甲鱼手把手教授Python,包含电子 ...
- OpenJDK源码研究笔记(十):枚举的高级用法,枚举实现接口,竟是别有洞天
在研究OpenJDK,Java编译器javac源码的过程中,发现以下代码. 顿时发现枚举类竟然也有如此"高端大气上档次"的用法. 沙场点兵(用法源码) com.sun.tools. ...
- 【Henu ACM Round#18 A】 Multiplication Table
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 遍历i从1..n 看看x%i==0以及x/i<=n是否成立. [代码] #include <iostream> u ...
- SGU 323 Aviamachinations
Aviamachinations Time Limit: 4500ms Memory Limit: 65536KB This problem will be judged on SGU. Origin ...
- 怎样用redis实现分布式锁
引子 redis作为一个强大的key/value数据库.事实上还能够用来实现轻量级的分布式锁. 1.实现方案1 最早官方在SETNX命令页给了一个实现: acquire lock: SETNX loc ...
- android 图片特效处理之图片叠加
这篇将讲到图片特效处理的图片叠加效果.跟前面一样是对像素点进行处理,可参照前面的android图像处理系列之七--图片涂鸦,水印-图片叠加和android图像处理系列之六--给图片添加边框(下)-图片 ...
- css+ js 实现圆环时钟
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- HDP和HDF
参考文档: HDP安装: 官方文档:https://docs.hortonworks.com/HDPDocuments/Ambari-2.5.0.3/bk_ambari-installation/co ...
- js17---创建对象:构造函数式和原型组合模式、动态原型模式、稳妥构造函数式
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- Android开发经验之获取画在画布上的字符串长度、宽度(所占像素宽度)
Android中获取字符串长度.宽度(所占像素宽度) 计算出当前绘制出来的字符串有多宽,可以这么来! 方法1: Paint paint = new Paint(); Rect rect = new R ...