SEH X64(1)

来源:互联网 发布:网络教育专升本统考 编辑:程序博客网 时间:2024/05/29 18:05

           开门见山的介绍X64-SEH 的结构:相比于X86 在程序运行中动态构建SEH结构,X64-SEH 是静态的,其信息包含在PE文件中。下面我们首先看一下对应的结构,然后看看其提供的信息是否能够满足异常捕获以及异常处理的功能。

为异常处理和调试器支持展开数据

执行异常处理和调试支持所需的数据结构

RUNTIME_FUNCTION

typedef struct _RUNTIME_FUNCTION_ {
    DWORD BeginAddress;                             // 函数起始地址
    DWORD EndAddress;                                // 函数结束地址
    DWORD UnwindInfoAddress;               // 展开信息地址看下面的_UNWIND_INFO 结构体   
}RUNTIME_FUNCTION , *_RUNTIME_FUNCTION ;

该结构在内存中必须为DWORD 对齐。所有的地址都是ImageBase 的 RVA 值。这些项已经经过排序了(按照BeginAddress升序排列),放置在PE32+ 的 .pdata节中。对于动态生成的函数,我们暂时不介绍。

UNWIND_INFO

//
// Define unwind information flags.
//

#define UNW_FLAG_NHANDLER 0x0
#define UNW_FLAG_EHANDLER 0x1
#define UNW_FLAG_UHANDLER 0x2
#define UNW_FLAG_CHAININFO 0x4

上面四个标志依次代表:

既没有EXCEPT_FILTER也没有EXCEPT_HANDLER

有EXCEPT_FILTER 和 EXCEPT_HANDLER

有 FINALLY_HANDLER

有多个UNWIND_INFO 串联在一起,

typedef struct _UNWIND_INFO {
    UCHAR Version : 3;                     // 版本信息,当前为1
    UCHAR Flags : 5;                          // 对应上面的四个标志
    UCHAR SizeOfProlog;                 // Prolog 的大小,单位是字节
    UCHAR CountOfCodes;              // UNWIND_INFO 包含多少UNWIND_CODE结构
    UCHAR FrameRegister : 4;  
    UCHAR FrameOffset : 4; 
    UNWIND_CODE UnwindCode[1];

//
// unwind codes 数组后面是一个可选的DWROD 对齐的成员。此成员有两种可能,异常处理函数地址或者function table entry(flags中指定了UNW_FALGS_CHAININFO),如果是异常处理函数地址的话,它将为一个语言相关的异常处理数据
//
//  union {
//      struct {
//          ULONG ExceptionHandler;
//          ULONG ExceptionData[];
//      };
//
//      RUNTIME_FUNCTION FunctionEntry;
//  };
//

} UNWIND_INFO, *PUNWIND_INFO;

UNWIND_INFO 结构体必须是DWORD 对齐的。

FrameRegister

如果不是0 的话,这个函数使用了帧指针,该值表示帧指针使用的非易失性寄存器的数目。与UNWIND_CODE中的成员使用相同的编码。

FrameOffset

如果FrameRegister 不为0,表示在刚建立栈帧时,应用于FP 寄存器的RSP 的缩放偏移量。实际的FP 为RSP+16*当前值,范围是0~240。这样允许将FP 寄存器指向本地动态栈帧的中间位置,然后通过更短的指令来提高代码密度(更多的 指令将可以使用8位带符号偏移形式)。

Unwind code 数组

这一系列的code 代表了prolog 中对于非易失性寄存器和RSP 寄存器的影响。由于对齐, 这个数组始终有偶数个,最后的一个成员可能未被使用。

Exception Handler

ExceptionHandler 该域为一个RVA,指向exception handler的地址,ExceptionData 指向一个类似于scopetable 的地址

如果Flags指定的是 UNW_FLAG_CHAININFO,该域为 RUNTIME_FUNCTION

UNWIND_CODE

typedef enum _UNWIND_OP_CODES {      UWOP_PUSH_NONVOL = 0, /* info == register number */      UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */      UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */      UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */      UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */      UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */      UWOP_SAVE_XMM128,     /* info == XMM reg number, offset in next slot */      UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */      UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */  } UNWIND_CODE_OPS;    typedef union _UNWIND_CODE {      struct {          UBYTE CodeOffset;          UBYTE UnwindOp : 4;          UBYTE OpInfo   : 4;      };      USHORT FrameOffset;  } UNWIND_CODE, *PUNWIND_CODE;  
UNWIND_CODE结构体用来记录在proglog 中影响非易失性寄存器和RSP寄存器的序列。每个UNWIND_CODE有上述结构。其中,分别表示本操作在prolog中的offset,Unwind操作码,操作信息。该数组的排列按照prolog中的offset的降序排列。
 
 有些展开操作代码需要本地栈帧的一个无符号偏移。这个偏移是相对于固定栈申请而言的。如果UNWIND_INFO的Frame Register 成员为0,offset 是对RSP而言的,否则,offset 是相对于在建立栈帧的时候RSP 被存储的的地方。此时需要栈帧-栈帧寄存器的偏移(16*缩放帧寄存器在UNWIND_INFO中的偏移)。如果FP 寄存器被使用,所有使用offset的unwind code必须在prolog建立栈帧之后才可以使用。
 
