/SafeSEH编译选项 : 原理及绕过技术浅析

2024-02-05 19:58

本文主要是介绍/SafeSEH编译选项 : 原理及绕过技术浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

/SafeSEH编译选项

x86 : XP支持/Win7支持

x64 : 不支持


摘要:主要介绍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 SP2VISTA一样)。

加载准备过程:

加载PE文件时,定位和读出合法SEH函数表的地址(如果该IMAGE是不支持SafeSEH的,则这个SEH函数表的地址为0),并使用共享内存中的一个随机数加密。将加密后的SEH函数表地址,IMAGE的开始地址,IMAGE的长度,合法SEH函数的个数,作为一条记录放入ntdllntdll模块是进行异常分发的模块)的加载模块数据内存中。

异常发生后,异常处理过程如下(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;

}

 

对上面的伪码的理解,请看代码注释和流程图:

伪码里面的ExecuteDispatchEnableImageDispatchEnable位标志是内核KPROCESS结构的一部分,这两个位用来控制当异常处理函数在不可以执行内存或者不在异常模块的映像(IMAGE)内时,是否执行异常处理函数。这两个位的值可以在运行时修改,不过默认情况下如果进程的DEP被关闭,则这两个位置1,如果进程的DEP是开启状态,则这两个位被置0

在进程的DEP是开启的情况,有两种异常处理函数被异常分发器认为是有效的:

a)异常处理函数在进程映像的SafeSEH表中,并且没有NO_SEH标志。

b)异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NETILonly标志。

在进程的DEP关闭的情况下,有三种情况异常处理函数被异常分发器认为是有效的:

a)异常处理函数在进程映像的SafeSEH表中,并且没有NO_SEH标志。

b)异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NETILonly标志。

c)异常处理函数不在当前进程的映像里面,但是不在当前线程的堆栈上。

这里的伪码是非常简单的,还有很多值得探讨的问题,譬如ntdll里面,当前异常处理句柄是怎么和SEH表进行对比的等等,不过这暂时不列入讨论。

 

怎么关掉编译器的SafeSEH支持

虽然我不知道你为什么要这么做,而且我觉得很疯狂,但是方法还是有的,在编译器的属性框Liker|CommandLineAdditional 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安全:软件漏洞分析技术》上面有自己书籍作者自己写的例子。以后这块再详说。

 

如何使用dumpbin.exe检测是否开启 : 

系统环境变量Path添加C:\Program Files (x86)\Microsoft Visual Studio 8\VC\bin和C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDE


参考文献

[1] Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP

http://blogs.technet.com/b/srd/archive/2009/02/02/preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx(可以列入翻译计划)

[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

这篇关于/SafeSEH编译选项 : 原理及绕过技术浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/681998

相关文章

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

Java利用JSONPath操作JSON数据的技术指南

《Java利用JSONPath操作JSON数据的技术指南》JSONPath是一种强大的工具,用于查询和操作JSON数据,类似于SQL的语法,它为处理复杂的JSON数据结构提供了简单且高效... 目录1、简述2、什么是 jsONPath?3、Java 示例3.1 基本查询3.2 过滤查询3.3 递归搜索3.4

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

浅析CSS 中z - index属性的作用及在什么情况下会失效

《浅析CSS中z-index属性的作用及在什么情况下会失效》z-index属性用于控制元素的堆叠顺序,值越大,元素越显示在上层,它需要元素具有定位属性(如relative、absolute、fi... 目录1. z-index 属性的作用2. z-index 失效的情况2.1 元素没有定位属性2.2 元素处

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操