栈溢出笔记1.9 认识SEH

从本节开始,我们就要研究一些稍微高级点的话题了,如同在1.2节中看到的,Windows中为抵抗栈溢出做了很多保护性的检查工作,编译的程序默认开启了这些保护。如果我们不能绕过这些保护,那么我们的Shellcode也就是一个玩具而已,什么都做不了。

我们从SEH(结构化异常处理)开始。

这篇文章讲SEH简洁易懂:http://www.securitysift.com/windows-exploit-development-part-6-seh-exploits/

因此,本文的前面部分就直接对其进行翻译了,后面动手的部分再结合自己的例子进行,因为动手实践还是用自己写的代码好。

(1)什么是结构化异常处理?

Windows下的硬件和软件异常统一采用结构化异常处理(SEH)机制。异常处理结构通常包含在一个try/except或try/catch代码块中。如下:

/*****************************************************************************/
__try {
    // 受保护的代码区域
    ...
}
__except (exception filter) {
    // 异常处理代码
    ...
}
/*****************************************************************************/

含义很简单,try保护的代码一定会执行,在发生指定的错误/异常之后,就执行except中的代码,进行异常处理。异常处理器(exception filter)就是告诉操作系统对指定的错误/异常执行什么操作。

异常处理器(exception filter)可能由应用程序实现(通过__try/__except结构),或者使用系统自带的。由于错误的种类很多(除0,越界等),对应的异常处理器也有很多。

所有种类的异常处理器,包括应用程序实现和操作系统实现的,都由Windows系统通过一些数据结构和函数进行统一管理。

(2)SEH的主要组成

每个异常处理器都对应一个EXCEPTION_REGISTRATION_RECORD结构,该结构如下:

这些异常处理器的EXCEPTION_REGISTRATION_RECORD结构连接在一起,组成一个SEH链表。EXCEPTION_REGISTRATION_RECORD结构中的第一个成员Next指向SEH链表中的下一个成员,因此,你可以通过Next来遍历SEH链。EXCEPTION_REGISTRATION_RECORD结构中的第二个成员Handler为一个异常处理函数的函数指针,该异常处理函数定义如下:

函数的第一个参数指向一个_EXCEPTION_RECORD结构。该结构保存了某个异常的相关信息,包括异常码,异常发生的地址,参数的个数等,如下:

_except_handler异常处理函数使用该结构中的信息(还有ContextRecord 参数中的寄存器信息)来判断该异常能否被SEH链中的某个异常处理器处理。EstablisherFrame 参数也很重要,后面会说到。

_except_handler异常处理函数返回EXCEPTION_DISPOSITION,如果为ExceptionContinueExecution,表示该异常是否已经被成功处理,如果为ExceptionContinueSearch,表示当前异常处理器无法处理该异常,异常移交给SEH链中的下一个异常处理器。

那么,异常处理机制是如何使用这些结构和函数来进行异常处理的呢?当一个异常发生的时候,操作系统从SEH链头部开始,检查第一个_EXCEPTION_REGISTRATION_RECORD(即异常处理器)的异常处理函数,看它能否处理该异常(通过ExceptionRecord 和ContextRecord参数)。如果不能,则移动到下一个_EXCEPTION_REGISTRATION_RECORD,继续检查,直到找到合适的异常处理器。Windows在SEH链的末尾放置了一个默认的通用异常处理器,保证异常肯定能被处理。如果使用默认的异常处理器处理,你通常会看到“程序遇到了一个问题,需要关闭…”之类的信息。

每个线程有它自己的SEH链。操作系统通过TEB中的ExceptionList成员定位SEH链的起始地址,TEB位于FS:[0]。下面为SEH链的一个示意图(图中简化了_EXCEPTION_REGISTRATION_RECORD结构):

图47 Windows SEH链

上图不是SEH机制的全部,但是足够你理解基本的原理。现在,我们用一个示例来看一看SEH机制。

好了,翻译到此为止,但是我后面所写的内容基本也就是原文的内容,只是我换了自己的示例,这样便于实际操作,基本上也就相当于翻译。我们找出1.2节中的example_2(具有栈溢出漏洞的那个程序),来看看它的SEH是什么样的。在Immunity Debugger中选择如下菜单:

图48 在Immunity Debugger查看SEH链

即可查看SEH链。我们看一看example_2的SEH链:

图49 example_2的SEH链

