首先有几点问题

1.在后文中看到的PE的节中的配置信息表Load configuration是对SEH回调函数的注册,那么Exception Table是加载的什么信息。

2.什么时候走进系统异常,什么时候走进自己注册的异常回调函数。

摘自 《加密与解密》和《Windows PE权威指南》

0x01 Windows结构化异常处理

  结构化异常处理是Windows OS处理程序错误或异常的技术。

  SEH是系统发现异常或错误时,在终结应用程序之前给应用程序的一个最后改正错误的机会,就是系统给终结程序之前给程序的一个执行其与设定的回调函数的机会。

  由于SEH使用了与硬件平台相关的数据指针,所以在不同的硬件平台上,SEH的实现方法是不同的。X86平台上的SEH处理框架中,把异常分为两类

    • 硬异常
    • 软异常

  硬异常分为

    (1)因执行指令失败引起的故障(Fault)异常,比如除以0,以及eip指向不可执行的页面。

    (2)执行了自陷指令的陷阱(Trap)异常,比如int 3指令。

    (3)无法恢复的严重出错,终止(Abort)异常,比如硬件故障引发的异常,或者系统表中出现了错误值引发的异常。

  软异常

    就是一函数调用的手段来模拟一次异常,即通过windows提供的API函数RaiseException,执行函数引发软异常。

VOID RasieException(
DWORD dwExceptionCode, //标识所引发异常的代码
DWORD dwExceotionFlags, //异常继续是否执行的标识
DWORD nNumberOfArguments, //参数个数
CONST DWORD *lpArguments //指向参数缓冲区的指针
)

0x02 用户模式下的异常处理

  用户模式设计到了几个关键的数据结构。

  TEB(Thread Environment Block,线程环境块)在windows 9x系列中称为TIB,它记录着现成的重要信息,每一个线程都对应着一个TEB结构,在windows 2000 DDK中定义为

