x64 SEH分析
一些结构体描述都可以在这里找到–> https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-160
__try { |
实验文件
链接:https://pan.baidu.com/s/1jjJIoFOLP_gWYzui0pNqjg
提取码:5anj
|
filter |
ExceptionDirectory
x64 seh不再基于栈,效率高也安全,编译进了pe文件里的OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]即第三项异常表,RVA指向的是一个IMAGE_IA64_RUNTIME_FUNCTION_ENTRY的结构体数组
根据上方VirtualAddress在SectionHeaders里找到位置在.pdata节
计算出在pe文件里地址为7200h
ida里已经帮我们分析好了(对着函数找交叉引用就可以找到)
RUNTIME_FUNCTION 结构体
typedef _IMAGE_RUNTIME_FUNCTION_ENTRY IMAGE_IA64_RUNTIME_FUNCTION_ENTRY; |
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { |
每个非叶函数至少对应一个 RUNTIME FUCNTION结构体
x64里的叶函数:既不调用函数、又没有修改栈指针,也没有使用 SEH
DUMMYUNIONNAME成员指向了_UNWIND_INFO
typedef struct _UNWIND_INFO { |
Version
展开数据的版本号,目前都是1
Flags
对于不同Flags后面UnwindCode是不同的
UNW_FLAG_NHANDLER
后面是常规UnwindCode,直接回滚,抛给上层函数
比如exc函数的_UNWIND_INFO
UNW_FLAG_EHANDLER 或者 UNW_FLAG_UHANDLER
除了后面是常规UnwindCode,最后一个 UNWIND_CODE 之后存放着 ExceptionHandler(相当于 x86 EXCEPTION_REGISTRATION::handler)和 ExceptionData(相当于 x86 EXCEPTION_REGISTRATION::scopetable)。
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 { |
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压入寄存器的标号
exec()异常回滚过程
首先找到exec函数的RUNTIME_FUNCTION的UNWIND_INFO结构体
FLAGS=0=UNW_FLAG_NHANDLER,不处理异常,根据UNWIND_CODE回滚
首先和SizeOfProlog比较,如果大于等于,直接所有_UNWIND_INFO按照顺序都回滚即可,否则就需要和每一个_UNWIND_INFO.CodeOffset比较看哪一个需要回滚
本例中异常发送处超过SizeOfProlog=4就把rsp加上(2+1)*8=0x18,然后根据函数调用栈找到main,找到main函数的RUNTIME_FUNCTION的UNWIND_INFO结构体
异常发送处都在两个_SCOPE_TABLE的try块范围中,两个都处理异常,要处理就不需要回滚了
第一个是JmpTarget是0,所以是finally,在此处进行记录。直到找到filter接管后,再执行finally再EXCEPT*_HANDLER*
reference
https://www.pediy.com/kssd/pediy12/142371.html