C++ 代码:
#include "stdafx.h"
#include <windows.h>
ULONG WINAPI FilterFunc(DWORD dwExceptionCode)
{
return (dwExceptionCode == STATUS_INTEGER_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
}
int add1(int a,int b)
{
int c = 0,d=0;
__try
{
printf("a address=%p\n",&a);
__try
{
printf("b address=%p\n", &b);
c = a / b;
}
__except (FilterFunc(GetExceptionCode()))
{
printf("c address=%p\n", &c);
c = a / (a + b);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("d address=%p\n", &d);
c = c + 1;
}
return c;
}
#include <stdio.h>
#include <Windows.h>
int main()
{
DebugBreak();
add1(0,0);
return 0;
}
百度云代码:SEH_Sample.zip
1 X64 的基本概念和结构体
相比于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; //如果上面的Flags指定的是 UNW_FLAG_CHAININFO,该联合体为 RUNTIME_FUNCTION
};
} 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建立栈帧之后才可以使用。
2 查找add1函数的RUNTIME_FUNCTION
使用windbg
0:000> .fnent ConsoleApplication3!add1
Debugger function entry 00000000`003a0fc0 for:
d:\work\temp\consoleapplication3\consoleapplication3.cpp(14)
(00000001`3f501030) ConsoleApplication3!add1 | (00000001`3f5010e0) ConsoleApplication3!main
Exact matches:
ConsoleApplication3!add1 (int, int)
BeginAddress = 00000000`00001030
EndAddress = 00000000`000010d4
UnwindInfoAddress = 00000000`00002670
Unwind info at 00000001`3f502670, 10 bytes
version 1, flags 1, prolog c, codes 1
handler routine: ConsoleApplication3!_C_specific_handler (00000001`3f501e10), data 2
00: offs c, unwind op 2, op info 8 UWOP_ALLOC_SMALL.
根据上面的信息,结合Study_PE+,从.pdata section中找到对应的信息
2 .pdada Section中寻找add1 UNW_INFO的信息
我们知道add1的UNW_INFO 的RVA是2670,所以我们使用PE工具得到FOA
3 add1的 UNW_INFO 解析
000000001A70: 09 0C 01 00
09的二进制00