Windows逆向工程提升之IMAGE_RUNTIME_FUNCTION_ENTRY
- 公开视频 -> 链接点击跳转公开课程
- 博客首页 -> 链接点击跳转博客主页
目录
异常处理信息在 PE 文件中的存放
核心数据结构
RUNTIME_FUNCTION
UNWIND_INFO
UNWIND_CODE
SCOPE_TABLE
图解
与 x86 平台 SEH 的对比
x86 SEH
x64 SEH
执行图解
IMAGE_DATA_DIRECTORY
IMAGE_RUNTIME_FUNCTION
UNWIND_INFO
UNWIND_CODE
SCOPE_TABLE
异常处理信息在 PE 文件中的存放
- PE异常目录: 在 PE 文件的 Data Directory 中,IMAGE_DIRECTORY_ENTRY_EXCEPTION 指向异常表,通常位于 .pdata 段。该段中包含整个模块中所有函数的异常信息条目。
- 功能: Windows x64 不在每个函数的栈中设置 SEH 帧,而是在静态数据(即异常表中)记录每个函数的异常和栈展开信息,在异常发生时,通过查询该表快速定位出当前函数对应的 RUNTIME_FUNCTION 数据,从而执行栈展开。
核心数据结构
RUNTIME_FUNCTION
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { DWORD BeginAddress; // 函数起始 RVA,相对于模块基址 DWORD EndAddress; // 函数结束 RVA union { DWORD UnwindInfoAddress; // 指向 UNWIND_INFO 结构的 RVA(通常) DWORD UnwindData; } DUMMYUNIONNAME;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
- 每个 RUNTIME_FUNCTION 条目描述一个函数的异常与栈展开信息。
- 在异常处理过程中,系统会根据错误地址在该表中查找匹配的函数范围。如果找到匹配的 BeginAddress 与 EndAddress,则根据 UnwindInfoAddress 获取对应的展开信息。
UNWIND_INFO
typedef struct _UNWIND_INFO { UCHAR Version : 3; // 版本号(通常为1) UCHAR Flags : 5; // 标志(如是否包含异常处理程序)UCHAR SizeOfProlog; // 函数序言(Prolog)的字节数 UCHAR CountOfCodes; // 展开代码条目数量(数组中 UNWIND_CODE 个数) UCHAR FrameRegister : 4; // 帧指针寄存器(如 RBP、RDI 等) UCHAR FrameOffset : 4; // 帧指针偏移,用 16 字节单位(FP = RSP + FrameOffset * 16) UNWIND_CODE UnwindCode[1]; // 不定长度数组,描述具体的栈展开操作 // 后续紧跟可选字段: // 1. 如果设置了 UNW_FLAG_EHANDLER 或 UNW_FLAG_UHANDLER,则紧跟有 ExceptionHandler 和 ExceptionData。 // 2. 如果设置了 UNW_FLAG_CHAININFO,则后续为一个 RUNTIME_FUNCTION 结构。
} UNWIND_INFO, *PUNWIND_INFO;
UNWIND_CODE
typedef union _UNWIND_CODE { struct { UBYTE CodeOffset; // 在函数中相对于 Prolog 起始处的偏移量,指定该操作的开始位置 UBYTE UnwindOp : 4; // 展开操作类型,属于 UNWIND_OP_CODES 之一 UBYTE OpInfo : 4; // 补充信息,根据不同的 UnwindOp 含义不同 }; USHORT FrameOffset; // 某些操作直接用来描述栈帧内偏移的值
} UNWIND_CODE, *PUNWIND_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 = 8, /* 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;
SCOPE_TABLE
typedef struct _SCOPE_TABLE { DWORD Count; struct { DWORD BeginAddress; DWORD EndAddress; DWORD HandlerAddress; DWORD JumpTarget; } ScopeRecord[1];
} SCOPE_TABLE, *PSCOPE_TABLE;
图解
┌──────────────────────┐ ┌──────────────────────┐
│ RUNTIME_FUNCTION │ │ UNWIND_INFO │
├──────────────────────┤ ├──────────────────────┤
│ BeginAddress │ │ Version/Flags │
│ EndAddress │ │ SizeOfProlog │
│ UnwindInfoAddress |─────── | CountOfCodes │
└──────────────────────┘ │ FrameRegister/Offset ││ UnwindCode[] ││ ExceptionHandler ─┐└──────────────────────┘ ││▼┌──────────────────────┐│ SCOPE_TABLE │├──────────────────────┤│ Count ││ ScopeRecord[0..N] │└──────────────────────┘
与 x86 平台 SEH 的对比
x86 SEH
- 基于链表机制,在每个函数的栈上构造 SEH 帧(链表节点)来捕获异常信息。
- 异常处理信息嵌入在函数栈区,处理过程依赖于栈上注册的异常处理例程。
x64 SEH
- 将所有异常相关信息集中存放于静态数据区(PE 文件中的异常目录),运行时通过查询定位当前函数展开信息。
- 栈展开完全依赖预编译生成的 UNWIND_INFO 和 UNWIND_CODE,由操作系统提供支持。