typedef struct _NT_TIB{
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //指向SEH链入口
PVOID StackBase; //基址地址
PVOID StackLimit; //栈大小
PVOID SubSystemTib;
Union{
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPotiner;
struct _NT_TIB *Self; //NIT_TIB结构自身的线性地址
}NT_TIB;

  其中ExceptionList指向一个EXCEPTION_REGISTRATION结构,该结构定义为

EXCEPTION_REGISTRATION struc
prev dd ? ;指向前一个EXCEPTION_REGISTRATION结构的指针
handler dd ? ;当前异常处理回调函数地址
EXCEPTION_REGISTRATION ends

  与异常处理相关的项是指向EXCEPTION_REGISTRATION结构的指针ExceptionList,正好位于TEB的偏移0处。Windows创建线程时,OS会为每个线程分配TEB,而且都将FS段选择器指向当前线程的TEB数据,这就为程序提供了存取TEB数据的途径,即TEB总是有[FS:0]指向。

  当异常发生时,系统从NT_TIB中取出第一个字段,然后依据该字段获取第一个异常处理程序句柄Handler,并根据其中的地址调用该回调函数。

  异常发生在用户空间,在内核中的异常处理程序就会由内核态函数KiKernelTrapHandler改变为用户态函数KiUserTrapHandler。该函数构造异常记录块,然后转交给函数KiDispatchException来处理。用户模式下只有一个类似于内核空间的KiDispatchException函数,它就是动态链接库ntdll.dll中的KiUserExceptionDispatcher,它是用户模式下SEH异常处理的总入口。

KiUserTrapHandler(
PKTRAP_FRAME Tf,
ULONG ExceptionNr,
PVOID Cr2
)
KiDispatchException(
PEXCEPTION_RECORD ExceptionRecord, //指向ExceptionRecord指针
PKEXCEPTION_FRAME ExceptionFrame, //对x86 为NULL
PKTRAP_FRAME TrapFrame, //陷阱框架指针
KPROCESSOR_MODE PreviousMode, //模式
BOOLEAN FirstChance //是否为进行的第一次努力
)

  可是,尽管是发生于用户空间的异常,对异常的初期响应和处理毕竟是在内核中,现在的目的就是要从内核中的KiDispatchException()启动用户空间这个函数的执行。

  对于内核中的KiDispatchException(),这就是针对用户空间异常的主要操作。不过具体的实现还要再复杂一些,就像针对系统空间异常一样,内核中涉及用户空间异常的处理也分三步:

    第一步、参数FirstChance为1时,先通过KdpEnterDebuggerException()交由内核调试程序(Kernel Debugger)处理。如果内核调试程序解决了问题、或者认为无需提交用户空间,则返回值就是kdContinue,这就行了。否则就要把异常提交给用户空间,由用户空间的程序加以处理。

    第二步、然而,万一用户空间处理不了,例如ExceptionList中没有安排下可以认领、处理本次异常的节点,就会通过RtlRaiseException()、从而通过系统调用ZwRaiseException()发起一次“软异常”(见后),把问题交还内核。此时CPU再次进入KiDispatchException(),但是此时的实际参数FirstChance为0,所以直接进入第二步措施。在Windows内核中,这第二次努力是通过进程间通信向用户空间的调试程序(Debugger)发送一个报文、将其唤醒,由调试程序作进一步的处理。例如,对于由用户空间调试程序设置的断点(INT3),就只能由用户空间调试程序加以处理。不过,在ReactOS 0.3.0版的代码中这一步尚未实现,所以这里有个注释说:“FIXME: Forward the exception to the debugger for 2nd chance”。

    第三步、如果用户空间调试程序不存在,或者也不能解决,那就属于不可恢复的问题了。于是就有第三步措施,那就是通过ZwTerminateThread()结束当前线程的运行。正常情况下针对当前线程本身的ZwTerminateThread()是不返回的;而倘若竟然返回了,那对于整个系统都是不可恢复的问题了,所以通过宏操作KEBUGCHECKWITHTF()显示出错信息、转储(Dump)当时的内存映像,并进入一个Ke386HaltProcessor()的无限循环。换言之,整个系统就“死”了。

  显然,这里最关键的一步、也是最有希望的一步,是把异常提交给用户空间。怎么提交呢?首先要把上下文数据结构Context和异常纪录块ExceptionRecord拷贝到用户空间堆栈上去,再在用户空间堆栈上安上两个指针,分别指向这两个数据结构的用户空间副本,并相应调整异常框架中的用户空间堆栈指针。下面就会看到,这两个指针将被用作用户空间的函数调用参数。

  最后、也是最关键的,则是把异常框架中的用户空间返回地址设置成函数指针KeUserExceptionDispatcher所指向的函数。顺利完成了这些准备以后,就把局部量UserDispatch设成1,因此紧接着就从本次异常处理返回了。当然,这是返回到了指针KeUserExceptionDispatcher所指向的函数中。已经熟悉APC机制的读者应该很容易由此联想到对用户空间APC函数的调用。事实上也确实非常相似,如果说APC相当于对用户空间软件的中断机制,则异常的提交就相当于对用户空间软件的异常机制。

  当CPU从异常返回,回到用户空间时,就进入了函数KiUserExceptionDispatcher,该函数就是用户模式下的异常响应/处理程序入口。

  下面是KiUserExceptionDispatcher的调用过程:

KiUserExceptionDispatcher()

  RtlDispatchException()   //(对ExceptionList扫描处理)  该函数位于KiDispatchException中

     RtlpIsValidHandler()             //异常处理函数指针安全验证

     RtlpExecuteHandlerForException(); //尝试处理异常

     ExecuteHandler()
RtlDispatchException(
IN PEXCEPTION_RECORD ExceptionRecord, //EXCEPTION_RECORD结构
IN PCONTEXT Context //CONTEXT结构
)

  当异常发生时,OS向引起异常的现成的堆栈里压入EXCEPTION_POINTERS结构,此结构包含两个指针,一个指向EXCEPTION_RECORD,一个指向CONTEXT结构

typedef struct _EXCEPTION_POINTERS{
EXCEPTION_RECORD ExceptionRecord DWORD ? //指向EXCEPTION_RECORD结构
CONTEXT ContextRecord DWORD ?//指向CONTEXT结构
}

  其中EXCEPTION_RECORD结构

EXCEPTION_RECORD STRUCT{
DWORD ExceptionCode ;异常代码
DWORD ExceptionFlags ;异常标志
Struct EXCEPTION_RECORD ;指向另外一个EXCEPTION_RECORD的指针
PVOID ExceptionAddress ;异常发生的地址
DWORD NumberParameters ;下面ExceptionInformation所含有的dword数目
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]
}EXCEPTION_RECORD ENDS

  ExceptionCode字段定义了产生异常的原因,也可以自定义ExceptionCode

  CONTEXT结构是Win32 API一个几乎唯一与处理器结构相关的结构,包括了线程运行时处理器各主要寄存器的完整镜像,用于保存线程运行时环境。

0x03 内核模式下的异常处理

  内核模式下,fs[0x00]也是一个异常处理链表的指针,KPCR结构中的第一个成分是KPCR_TIB数据结构,ExceptionList则是KPCR_TIB结构中的第一个字段,以下是定义

