x64 SEH分析

一些结构体描述都可以在这里找到–> https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160

__try {
// 这里是被保护的代码
}
__except (过滤器表达式) { //异常过滤
// 异常处理程序
}

实验文件

链接:https://pan.baidu.com/s/1jjJIoFOLP_gWYzui0pNqjg
提取码:5anj

#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
int filter() {
printf("filter\n");
return EXCEPTION_EXECUTE_HANDLER;
}
void exc() {
int x = 0;
int y = x / x;
}

int main() {
__try {
__try {
exc();
}
__finally {
printf("111\n");
}
}
__except (filter()) {
printf("222\n");
}
system("pause");
return 0;
}

filter
111
222
请按任意键继续. . .

ExceptionDirectory

x64 seh不再基于栈,效率高也安全,编译进了pe文件里的OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]即第三项异常表,RVA指向的是一个IMAGE_IA64_RUNTIME_FUNCTION_ENTRY的结构体数组image-20231108143547506

根据上方VirtualAddress在SectionHeaders里找到位置在.pdata节

image-20231108143712472

计算出在pe文件里地址为7200h

image-20231108143755950

ida里已经帮我们分析好了(对着函数找交叉引用就可以找到)

image-20231108144111996

RUNTIME_FUNCTION 结构体

typedef  _IMAGE_RUNTIME_FUNCTION_ENTRY  IMAGE_IA64_RUNTIME_FUNCTION_ENTRY;
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
DWORD BeginAddress;//函数开头的地址RVA
DWORD EndAddress;//函数末尾的地址RVA
union {
DWORD UnwindInfoAddress;
DWORD UnwindData;
} DUMMYUNIONNAME;//描述上方指定函数异常处理和调用堆栈展开信息的地址RVA
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;

每个非叶函数至少对应一个 RUNTIME FUCNTION结构体

x64里的叶函数:既不调用函数、又没有修改栈指针,也没有使用 SEH

DUMMYUNIONNAME成员指向了_UNWIND_INFO

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;

Version

展开数据的版本号,目前都是1

Flags

#define UNW_FLAG_NHANDLER       0x0 //该函数没有过滤器表达式也没有异常处理程序即不对异常进行处理,抛给上层
#define UNW_FLAG_EHANDLER 0x1 //该函数有过滤器表达式和异常处理程序处理异常
#define UNW_FLAG_UHANDLER 0x2 //该函数有finally块
#define UNW_FLAG_CHAININFO 0x4 //该函数有多个 UNWIND_INFO,它们串接在一起

对于不同Flags后面UnwindCode是不同的

  • UNW_FLAG_NHANDLER

    后面是常规UnwindCode,直接回滚,抛给上层函数

    比如exc函数的_UNWIND_INFO

    image-20231108225901821

  • UNW_FLAG_EHANDLER 或者 UNW_FLAG_UHANDLER

    除了后面是常规UnwindCode,最后一个 UNWIND_CODE 之后存放着 ExceptionHandler(相当于 x86 EXCEPTION_REGISTRATION::handler)和 ExceptionData(相当于 x86 EXCEPTION_REGISTRATION::scopetable)。

    image-20231108230240059

    typedef struct _SCOPE_TABLE {
    ULONG Count;//ScopeRecord 数组的个数
    struct
    {
    ULONG BeginAddress;//表示某个 __try 保护域的范围
    ULONG EndAddress;//表示某个 __try 保护域的范围
    ULONG HandlerAddress;
    ULONG JumpTarget;//对于 __try/__except 组合,HandlerAddress 代表 EXCEPT_FILTER,JumpTarget 代表 EXCEPT_HANDLER。
    // 对于 __try/__finally 组合,HandlerAddress 代表 FINALLY_HANDLER,JumpTarget 等于 0。
    } ScopeRecord[1];
    } SCOPE_TABLE, *PSCOPE_TABLE;
  • UNW_FLAG_CHAININFO

    除了后面是常规UnwindCode,最后一个 UNWIND_CODE之后,指向了一个_RUNTIME_FUNCTION结构体,形成了链式结构

SizeOfProlog

该函数的 Prolog 指令的大小,单位是 byte

CountOfCodes

下方UWIND_CODE数组成员个数

FrameRegister TODO

FrameOffset TODO

UnwindCode


typedef union FrameOffset {
struct {
UCHAR CodeOffset;
UCHAR UnwindOp : 4;
UCHAR OpInfo : 4;
};

USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
  • CodeOffset 需要unwind位置在Prolog里的偏移

  • UnwindOp 表明怎么做unwind的类型

    • UWOP_ALLOC_SMALL 0x2 表示在堆栈上分配一个小区域 sub esp之类的操作
    • UWOP_PUSH_NONVOL 0 表示压入一个非易失性整数寄存器
  • OpInfo 根据UnwindOp不同有不同含义

    • UWOP_ALLOC_SMALL 分配了OpInfo* 8 + 8大小栈

    • UWOP_PUSH_NONVOL push压入寄存器的标号

      image-20231108222855349

exec()异常回滚过程

首先找到exec函数的RUNTIME_FUNCTION的UNWIND_INFO结构体

image-20231109000315377

FLAGS=0=UNW_FLAG_NHANDLER,不处理异常,根据UNWIND_CODE回滚

image-20231109000550567

首先和SizeOfProlog比较,如果大于等于,直接所有_UNWIND_INFO按照顺序都回滚即可,否则就需要和每一个_UNWIND_INFO.CodeOffset比较看哪一个需要回滚

本例中异常发送处超过SizeOfProlog=4就把rsp加上(2+1)*8=0x18,然后根据函数调用栈找到main,找到main函数的RUNTIME_FUNCTION的UNWIND_INFO结构体

image-20231109000915133

异常发送处都在两个_SCOPE_TABLE的try块范围中,两个都处理异常,要处理就不需要回滚了

第一个是JmpTarget是0,所以是finally,在此处进行记录。直到找到filter接管后,再执行finally再EXCEPT*_HANDLER*

reference

https://www.pediy.com/kssd/pediy12/142371.html

https://www.bilibili.com/video/BV1tJ411M7kd

https://www.anquanke.com/post/id/247688