文章目录
- 内容回顾:
- 用户异常的处理流程:
- 总结
内容回顾:
异常如果发送在内核层,处理起来比较简单,因为异常处理函数也在0环,不用切换堆栈,但是如果异常发生在3环,就意味着必须要切换堆栈,回到3环执行处理函数
切换堆栈的处理方式与用户APC的执行过程几乎是一样的,惟一的区别就是执行用户APC时返回3环后执行的函数是KiUserApcDispatcher,而异常处理时返回3环后执行的函数是KiUserExceprionDispatcher
所以,理解用户APC的执行过程是理解3环异常处理的关键
用户异常的处理流程:
VOID KiDispatchException(ExceptionRecord,ExceptionFrame,TrapFrame,PreviousMode,FirstChance)
1._KeContextFromKframes 将Trap_frame备份到context为返回3环做准备
第一步:不管用户异常还是内核异常,先把Trap_frame(当前线程3环进入0环时,那些寄存区环境,也就是eip运行地方那些值)备份到context里(为返回3环做准备)
这个函数两种异常(用户异常和内核异常)的分发都归它管,所以就存在异常处理是否需要回到三环,内核异常不用回去(内核异常处理函数在0环),用户异常需要回去(用户层处理函数在3环0)。
2.第二步:判断先前模式,0是内核调用,1是用户调用,用户层异常呢,紧接着就是跳转:0x4258C3
第三步:第一次执行时,肯定是第一次调用(这个函数不止执行一次),所以接着往下:
这里判断是否启用了内核调试器,如果有内核调试器的话,那么这个值是非零的,如果有内核调试器的话,那它就调用,把异常信息先发送给异常调试器(那么我们此处假设没有内核异常调试器,或者内核异常调试器没有处理)
第四步:用来判断3环调试器(3环调试器如果不存在或者3环调试器没有处理的话,那么接着往下走)
第五步:为回到三环做准备
一开始就把Trap_frame备份到context里,然后这里接下来就可以随心所欲地改
最关键的修改,把KeUserExceptionDispatcher里面的值覆盖到Eip,它并没有在当前位置直接返回三环。而是让当前程序结束执行
总结
VOID KiDispatchException(ExceptionRecord,ExceptionFrame,TrapFrame,PreviousMode,FirstChance)
- _KeContextFromKframes 将Trap_frame备份到context为返回3环做准备
- 判断先前模式 0是内核调用,1是用户调用
- .是否是第一次机会
- 是否有内核调试器
- 发送给3环调试
- 如果3环调试器没有处理这个异常,修正EIP为KiUserExceptionDispatcher
- KiDispatchException函数执行结束:CPU异常与模拟异常返回地点不同
CPU异常:CPU检测到异常—>查IDT执行处理函数—>CommonDispatchException------>KiDispatchException 通过IRETD返回3环
模拟异常:CxxThrowException—>RaiseException—>RtlRaiseException---->Nt!NtRaiseException—>Nt!KiRaiseException---->KiDispatchException通过系统调用返回3环 - 无论通过哪种方式,但线程再次回到3环,将执行KiUserExceptionDispatcher函数