Typedef struct _KPCR_TIB{
PVOID ExceptionList,
PVOID StackBase,
PVOID StackLimit,
PVOID SubSystemTib,
_ANONYMOUS_UNION union
{
PVOID FiberData,
DWORD Version
}DUMMYUNIONNAME
PVOID ArbitraryUserPointer,
Struct _NT_TIB *Self
}
Typedef struct _EXCEPTION_REGISTRATION_RECORD
{
Struct _EXCEPTION_REGISTRATION_RECORD *Next,
PEXCEPTION_ROUTINE Handler //SEH异常处理毁掉函数指针
}

  认为_KiTrapHandler是公共的异常处理函数,是因为大部分异常处理都是把它当成内核异常的入口,也有例外,比如14号异常(页异常),该异常的处理入口为函数_KiPageFaultHandler。公共的异常处理函数最终会根据CPU在发生异常时所处的地址空间而定,用户层调用函数_KiUserTrapHandler。如果是内核层,调用_KiKernelTrapHandler。

  下面是内核函数_KiUserTrapHandler的代码

ULONG
KiKernelTrapHandler(PKTRAP_FRAME Tf,ULONG ExceptionNr,PVOID Cr2)
{
EXCEPTION_RECORD Er;
Er.ExceptionFlags = ;
Er.ExceptionRecord = NULL;
Er.ExceptionAddress = (PVOID)Tf->Eip;
If(ExceptionNr ==) //页异常需要单独处理
{
Er.ExceptionCode = STATUS_ACCESS_VIOLATION;
Er.NumberParameters = ;
Er.ExceptionInformation[] = Tf->ErrCode & 0x1;
Er.ExceptionInformation[] = (ULONG)Cr2;
}
else
{ If(ExceptionNr<ARRAY_SIZE(ExceptionToNtStatus))
{
Er.ExceptionCode = ExceptionToNtStatus[ExceptionNr];
}
else
{
Er.ExceptionCode = STATUS_ACCESS_VIOLATION;
}
Er.NumberParameters = ;
}
Er.ExceptionFlags = ;
KiDispatchException(&Er,NULL,Tf,KernelMode,TRUE);
return ();
}

  该函数构造了异常记录块EXCEPTION_RECORD,然后调用KiDispatchException

EXCEPTION_RECORD STRUCT{
DWORD ExceptionCode ;异常代码
DWORD ExceptionFlags ;异常标志
Struct EXCEPTION_RECORD ;指向另外一个EXCEPTION_RECORD的指针
PVOID ExceptionAddress ;异常发生的地址
DWORD NumberParameters ;下面ExceptionInformation所含有的dword数目
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]
}EXCEPTION_RECORD ENDS

  KiDispatchException函数原型为

NTAPI

KiDispatchException(
PEXCEPTION_RECORD ExceptionRecord, //指向ExceptionRecord指针
PKEXCEPTION)FRAME ExceptionFrame, //对x86 NULL
PKTRAP_FRAME TrapFrame, //陷阱框架指针
KPROCESSOR_MODE PreviousMode, //用户模式还是内核模式
BOOLEAN FirstChance //是否为进行的第一次
)

  该函数既是内核模式下异常处理最后调用函数,也是用户模式下异常处理函数KiUserTrapHandler最后调用的函数。

  ①第一次尝试,FirstChance=1,异常会先提交给调试程序,如果调试程序不存在或调试程序也不能解决该异常,就调用函数RtlDispatchException进行实质性的SEH处理。

  ②SEH对应有三种可能,A如果异常被某个SEH框架接受,并实施长程跳转(Handler回调函数),程序就不用返回,B如果异常被某个SEH框架接受,但是程序认为对该异常只需执行善后函数,这样程序就会从RtlDispatchException(对ExceptionList扫描处理)返回,返回值为true。C异常被所有SEH框架都拒绝接受,第一次失败。

  ③第二次和第一次尝试失败后,程序再次提交给调试程序,通过调用其他的调试支持判断是否可以处理该异常,如果成功,返回常数KdContinue,否则第三次尝试

  第三次尝试表示系统已经没有办法处理故障,系统会显示出错信息,并且将出错信息转储到文件中。

  SEH处理核心就是对ExceptionList(异常处理链表)的扫描处理,由函数RtlDispatchException完成,此函数的调用位于函数KiDispatchException中。

  内核模式下异常处理调用关系如下

 _KiTrapHandler
_KiKernelTrapHandler
_KiDispatchException //三次尝试
_RtlDispatchException //对ExceptionList扫描处理
_RtlpGetExceptionList
_RtlpExecuteHandlerForException //尝试循环执行异常处理

0x04 PE中加载配置信息