SEH的try/except或try/catch代码块实际上是宏定义的一段代码,将我们自己的代码包裹起来,因此,我们可以从当前线程的栈上来找到SEH链,对照上面的地址,找到它:

图50 栈上的SEH链

对照前面讲述的EXCEPTION_REGISTRATION_RECORD结构,Next成员为链中的下一个异常处理器地址,为0xFFFFFFFF表示已经结尾,即最后的一个默认异常处理器。0x7c839ac0为该默认异常处理器的异常处理函数地址。

回看example_2的代码,我们并没有定义自己的异常处理块(try/except或try/catch),因此,程序自带一个默认异常处理器。前面说到,每个线程都有一个异常处理链,而线程是动态变化的,随着指令流的进行,执行不同的代码块,调用函数等。那么,程序执行起来又是什么样子的呢?

为了回答上面的问题,我们再来看一看。这个程序有输入字符串的操作(gets),因此,我们让程序运行,到达等待输入的时刻,然后再来看SEH链:

图51 暂停于gets时刻的SEH链

好大一串。其中有系统的,有VS2008的,还有一个我们“自己”的,最后才是系统默认的。这些异常都是用来干嘛的?现在,我们把断点设在调用gets函数之后:

图53

看来,刚刚我们应该是看错了位置。我们前面是在gets函数等待输入的时候看的,也就是说停在了gets函数内部,而gets函数由编译器实现,因此,它内部包装有自己的异常处理,这就是图51中为什么我们看到了那么多系统和编译器提供的异常处理函数。看来,SEH链是在动态变化的,进入了包装有异常处理的代码,就会在SEH链中添加异常处理器,退出其代码块之后,又会从SEH链中删除异常处理器。这就是为什么说SEH链是与线程对应的。但是,既然我们自己没有定义异常处理,这里为什么还多出来一个?这个后面再说。

接下来,我们给example_2的程序包装一个异常处理块,然后再看看SEH链的样子:

/*****************************************************************************/
// example_10: 演示SEH链
#include <Windows.h>
#include <stdio.h>

void get_print()
{
    char str[11];

    __try
    {
        gets(str);
        printf("%s\n", str);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //
    }
}

int main()
{
    get_print();

    return 0;
}
/*****************************************************************************/

最初暂停的点:

图54

最初还是只有一个SEH链。同样在调用gets之后的语句暂停:

图55 example_10的SEH链

和图53对比,SEH链中多了一个节点,因为我们自己添加了一个异常处理块。现在还有一个疑问,多出来的那个是什么?按照SEH链的原理,局部的应该位于前面,因此,第一个是我们自己定义的,那第二个是哪里来的呢?(注意不要根据地址来和图53比较进行判断,现在已经是一个不同的程序了)它的异常处理函数地址为0x0041104B,明显位于本模块中。我们把断点设置调用 get_print()之前,也就是main函数中,来看:

图56

这个时候,第二个异常处理器就已经出现了,因此,这个异常处理器是main函数的,VC++实现main函数的时候也包装了一个异常处理块。你可以自己去找到是何时设置的。

我们来看看两个异常处理函数的地址,分别为0x411046和0x41104B:

图56

图57

第一个指向MSVCR90D.dll中的_except_handler3,第二个最终指向MSVCR90D.dll中的_except_handler4_common。这是VC++对SEH的实现,并非使用原生的SEH,要理解这个_except_handler3和_except_handler4_common,你需要这篇文章:https://www.microsoft.com/msj/0197/exception/exception.aspx。这篇经典的文章有中文翻译。

本节先到这里,下一节继续。

时间: 2024-08-12 22:47:55

栈溢出笔记1.9 认识SEH的相关文章

栈溢出笔记1.10 基于SEH的栈溢出

上节中简单地讲述了SEH的原理及逻辑结构.本节,要继续讲述SEH的物理结构及如何利用它进行栈溢出. 先来看SEH的物理结构.先回想上节中的图51,我们在程序停在gets函数输入的时候查看SEH链,看到了一大堆异常处理器,而当我们把断点设置在gets函数下一条语句的时候,其中很多没有了,这给我们一个直观的感觉:SEH链保存在栈上. 下面,我们就来看看栈上的SEH链.我们使用的是example_10,即添加了一个自己的异常处理块的程序(编译时继续采用前面教程中的设置,即关闭缓冲区安全检查).依然把断