除了UWOP_SAVE_XMM128 和 UWOP_SAVE_XMM128_FAR,所有的offset 是8 的倍数。 对于offset小于512KB 的操作码,nodes里面的最后一个ushort存储的是offset / 8。对于512KB <= offset < 4GB 的operation codes 来说,nodes中最后的两个ushort nodes存储的是offset(小尾方式)。  
对于 UWOP_SAVE_XMM128 和 UWOP_SAVE_XMM128_FAR ,offset 为16的倍数。因此,UMOD_SAVE_XMM128 使用比例因子16,允许小于1M 的偏移量。
展开操作码为下列的一部分:
1. UWOP_PUSH_NONVOL 1 node
push 一个非易失性 integer 寄存器,rsp 减8。operation info 是进存起的数目。因为epilogs 的限制,UWOP_PUSH_NONVOL unwind codes 必须出现在prolog 的开始的地方,相应地,在最后的展开代码数组的最后的位置。该相对排序适用于除了UWOP_PUSH_MACHFRAME之外的所有其他展开代码。
 
2. UWOP_ALLOC_LARGE 2 or 3 nodes
申请一个大区域的栈区。有两种形式,如果operation info = 0,堆栈大小/8 存储在下一个slot中,允许分配高达512K-8。 如果operation info = 1,未缩放的大小被以小尾数记录在接下来的两个slots中,大小为4GB-8。
 
3. UWOP_ALLOC_SMALL 1 node
申请一个小的栈区大小,分配的大小是operation info 字段*8 + 8,允许从8 到 128 字节。
 
栈分配的炸开代码必须使用最短的编码。
 
4. UWOP_SET_FPREG 1 node
通过将寄存器设置为当前RSP 的某些偏移来建立帧指针寄存器。这个偏移= UNWIND_INFO 中的成员Frame Register 的偏移*16,允许的值为0~240.使用偏移允许简历指向固定栈分配中间的帧指针,通过允许更多的访问使用短指令形式来帮助提高代码密度。此时operation info 字段是保留字段,不应该被使用。

5. UWOP_SAVE_NONVOL 2 nodes
使用MOV 指令而不是PUSH 来将寄存器存储在栈中。operation info 是寄存器的编号,下一个展开operation code 中存储的是栈偏移/8。

6. UWOP_SAVE_NONVOL_FAR 3 nodes
同上,只不过此时offset 比较大。offset 被存储在接下来的两个展开operation code 中,跟之间的类似。

7. UWOP_SAVE_XMM128 2 nodes
在栈中存储一个非易失性寄存器 XMM 寄存器的128 bits。operation info 是寄存器的数字,下一个slot 存储的是offset / 16。

8. UWOP_SAVEXMM128_FAR 3 nodes
跟之前类似,不过offset 更大。

9. UWOP_PUSH_MACHFRAME 1 node
PUSH 一个 machine frame。用来记录一个硬件中断或者异常的影响。两种形式,operation info = 0:
下面的信息被PUSH 进栈
image
如果operation info = 1 :
下面的信息被PUSH 进栈
image
这个展开代码是假的prolog,它刚开始并不会执行,直到中断程序的入口代码之前才会出现,它的存在仅仅是为了模拟PUSH 操作。UWOP_PUSH_MACHFRAME 记录这个模拟的过程,表明在概念上做了如下工作:
POP RIP 从栈顶返回地址到Temp
PUSH SS
PUSH Old RSP
PUSH EFLAGS
PUSH CS
PUSH Temp
PUSH Error Code(如果OP = 1)
模拟的UWOP_PUSH_MACHFRAME 操作将RSP-40(OP=1)或者RSP-48(OP=1)
operation info
这4个bits 取决于上面的operation code。要对通用的整数寄存器进行编码,使用下面的映射
image

前面介绍过了,unwind 数据描述prolog 的动作,且提供足够的信息来撤销prolog 所做的操作。这样的信息就满足了unwind----即展开操作的需要。回想前面我们介绍X86SEH 的时候,展开就是之前操作的逆操作,即将程序状态恢复到prolog之前的状态。上面所描述的结构都是为达到这一目服务的。
另外,前面提到的“叶子函数“是没有RUNTIME_FUNCTION结构的,因为它不需要。
除了上面的结构,我们应该了解下面这个比较熟悉的结构