IMAGE_LOAD_CONFIG_DIRECTORY STRUCT
Characteristics dd ? ;加载配置属性,一般为48h
TimeDateStamp dd ? ;时间戳
MagorVersion dw
MinorVersion dw
..
IMAGE_LOAD_CONFIG_DIRECTORY ENDS

  加载配置信息中首先注册源代码中定义的安全SEH回调函数

SEH结构的更多相关文章

  1. 异常处理第三讲,SEH(结构化异常处理),异常展开问题

    异常处理第三讲,SEH(结构化异常处理),异常展开问题 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 不知道昨天有木有 ...

  2. 第25章 SEH结构化异常处理_未处理异常及向量化异常

    25.1 UnhandledExceptionFilter函数详解 25.1.1 BaseProcessStart伪代码(Kernel32内部) void BaseProcessStart(PVOID ...

  3. 第24章 SEH结构化异常处理_异常处理及软件异常

    24.1  程序的结构 (1)try/except框架 __try{ //被保护的代码块 …… } __except(except fileter/*异常过滤程序*/){ //异常处理程序 } (2) ...

  4. 第23章 SEH结构化异常处理(3)_终止处理程序

    23.3 终止处理程序 23.3.1 程序的结构 (1)框架 __try{ //被保护的代码块 …… } __finally{ //终止处理 } (2)__try/__finally的特点 ①fina ...

  5. 第23章 SEH结构化异常处理(2)_编译器对系统SEH机制的封装

    23.2 编译器层面对系统SEH机制的封装 23.2.1 扩展的EXCEPTION_REGISTRATION级相关结构:VC_EXCEPTION_REGISTRATION (1)VC_EXCEPTIO ...

  6. 第23章 SEH结构化异常处理(1)_系统SEH机制

    23.1 基础知识 23.1.1 Windows下的软件异常 (1)中断和异常 ①中断是由外部硬件设备或异步事件产生的 ②异常是由内部事件产生的,可分为故障.陷阱和终止三类. (2)两种异常处理机制: ...

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

    SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 __try { //要保 ...

  8. 构造SEH来实现跳转-转载

    下面的代码出自CSDN Delphi版的一高人(kiboisme 蓝色光芒) procedure ExceptProc{ExceptionRecord,SEH,Context,DispatcherCo ...

  9. 利用SEH防范BP(int 3)断点

    利用SEH技术实现反跟踪,这个方法比单纯用判断API函数第一个字节是否为断点更加有效,可以防止在API函数内部的多处地址设置断点 通过int 3指令故意产生一个异常,从而让系统转入自己的异常处理函数, ...

随机推荐

  1. c# 读取txt文档和写入文档的方法

    StreamReader sr = new StreamReader(path); //path是要读取的文件的完整路径 String str_read = sr.ReadToEnd(); //从开始 ...

  2. 关于MultiDataTrigger和MultiTrigger的一些注意事项

    他俩有着相同的语法. 都是在conditions中编写触发条件. 因为都是同一个触发类. 在conditions中有Property和Binding这两个属性.那么这两个可以同时使用吗?当然是不可以的 ...

  3. Mysql初识数据库《五》初识sql语句

    初识sql语句 有了mysql这个数据库软件,就可以将程序员从对数据的管理中解脱出来,专注于对程序逻辑的编写 mysql服务端软件即mysqld帮我们管理好文件夹以及文件,前提是作为使用者的我们,需要 ...

  4. VIN-Fusion config with Realsense D435i

    ### First shot Copy the .launch file in package VINS-Fusion to the directory of realsense2_cameara/l ...

  5. HTML5新增的表单元素有哪些?

    表单控:color ,  calendar  ,  date ,  datetime, datetime-local,  time, mouth , week, email, url , search ...

  6. ceph_osd故障检测

    1.     当前monitor可以通过3种途径检测到osd离线 1)      Osd自主上报 2)      Osd通过投票的方式(满足一下条件之一,mon会将osd标记为down) a)     ...

  7. flink学习笔记-flink实战

    说明:本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKh ...

  8. 学习TypeScript,笔记一:TypeScript的简介与数据类型

    该文章用于督促自己学习TypeScript,作为学笔记进行保存,如果有错误的地方欢迎指正 2019-03-27  16:50:03 一.什么是TypeScript? TypeScript是javasc ...

  9. 5.js与jQuery入口函数执行时机

    js与jQuery入口函数执行时机区别: JS入口函数是在所有资源加载完成后,才执行.(包括:页面.外部js文件.外部css文件.图片) jQuery入口函数,是在文档加载完成后就执行.文档加载完成指 ...

  10. redis原理及实现

    1 什么是redis redis是nosql(也是个巨大的map) 单线程,但是可处理1秒10w的并发(数据都在内存中) 使用java对redis进行操作类似jdbc接口标准对mysql,有各类实现他 ...