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优先
- 它是基于进程的
- 它不需要栈展开,所以只有一次调用机会.
需要使用AddVectoredExceptionHandler和RemoveVectoredExceptionHandler来注册回调函数和卸载
Windows下的核心机制
好吧,我还是想写系统内核怎么调用的,毕竟看了加密与解密还是心里痒痒的.
核心的分发异常函数是ntdll!RtlDispatchException
首先它会判断这个异常是否二次触发,如果是则判断该异常不可继续,这也解释了为什么IDA碰到同一个异常时不处理要点两次.
然后去尝试先调用VEH
接着尝试调用SEH,会有一些安全机制后续再讲.
这里的调用就开始遍历节点了.
最后调用VEH ContinueHandler.
另外,还有一个机制,顶层异常处理,因为程序加载不是只有exe,还有加载器,进程真正创建点不在exe中,里面也有异常处理.
所以所有线程没有得到处理的都会跑到这里被处理.
主要是一个函数UnhandledExceptionFilter,可以被称为UEF函数,位于kernel32.dll中,里面有预处理的函数.
微软有个APISetUnhandledExceptionFilter,允许用户设置一个顶层异常过滤回调函数.它会把用户最后一个设置的放在一个全局函数kernel32!BasepCurrentTopLevelFilter并加密上,所以只有最后一次设置是有效的.
关于如何使用这个,具体看加密与解密吧.

浙公网安备 33010602011771号