56、异常处理逆向:SEH、VEH与异常分发机制
异常处理,说白了就是程序出错了怎么办。Windows下这套机制挺复杂的,SEH、VEH、异常分发……我第一次接触时也绕得头晕。但搞逆向,这关必须过。你想想看,很多反调试、代码保护、漏洞利用都跟异常处理有关。
我个人习惯把异常处理分成两个层面:用户态和内核态。今天咱们重点聊用户态,也就是SEH和VEH。
一、SEH(结构化异常处理)—— 老牌选手
SEH是Windows最早引入的异常处理机制,从Windows 95就有了。它基于线程的栈结构,每个线程维护一个异常处理链表。
核心数据结构:EXCEPTION_REGISTRATION_RECORD
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next; // 指向下一个节点
PEXCEPTION_ROUTINE Handler; // 异常处理函数指针
} EXCEPTION_REGISTRATION_RECORD;
这个结构体就两个字段:Next指针和Handler指针。Next指向链表中的下一个节点,Handler就是异常发生时调用的回调函数。
嗯,这里要注意:这个链表是倒着挂的。新注册的异常处理节点会放在链表头部,所以最后注册的反而最先被调用。
二、VEH(向量化异常处理)—— 后来居上
VEH是Windows XP/Vista引入的,比SEH晚了不少。它不依赖栈,而是全局的。什么意思?就是整个进程共享一份VEH链表。
我记得有一次调试一个崩溃转储,SEH链全乱了,但VEH还能正常工作。这就是VEH的优势——它不依赖栈的完整性。
个人经验:VEH在反调试中很常用。比如你注册一个VEH处理函数,当调试器触发断点时,VEH可以抢先处理异常,让调试器扑空。我曾在项目中用这招绕过了一个商业保护。
// 注册VEH
PVOID AddVectoredExceptionHandler(
ULONG First, // 0表示追加到末尾,非0表示插入头部
PVECTORED_EXCEPTION_HANDLER Handler // 处理函数
);
VEH处理函数的原型:
LONG CALLBACK VectoredHandler(
PEXCEPTION_POINTERS ExceptionInfo
);
返回值有三种:
- EXCEPTION_CONTINUE_SEARCH:继续搜索下一个处理者
- EXCEPTION_CONTINUE_EXECUTION:恢复执行(你修好了异常条件)
- EXCEPTION_EXECUTE_HANDLER:执行当前处理函数(VEH不常用这个)
三、异常分发机制 —— 谁先谁后?
异常发生时,Windows到底先找谁?这个顺序我画了张图,你一看就明白。
流程其实很清晰:
- 调试器先接招:如果程序正在被调试,异常先发给调试器。调试器可以选择处理或忽略。
- VEH上场:调试器不处理,就轮到VEH。VEH是全局的,所有线程共享。
- SEH登场:VEH也不处理,就找SEH。SEH是线程私有的,沿着栈回溯。
- 最终处理:谁都不管,系统就弹崩溃对话框或者直接终止进程。
避坑指南:我曾经遇到过一个诡异的问题——SEH链被破坏了,但VEH还能正常工作。后来发现是栈溢出把SEH节点覆盖了。所以搞逆向时,如果SEH链看起来不对劲,先检查栈完整性。
四、逆向实战技巧
搞逆向时,怎么定位异常处理代码?我分享几个实用方法。
4.1 定位SEH链
在x86下,SEH链的头部存放在FS:[0]段寄存器中。你可以直接读这个地址:
// 读取当前线程的SEH链头
EXCEPTION_REGISTRATION_RECORD* pHead;
__asm {
mov eax, fs:[0]
mov pHead, eax
}
在x64下,SEH信息存放在PE文件的.pdata节中,是静态的。这跟x86完全不同,要注意。
4.2 识别VEH处理函数
VEH处理函数注册在进程的全局结构中。你可以通过遍历PEB(进程环境块)来找到VEH链表。
// PEB中VEH链表的位置(x64)
typedef struct _PEB {
// ... 前面很多字段
PVOID VectorRegister; // 指向VEH链表头
// ...
} PEB;
我个人习惯用WinDbg直接看:
!peb
// 查看 PEB 中的 VEH 信息
dt nt!_PEB @$peb
4.3 异常处理逆向的常见场景
| 场景 | 异常类型 | 逆向关注点 |
|---|---|---|
| 反调试 | INT3、单步异常 | VEH/SEH是否拦截了调试异常 |
| 代码保护 | 访问违例、非法指令 | 异常处理函数是否解密/修复代码 |
| 漏洞利用 | SEH覆盖 | SEH链是否被篡改 |
| 崩溃分析 | 各种异常 | 异常处理链是否完整 |
小技巧:分析恶意软件时,如果发现它注册了VEH但SEH链是空的,十有八九是在搞反调试。我曾经分析过一个样本,它在VEH里检查异常地址是否在ntdll中,如果是就放行,否则就终止。这招挺阴的。
五、总结
SEH和VEH是Windows异常处理的两大支柱。SEH老而弥坚,依赖栈结构;VEH后来居上,全局可控。搞逆向时,这两者都是重点分析对象。
我个人建议:先学会用WinDbg看SEH链,再学会遍历VEH链表。这两个基本功练好了,大部分异常处理相关的逆向问题都能搞定。
嗯,今天就聊到这儿。异常处理这块内容不少,但核心就是那几步流程。你多动手调试几次,自然就熟了。