/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

相关文章

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

Java 队列Queue从原理到实战指南

《Java队列Queue从原理到实战指南》本文介绍了Java中队列(Queue)的底层实现、常见方法及其区别,通过LinkedList和ArrayDeque的实现,以及循环队列的概念,展示了如何高效... 目录一、队列的认识队列的底层与集合框架常见的队列方法插入元素方法对比(add和offer)移除元素方法

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

Spring IOC核心原理详解与运用实战教程

《SpringIOC核心原理详解与运用实战教程》本文详细解析了SpringIOC容器的核心原理,包括BeanFactory体系、依赖注入机制、循环依赖解决和三级缓存机制,同时,介绍了SpringBo... 目录1. Spring IOC核心原理深度解析1.1 BeanFactory体系与内部结构1.1.1

python协程实现高并发的技术详解

《python协程实现高并发的技术详解》协程是实现高并发的一种非常高效的方式,特别适合处理大量I/O操作的场景,本文我们将简单介绍python协程实现高并发的相关方法,需要的小伙伴可以了解下... 目录核心概念与简单示例高并发实践:网络请求协程如何实现高并发:核心技术协作式多任务与事件循环非阻塞I/O与连接

MySQL 批量插入的原理和实战方法(快速提升大数据导入效率)

《MySQL批量插入的原理和实战方法(快速提升大数据导入效率)》在日常开发中,我们经常需要将大量数据批量插入到MySQL数据库中,本文将介绍批量插入的原理、实现方法,并结合Python和PyMySQ... 目录一、批量插入的优势二、mysql 表的创建示例三、python 实现批量插入1. 安装 PyMyS

浅析Python中如何处理Socket超时

《浅析Python中如何处理Socket超时》在网络编程中,Socket是实现网络通信的基础,本文将深入探讨Python中如何处理Socket超时,并提供完整的代码示例和最佳实践,希望对大家有所帮助... 目录开篇引言核心要点逐一深入讲解每个要点1. 设置Socket超时2. 处理超时异常3. 使用sele

Java编译错误java.lang.NoSuchFieldError的解决方案详析

《Java编译错误java.lang.NoSuchFieldError的解决方案详析》java.lang.NoSuchFieldError是Java中的一种运行时错误,:本文主要介绍Java编译错... 目录前言解决方案1. 统一JDK版本环境2. 优化maven-compiler-plugin配置3. 清

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

GO语言中gox交叉编译的实现

《GO语言中gox交叉编译的实现》本文主要介绍了GO语言中gox交叉编译的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、安装二、使用三、遇到的问题1、开启CGO2、修改环境变量最近在工作中使用GO语言进行编码开发,因