x64的seh

来源:互联网 发布:vb中mid函数的使用方法 编辑:程序博客网 时间:2024/06/02 07:13

PatchGuard与异常处理密切相关,所以先看看x64的SEH

关于x86中内核如何捕获异常,分发异常,可以看http://blog.csdn.net/shevacoming/article/details/7580350


上面那篇没有跟进分析RtlDispatchException,这里跟进以复习x86 seh的结构,也看经典的这一篇 http://bbs.pediy.com/showthread.php?threadid=14042


x86的异常处理是链式存储,存储在FS[0]中的

cPublicProc _RtlpGetRegistrationHead    ,0;cPublicFpo 0,0        mov     eax,fs:PcExceptionList        stdRET    _RtlpGetRegistrationHeadstdENDP _RtlpGetRegistrationHead

seh的结构包含下一个嵌套的seh和回调函数地址
typedef struct _EXCEPTION_REGISTRATION_RECORD {    struct _EXCEPTION_REGISTRATION_RECORD *Next;    PEXCEPTION_ROUTINE Handler;} EXCEPTION_REGISTRATION_RECORD;
EXCEPTION_DISPOSITION__cdecl _except_handler(     struct _EXCEPTION_RECORD *ExceptionRecord,     void * EstablisherFrame,     struct _CONTEXT *ContextRecord,     void * DispatcherContext     );


x64不再基于链式存储seh,而是使用表式存储  可以看这篇文章 http://www.boxcounter.com/showthread.php?tid=74

PE64里面的IMAGE_RUNTIME_FUNCTION_ENTRY记录了一些信息

typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {  DWORD BeginAddress;  DWORD EndAddress;  DWORD UnwindInfoAddress;} _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;

为此我写了一个小程序

void MyPeView(){MAP_FILE_STRUCT FileContext;LoadFileR(L"notepad.exe",&FileContext);PIMAGE_RUNTIME_FUNCTION_ENTRY   pRtlFunc = (PIMAGE_RUNTIME_FUNCTION_ENTRY)GetDirectoryEntryToData(FileContext.ImageBase,IMAGE_DIRECTORY_ENTRY_EXCEPTION);for (int i=0;pRtlFunc[i].BeginAddress;i++){printf("BeginAddress 0x%x  EndAddress  0x%x  UnwindData 0x%x \n",pRtlFunc[i].BeginAddress,pRtlFunc[i].EndAddress,pRtlFunc[i].UnwindInfoAddress);}UnLoadFile(&FileContext);}

他输出的前几行与ida中一致,这个值是相对于基址的偏移。他记录了每一个函数的开头结尾以及异常信息。

BeginAddress 0x1000  EndAddress  0x10c8  UnwindData 0x13235
BeginAddress 0x10c8  EndAddress  0x11ff  UnwindData 0xeac0
BeginAddress 0x1234  EndAddress  0x129f  UnwindData 0xef68
BeginAddress 0x129f  EndAddress  0x12e8  UnwindData 0x13055
BeginAddress 0x12e8  EndAddress  0x1314  UnwindData 0x1300d
BeginAddress 0x1314  EndAddress  0x1364  UnwindData 0x1324d
BeginAddress 0x1364  EndAddress  0x13e0  UnwindData 0xeaa8
BeginAddress 0x13e0  EndAddress  0x14b1  UnwindData 0xeaf0
BeginAddress 0x14b1  EndAddress  0x1500  UnwindData 0x1300d
BeginAddress 0x1500  EndAddress  0x15d2  UnwindData 0xea48
BeginAddress 0x15d2  EndAddress  0x164f  UnwindData 0x1300d
BeginAddress 0x165f  EndAddress  0x1690  UnwindData 0x1300d
BeginAddress 0x1690  EndAddress  0x1712  UnwindData 0xec2c
BeginAddress 0x1712  EndAddress  0x171f  UnwindData 0x1300d
BeginAddress 0x171f  EndAddress  0x1782  UnwindData 0x13235
BeginAddress 0x1782  EndAddress  0x17b0  UnwindData 0x13049
BeginAddress 0x17b0  EndAddress  0x17e6  UnwindData 0x1306d
BeginAddress 0x17e6  EndAddress  0x180c  UnwindData 0x1300d
BeginAddress 0x180c  EndAddress  0x1a47  UnwindData 0xeb38
BeginAddress 0x1a47  EndAddress  0x1bfd  UnwindData 0x1306d
BeginAddress 0x1bfd  EndAddress  0x1c27  UnwindData 0x130d9
BeginAddress 0x1c27  EndAddress  0x1d75  UnwindData 0x1306d
BeginAddress 0x1d82  EndAddress  0x1dd0  UnwindData 0x1300d
BeginAddress 0x1dd0  EndAddress  0x1ec3  UnwindData 0xe9f8




