Windows异常处理机制

Windows异常处理机制

异常处理这部分实际上一直不是很理解,实现了南大PA后,大概分清楚关系了.

硬件提供机制,二者中间层提供ABI,操作系统负责提供解决方案(回调函数),进程实现相应方案.

不写很详细的异常处理,就是切换用户态巴拉巴拉的,知道系统会调用即可.

参考:加密与解密,逆向工程核心原理

一些注意点

异常处理顺序:调试器>VEH>SEH

异常处理方式:调试器修改,被调试者处理,OS默认的处理机制

异常号

系统中有很多异常,这个不一一举例,只写最有代表的五个.

  • EXCEPTION_ACCESS_VIOLATION C0000005 访问不合法内存区域
  • EXCEPTION_BREAKPOINT 80000003 断点异常,int3
  • EXCEPTION_ILLEGAL_INSTRUCTION C000001D 无法解析的指令
  • EXCEPTION_INT_DIVIDE_BY_ZERO C0000094 除0异常
  • EXCEPTION_SINGLE_STEP 80000004 单步工作模式,将EFLAGS中的TF(陷阱标志)设1后CPU就会进入单步工作模式

SEH 线程异常处理

32位

SEH安装和卸载在函数头,尾.

typedef struct _EXCEPTION_REGISTRATION_RECORD {
    PEXCEPTION_REGISTRATION_RECORD Next; //指向下一个链表节点
    PEXCEPTION_DISPOSITION Handler; //回调函数
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

这是SEH链表节点结构,SEH本质就是链表,由系统在出现异常是自动调用,当然注册要我们注册.如果Next为FFFFFFFF,说明它是最后一个.

EXCEPTION_DISPOSITION _except_handler(  //EXCEPTION_DISPOSITION为枚举类型,见下
    EXCEPTION_RECORD *pRecord, //见下
    EXCEPTION_REGISTRATION_RECORD *pFrame,
    CONTEXT *pContext, //上下文
    PVOID pValue
)//来自逆向工程核心原理作者的推断,微软没有公开.
    
//EXCEPTION_RECORD的结构体
typedef struct _EXCEPTION_RECORD {   
    DWORD ExceptionCode;   //异常类型码
    DWORD ExceptionFlags;   //0 可修复异常, 1 不可修复异常, 2 栈展开
    //如果系统中(如果有)所有的回调函数都不处理,程序终结前会给所有注册的回调函数设置2,并且再调用一遍,意思给个机会清理资源.保证安全
    struct _EXCEPTION_RECORD *ExceptionRecord;   
    PVOID ExceptionAddress;   //异常发生地址
    DWORD NumberParameters;   
    DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

//枚举量
enum _EXCEPTION_DISPOSITION
{
    ExceptionContinueExecution = 0, //继续执行异常代码
    ExceptionContinueSearch = 1, //运行下一个异常处理器
    ExceptionNestedException = 2, //OS内部使用
    ExceptionCollidedUnwind = 3 //OS内部使用
};

如何访问这个链表呢?事实上TEB第一个成员TEB.NtTib.ExceptionList就是,也就是TEB的地址所在,也就是FS:[0]

我们可以用__try,__except__finally来添加SEH,MSCV对异常处理机制加强了很多,改了许多结构.

比如说:对于每一个注册的函数实际上应该要都有一个_EXCEPTION_REGISTRATION_RECORD结构,但是它改为了一个专门的代理函数,实现一个try块一个注册函数.

当然我们也可以用汇编注册

PUSH @Handler ;我们的异常回调函数
PUSH DWORD PTR FS:[0] ;把下一个节点的地址压入栈
MOV DWORD PTR FS:[0], ESP ;注册

这里要注意一个异常处理非常经典的操作,就是访问上下文查看PEB.BeingDebugged

移除SEH也很简单,pop出来,然后栈平衡即可.

64位

由于不需要考虑乱七八糟的兼容问题,所以微软可以放开手脚了.

在编译期,Handler的地址就放在一个只读的表(数据项目标中定义为3).存放着函数的RVA(按照大小排序)

VEH

只有用户态才可以使用.

与SEH的区别为:

  • 它的注册方式保存在ntdll中的独立链表中,与栈无关,而且你可以自己独立设置加载链表头还是尾.
  • 它比SEH优先
  • 它是基于进程的
  • 它不需要栈展开,所以只有一次调用机会.

需要使用AddVectoredExceptionHandlerRemoveVectoredExceptionHandler来注册回调函数和卸载

Windows下的核心机制

好吧,我还是想写系统内核怎么调用的,毕竟看了加密与解密还是心里痒痒的.

核心的分发异常函数是ntdll!RtlDispatchException

首先它会判断这个异常是否二次触发,如果是则判断该异常不可继续,这也解释了为什么IDA碰到同一个异常时不处理要点两次.

然后去尝试先调用VEH

接着尝试调用SEH,会有一些安全机制后续再讲.

这里的调用就开始遍历节点了.

最后调用VEH ContinueHandler.

另外,还有一个机制,顶层异常处理,因为程序加载不是只有exe,还有加载器,进程真正创建点不在exe中,里面也有异常处理.

所以所有线程没有得到处理的都会跑到这里被处理.

主要是一个函数UnhandledExceptionFilter,可以被称为UEF函数,位于kernel32.dll中,里面有预处理的函数.

微软有个APISetUnhandledExceptionFilter,允许用户设置一个顶层异常过滤回调函数.它会把用户最后一个设置的放在一个全局函数kernel32!BasepCurrentTopLevelFilter并加密上,所以只有最后一次设置是有效的.

关于如何使用这个,具体看加密与解密吧.

posted @ 2025-08-30 14:02  T0fV404  阅读(43)  评论(0)    收藏  举报