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到底先找谁?这个顺序我画了张图,你一看就明白。

Windows异常分发流程 异常发生 步骤1:调试器优先(如果被调试) 步骤2:VEH(向量化异常处理) 步骤3:SEH(结构化异常处理) 步骤4:最终处理(崩溃或终止) 全局链表,不依赖栈 线程栈链表,依赖栈完整性

流程其实很清晰:

  1. 调试器先接招:如果程序正在被调试,异常先发给调试器。调试器可以选择处理或忽略。
  2. VEH上场:调试器不处理,就轮到VEH。VEH是全局的,所有线程共享。
  3. SEH登场:VEH也不处理,就找SEH。SEH是线程私有的,沿着栈回溯。
  4. 最终处理:谁都不管,系统就弹崩溃对话框或者直接终止进程。

避坑指南:我曾经遇到过一个诡异的问题——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链表。这两个基本功练好了,大部分异常处理相关的逆向问题都能搞定。

嗯,今天就聊到这儿。异常处理这块内容不少,但核心就是那几步流程。你多动手调试几次,自然就熟了。


公众号:蓝海资料掘金营,微信deep3321