接下来就看看Unwind Info是什么

    typedef struct _RUNTIME_FUNCTION {           ULONG BeginAddress;           ULONG EndAddress;           ULONG UnwindData;       } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;       typedef enum _UNWIND_OP_CODES {           UWOP_PUSH_NONVOL = 0,           UWOP_ALLOC_LARGE,       // 1           UWOP_ALLOC_SMALL,       // 2           UWOP_SET_FPREG,         // 3           UWOP_SAVE_NONVOL,       // 4           UWOP_SAVE_NONVOL_FAR,   // 5           UWOP_SPARE_CODE1,       // 6           UWOP_SPARE_CODE2,       // 7           UWOP_SAVE_XMM128,       // 8           UWOP_SAVE_XMM128_FAR,   // 9           UWOP_PUSH_MACHFRAME     // 10       } UNWIND_OP_CODES, *PUNWIND_OP_CODES;       typedef union _UNWIND_CODE {           struct {               UCHAR CodeOffset;               UCHAR UnwindOp : 4;               UCHAR OpInfo : 4;           };                  USHORT FrameOffset;       } UNWIND_CODE, *PUNWIND_CODE;              #define UNW_FLAG_NHANDLER 0x0       #define UNW_FLAG_EHANDLER 0x1       #define UNW_FLAG_UHANDLER 0x2       #define UNW_FLAG_CHAININFO 0x4       typedef struct _UNWIND_INFO {           UCHAR Version : 3;           UCHAR Flags : 5;           UCHAR SizeOfProlog;           UCHAR CountOfCodes;           UCHAR FrameRegister : 4;           UCHAR FrameOffset : 4;           UNWIND_CODE UnwindCode[1];              //       // The unwind codes are followed by an optional DWORD aligned field that       // contains the exception handler address or a function table entry if       // chained unwind information is specified. If an exception handler address       // is specified, then it is followed by the language specified exception       // handler data.       //       //  union {       //      struct {       //          ULONG ExceptionHandler;       //          ULONG ExceptionData[];       //      };       //       //      RUNTIME_FUNCTION FunctionEntry;       //  };       //              } UNWIND_INFO, *PUNWIND_INFO;       typedef struct _SCOPE_TABLE {           ULONG Count;           struct           {               ULONG BeginAddress;               ULONG EndAddress;               ULONG HandlerAddress;               ULONG JumpTarget;           } ScopeRecord[1];       } SCOPE_TABLE, *PSCOPE_TABLE;


关于unwind info 引用一下www.boxcounter.com 的解释:


       x64 中,MSC 为几乎所有的函数都登记了完备的信息,用来在展开过程中完整的回滚函数所做的栈、寄存器操作。登记的信息包括:
       函数是否使用了 SEH、
       函数使用的是什么组合的 SEH(__try/__except?__try/__finally?)、
       函数申请了多少栈空间、
       函数保存了哪些寄存器、
       函数是否建立了栈帧,
       等等,
       同时也记录了这些操作的顺序(以保证回滚的时候不会乱套)。


       这些信息就存储在 UNWIND_INFO 之中。
       UNWIND_INFO 相当于 x86 下的 EXCEPTION_REGISTRATION。它的成员分别是:
               Version —— 结构体的版本。
               Flags —— 标志位,可以有这么几种取值:
                       UNW_FLAG_NHANDLER (0x0): 表示既没有 EXCEPT_FILTER 也没有 EXCEPT_HANDLER。
                       UNW_FLAG_EHANDLER (0x1): 表示该函数有 EXCEPT_FILTER & EXCEPT_HANDLER。
                       UNW_FLAG_UHANDLER (0x2): 表示该函数有 FINALLY_HANDLER。
                       UNW_FLAG_CHAININFO (0x4): 表示该函数有多个 UNWIND_INFO,它们串接在一起(所谓的 chain)。
               SizeOfProlog —— 表示该函数的 Prolog 指令的大小,单位是 byte。
               CountOfCodes —— 表示当前 UNWIND_INFO 包含多少个 UNWIND_CODE 结构。
               FrameRegister —— 如果函数建立了栈帧,它表示栈帧的索引(相对于 CONTEXT::RAX 的偏移,详情参考 RtlVirtualUnwind 源码)。否则该成员的值为0。
               FrameOffset —— 表示 FrameRegister 距离函数最初栈顶(刚进入函数,还没有执行任何指令时的栈顶)的偏移,单位也是 byte。
               UnwindCode —— 是一个 UNWIND_CODE 类型的数组。元素数量由 CountOfCodes 决定。
       需要说明几点:
               1. 如果 Flags 设置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,那么在最后一个 UNWIND_CODE 之后存放着 ExceptionHandler(相当于 x86 EXCEPTION_REGISTRATION::handler)和 ExceptionData(相当于 x86 EXCEPTION_REGISTRATION::scopetable)。
               2. UnwindCode 数组详细记录了函数修改栈、保存非易失性寄存器的指令。
               3. MSDN 中有 UNWIND_INFO 和 UNWIND_CODE 的详细说明,推荐阅读。


       那 UNWIND_INFO 是如何与其描述的函数关联起来的呢?答案是:通过一个 RUNTIME_FUNCTION 结构体。
       RUNTIME_FUNCTION::BeginAddress 同 RUNTIME_FUNCTION::EndAddress 一起以 RVA 形式描述了函数的范围。
       RUNTIME_FUNCTION::UnwindData 就是 UNWIND_INFO 了,它也是一个 RVA 值。


       PE+ 中的 ExceptionDirectory 中存放着所有函数的 RUNTIME_FUNCTION,按 RUNTIME_FUNCTION::BeginAddress 升序排列。一旦触发异常,系统可以通过 EXCEPT_POINT 的 RVA 在 ExceptionDirectory 中二分查找到 RUNTIME_FUNCTION,进而找到 UNWIND_INFO。
       
       前面有提到,MSC 为几乎所有的函数都登记了完毕的信息,那是不是有一些特殊函数没有登记信息呢?
       是的。x64 新增了一个概念,叫做“叶函数”。熟悉数据结构的朋友可能第一时间就联想到“叶节点”。没错,“叶函数”的含义跟“叶节点”很类似,叶函数不会有子函数,也就是说它不会再调用任何函数。另外 x64 对这个概念额外加了一些要求:不修改栈指针(比如分配栈空间)、没有使用 SEH。总结下来就是:既不调用函数、又没有修改栈指针,也没有使用 SEH 的函数就叫做“叶函数”。
       叶函数可以没有登记信息,原因很简单,它根本就没信息需要登记~


       还有一个 SCOPE_TABLE 结构,熟悉 x86 SEH 的朋友应该很眼熟 :-),它等同于 x86 SEH 中的 REGISTRATIOIN_RECORD::scopetable 的类型。其成员有:
               Count —— 表示 ScopeRecord 数组的大小。
               ScopeRecord —— 等同于 x86 中的 scopetable_entry 成员。其中,
                       BeginAddress 和 EndAddress 表示某个 __try 保护域的范围。
                       HandlerAddress 和 JumpTarget 表示 EXCEPTION_FILTER、EXCEPT_HANDLER 和 FINALLY_HANDLER。具体对应情况为:
                               对于 __try/__except 组合,HandlerAddress 代表 EXCEPT_FILTER,JumpTarget 代表 EXCEPT_HANDLER。
                               对于 __try/__finally 组合,HandlerAddress 代表 FINALLY_HANDLER,JumpTarget 等于 0。
                       这四个域通常都是 RVA,但当 EXCEPT_FILTER 简单地返回或等于 EXCEPTION_EXECUTE_HANDLER 时,HandlerAddress 可能直接等于 EXCEPTION_EXECUTE_HANDLER,而不再是一个 RVA。


另外x64多了RtlVirtualUnwind可以虚拟执行完当前执行完当前抛出异常的函数,很高级。



参考:

Structured Exception Handling http://bbs.pediy.com/showthread.php?threadid=14042

Moving to Windows x64 http://bbs.pediy.com/showthread.php?t=145198

SEH分析笔记(X64篇)  http://www.boxcounter.com/showthread.php?tid=74

msdn http://msdn.microsoft.com/zh-cn/library/ft9x1kdx

《软件调试》,WRK

原创粉丝点击