typedef struct _SCOPE_TABLE {
    ULONG Count;
    struct
    {
        ULONG BeginAddress;
        ULONG EndAddress;
        ULONG HandlerAddress;
        ULONG JumpTarget;
    } ScopeRecord[1];
} SCOPE_TABLE, *PSCOPE_TABLE;

它类似于X86 中的scopetable 的类型。

Count 表示ScopeRecord数组的大小,ScopeRecord 就是ScopeEntry。BeginAddress和EndAddress 表示一个__try 对应的区域。HandlerAddress 和JumpTarget 含义如下:

操作 HandlerAddress JumpTarget __try/__except EXCEPT_FILTER EXCEPT_HANDLER __try/__finally FINALLY_HANDLER 0 上面的四个域通常都是RVA。 当EXCEPT_FILTER 简单地返回或等于EXCEPTION_EXECUTE_HANDLER时,HandlerAddress = EXCEPTION_EXECUTE_HANDLER而不是RVA
简单介绍上面的结构之后我们来看PE 文件中上述结构与SEH 的对应。

#include <stdio.h>
#include <Windows.h>
int main()
{
    _try { 
        *(PDWORD)0 = 0;
    } 
    _except( EXCEPTION_EXECUTE_HANDLER ) { 
         printf( "Caught the exception in main()\n" ); 
    } 
    return 0;
}


对应的汇编代码为:

.text:0000000140001000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:0000000140001000 main            proc near               ; CODE XREF: __tmainCRTStartup+115p
.text:0000000140001000                                         ; DATA XREF: .pdata:ExceptionDiro
.text:0000000140001000                 sub     rsp, 28h
.text:0000000140001004
.text:0000000140001004 loc_140001004:                          ; DATA XREF: .rdata:00000001400021F8o
.text:0000000140001004                 mov     dword ptr ds:0, 0
.text:000000014000100F                 jmp     short loc_14000101F
.text:0000000140001011 ; ---------------------------------------------------------------------------
.text:0000000140001011
.text:0000000140001011 loc_140001011:                          ; DATA XREF: .rdata:00000001400021F8o
.text:0000000140001011                 lea     rcx, Format     ; "Caught the exception in main()\n"
.text:0000000140001018                 call    cs:printf
.text:000000014000101E                 nop
.text:000000014000101F
.text:000000014000101F loc_14000101F:                          ; CODE XREF: main+Fj
.text:000000014000101F                 xor     eax, eax
.text:0000000140001021                 add     rsp, 28h
.text:0000000140001025                 retn
.text:0000000140001025 main            endp

PE 文件分析如下

image
我们看到其RVA 为 0x4000
从上面RUNTIME_FUNCTION 结构定义我们知道,其大小为0xC,而且该结构体在PE中是按照函数起始地址升序排列的。
image
image
其中0x21E8 对应的结构体如下:
直接的数据如下:
image
IDA识别之后如下:
image
其中,0x9 二进制为 1001,对应Version 为1,Flags 为1即UNW_FLAG_EHANDLER。SizeOfProlog 为4字节,UNWIND_CODE结构体个数为1,FrameRegister 和 FrameOffset 都为0。
结构体UNWIND_CODE 中CodeOffset 为4,即偏移为4。UnwindOp 为2,即UWOP_ALLOC_SMALL,OpInfo 为4即申请大小为(4+1)*0x8 = 0x28.
刚好与上面的汇编代码对应。0xDEE 和 oxDEF 处的 00是为了UNWIND_INFO的对齐而填充的,上面我们介绍过了。
然后我们看ExceptionHandler 对应的是 0x00001026
对应的代码为
image
image
这个函数对应的为MSVCR100 的导出函数。
SCOPE_TABLE 中Count对应为0x00000001,即下面的ScopeRecord结构数量为1。BeginAddress 为 0x001004,EndAddress 为0x00001011。HandlerAddress为0x00000001 ,JumpAddress为0x00001011。由于我们的ExceptFilter为直接的EXCEPT_EXECUTE_HANDLER,因此ExceptFilter值直接为1,而不是过滤函数的RVA。ExceptHandler的地址就是对应于上面的printf( "Caught the exception in main()\n" );  语句。
  到这里我们静态文件的简单分析就结束了。
下面再给出几个宏定义,方便操作。#define GetUnwindCodeEntry(info, index) \      ((info)->UnwindCode[index])    #define GetLanguageSpecificDataPtr(info) \      ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))    #define GetExceptionHandler(base, info) \      ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))    #define GetChainedFunctionEntry(base, info) \      ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))    #define GetExceptionDataPtr(info) \      ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1)