栈溢出笔记1.4 黑掉example_2

在1.2节中我们编写了一个有漏洞的程序,通过输入可以控制其EIP,本节,我们要让example_2运行我们的MessageBox.再看看example_2: /*****************************************************************************/ // example_2: 演示栈溢出 #include <stdio.h> void get_print() { char str[11]; gets(str); printf(

栈溢出笔记1.1 函数调用过程

选择从栈溢出开始学习Shellcode的编写,是因为在没有保护机制(栈Cookie,ASLR,DEP,SafeSEH)的系统中使用栈溢出是一件很简单的事情.栈区随着函数调用动态变化,每个函数调用时在栈上占用的空间称为栈帧.用一个示例来说明栈上保存的内容及动态变化的过程. 下面是一个程序,生成一个对话框显示一条"Hello World!"消息.下面是该程序的C代码: 在VS2008中用Debug版编译之后,拖入Immunity Debugger中: 图1 example_1.exe入口点

栈溢出笔记1.2 覆盖EIP

1.1节中我们说到可以利用栈溢出来破坏栈中原有的内容,这一节中,我们就来看看如何争夺到返回地址(EIP),使得我们可以随意控制它的值,这样我们就可以控制程序.来看一个经典的程序: 这个程序的get_print函数中定义了一个大小为11个字节的数组,正常情况下我们的输入应该最多为10个字符(还有一个\0结束符),而gets函数没有明确定义输入的大小,因此,我们可以输入超过10个字符,从而造成栈溢出.如下,输入10个'A',一切正常: 图8 当我输入11个'A'时,虽然顺利打印出来11个'A',但是

栈溢出笔记1.3 准备Shellcode

经过1.1和1.2节的讲述,我们已经知道了怎样更改EIP的值. 程序运行函数之后将跳转到我们设定的位置開始运行,因此,我们须要准备一个自己的程序,接手后面的工作.这是一个什么样的程序?是一个C语言编写的代码?是一个可直接调用的exe?肯定不是,由于EIP所指的地址保存的内容为指令的操作码,CPU读取该操作码运行相应的操作. 所以我们要准备的程序也应该是一段"操作码". 继续写1.1中的Hello World.这次我们要把一个C语言编写的MessageBox换成一个仅仅有"操作

栈溢出笔记1.6 地址问题(1)

前面的Shellcode中,我使用的都是自己XP机器上的硬编码地址.不论什么时候在Shellcode中使用硬编码地址都不是个好主意,这一点与动态库的重定位相似,一旦系统环境和程序编译设置发生变化.Shellcode差点儿肯定会失效.因此.我们要找到更好一点的方法. 前面的Shellcode中,我用到了例如以下几个硬编码地址.它们的含义例如以下: 当中.LoadLibraryA的作用比較特殊,我们用它来载入user32.dll库. 如今我们要换掉这些硬编码地址.那么,怎样得到这些API函数的地址呢

第23章 SEH结构化异常处理(1)

23.1 基础知识 23.1.1 Windows下的软件异常 (1)中断和异常 ①中断是由外部硬件设备或异步事件产生的 ②异常是由内部事件产生的,可分为故障.陷阱和终止三类. (2)两种异常处理机制:SEH和VEH(WindowsXP以上新引进) (3)结构化异常处理(SEH)是Windows操作系统提供的强大异常处理功能.而Visual C++中的__try{} __finally{}和__try{} __except{}结构本质上是对Windows提供的SEH的封装. 23.1.2 SEH的

PE结构、SEH相关知识学习笔记

原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题 PE结构的学习 原文中用fasm自己构造了一个pe,这里贴一个用masm的,其实是使用WriteFile API将编写的PE数据写成文件~也没啥好说的,PE结构在这里没有仔细介绍,需要可以另外查询,剩下要说的的基本都在代码注释里了 参考:点击打开链接(PEDIY技术之新思路(二)_用'高级'编译器MASM实现自定义PE文件结构) Pe.asm: REMO

Windows内核读书笔记——SEH结构化异常处理

SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 1 __try 2 { 3 //要保护的代码 4 } 5 __finally 6 { 7 //终结处理块 8 } 退出保护块的方式:正常结束和非正常结束两种 1.正常结束 正常执行并顺序进入终结处理块称为正常结束 2.非正常结束 因为发生异常或是因为return.goto.break.continue等流程控制语句而离开被