异常处理
image.png
异常和中断是由CPU触发的.
操作系统怎么接收到异常的?
IDT表, 操作系统在启动时,就会将中断处理的地址存入到IDT中.
当产生中断的时候,CPU(硬件)就会调用IDT中的函数(软件).
Windows的异常处理机制, 都是由Windows操作系统提供.
- SEH
- VEH
SEH的原理
-
SEH的异常处理函数是怎么被调用?
产生异常后 , 操作系统使用fs段寄存器找到TEB, 通过TEB.ExceptionList 找到SEH链表的头节点, 通过节点中记录的异常处理函数的地址调用该函数. -
异常过滤函数是怎么被调用的?
由编译器提供的异常处理函数(except_handler4)内部所调用的,
except_handler4这个函数被充当为注册SEH节点时的异常处理函数.
系统调用的是except_handler4函数,except_handler4 调用我们在except块的异常过滤表达式中给出的异常过滤函数. -
except语句块怎么被执行的?
也是由except_handler4函数调用的. -
seh是怎么找到上一层的异常处理块的.
通过节点的Next找到下一个节点, 然后找到节点的异常出来函数.
=====================
- 异常是在哪里产生的?
1.1 CPU外部硬件。
1.2 CPU内部中断(中断指令)
-
SEH异常处理函数是被谁调用的?异常过滤函数呢?
异常处理函数 :保存在SEH节点中的,被操作系统调用。是系统的原生SEH异常处理。
异常过滤函数 : 保存在编译器所提供异常机制的某个结构中(《软件调试》),最终被exceptHandler4所调用。是对编译器在原生SEH异常处理机制的封装。 -
SEH节点保存在内存中的什么位置?
保存在栈中, 一般和函数是相关, 一般在进入到函数的时候,SEH的节点被安装在栈中,在离开函数之后就会删除节点。
- 从哪可以找到SEH的头节点?
TEB.TIB.ExceptionList,也就是FS:[0]
- 异常是怎么从CPU转发到操作系统中
1.1 CPU的处理:
1.1.1 当发生异常的时候,CPU根据异常号调用IDT表中对应的异常处理程序。
1.2 操作系统的处理:
1.2.1 将各种异常处理函数填充到IDT
1.2.2 等待IDT的异常处理函数被调用
1.2.2.1 KiTrap03 (IDT中3号处理函数),相当于异常的源头。
base\ntos\ke\i386\trap.asm
1.2.2.2 函数的功能:开辟栈空间保存了产生异常时的线程上下文。通过寄存器传参,调用CommonDispatchException函数。
主要传递了:通过寄存器传递的有:异常地址,异常代码,异常附加参数。通过栈传递有:线程的上下文。
1.2.3 CommonDispatchException
1.2.3.1 函数的功能:
1.2.3.1.1 开辟一个栈空间将异常记录信息保存到栈中。
1.2.3.1.2 获取异常发生的模式(用户层/内核层)
1.2.3.1.3 KiDispatchException
参数1: 异常记录结构体的地址
参数2: NULL
参数3:异常记录帧
参数4: 异常发生的模式
参数5:是否是第一次处理异常(通过KiTrapXX系列函数处理的异常都是第一次)
1.2.4 KiDispatchException
base\ntos\ke\i386\exceptn.c
1.2.4.1 功能:
1.2.4.1.1 内核模式的处理:
1.2.4.1.1.1 将异常交给内核调试器处理
1.2.4.1.1.2 内核调试器处理不了才交给异常处理机制处理
1.2.4.1.1.3 异常处理机制也处理不了,则进入到第二次异常分发:
再将异常交给调试器处理
1.2.4.1.1.4 还处理不了,就KeBugCheckEx
1.2.4.2 用户模式异常的处理
1.2.4.2.1 通过发送消息,将调试记录发送到用户层的调试器,并等待调试器的处理结果。
1.2.4.2.2 如果调试器处理不了, 则轮到异常处理机制处理。
1.2.4.2.2.1 将内核栈的数据拷贝到用户栈,并将esp指向拷贝后数据的首地址
1.2.4.2.2.2 将eip设置到ntdll!KiUserExceptionDispatcher函数中。这样当执行流从内核回到用户层的时候, esp指向了EXCEPTION_POINTERS的变量的地址, eip指向了用户层的异常分发函数的地址。
1.2.5 用户层的异常分发(用户层的SHE,VEH的调用过程):
1.2.5.1 遍历VEH,并调用异常处理函数
1.2.5.2 遍历SEH,并调用异常处理函数
- 用户异常处理程序怎么接收到的异常
- 分析操作系统处理异常的源码
3.1 系统异常处理的步骤
3.2 系统对调试机制的支持
3.2.1 反调试 - 反调试和反反调试
=====================
// 01_try_finally.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
__try{
printf("try块\n");
//return 0;
*(int*)0 = 0;
__leave; // 以正常方式离开try的关键字
}
__finally{
printf("finally块\n");
}
printf("main\n");
return 0;
}
====================
// 02_try_except.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
int* g_pNum = NULL;
void fun()
{
__try{
*(int*)0 = 0;
}
__except (EXCEPTION_CONTINUE_SEARCH){
}
}
// 异常过滤函数, 用于处理程序中出现的异常.
int seh(EXCEPTION_POINTERS* pExce)
{
printf("在%08X处产生了%08X异常\n",
pExce->ExceptionRecord->ExceptionAddress,
pExce->ExceptionRecord->ExceptionCode);
printf("EAX:%08X ECX:%08X\n",
pExce->ContextRecord->Eax,
pExce->ContextRecord->Ecx);
pExce->ContextRecord->Eax = (DWORD)new int;
return EXCEPTION_CONTINUE_EXECUTION;
}
int _tmain(int argc, _TCHAR* argv[])
{
//执行处理程序(except块)
EXCEPTION_EXECUTE_HANDLER;
//继续搜索
// 将异常传递到上一层的try和except,将异常交给它执行
EXCEPTION_CONTINUE_SEARCH;
//继续执行
// 继续执行产生异常的那条指令
EXCEPTION_CONTINUE_EXECUTION;
__try{
fun();
//*(int*)0 = 0;
*g_pNum = 10;
printf("try块\n");
}
__except ( seh(GetExceptionInformation())){
printf("finally块\n");
}
printf("main()\n");
__try{
*(int*)0 = 0;
printf("try块\n");
}
__except (EXCEPTION_CONTINUE_EXECUTION){
printf("finally块\n");
}
return 0;
}
=============================
// 03_veh.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
LONG WINAPI veh(EXCEPTION_POINTERS* pExce)
{
printf("veh\n");
// 继续执行, 说明异常已被处理,产生异常的指令将会
// 被继续执行
EXCEPTION_CONTINUE_EXECUTION;
// 让下一个veh节点处理异常.
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI seh(EXCEPTION_POINTERS* pExce){
printf("seh\n");
// 让下一个veh节点处理异常.
return EXCEPTION_CONTINUE_SEARCH;
}
int _tmain(int argc, _TCHAR* argv[])
{
//1. 将异常处理函数注册到系统
AddVectoredExceptionHandler(TRUE, veh);
__try{
*(int*)0 = 0;
}
__except (seh(GetExceptionInformation())){
}
return 0;
}
============================
// 04_异常 处理的优先级.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
LONG WINAPI vch(EXCEPTION_POINTERS* pExcept){
printf("vch\n");
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI veh(EXCEPTION_POINTERS* pExcept){
printf("veh\n");
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI seh(EXCEPTION_POINTERS* pExcept){
printf("seh\n");
return EXCEPTION_CONTINUE_SEARCH;
}
LONG WINAPI ueh(EXCEPTION_POINTERS* pExcept){
printf("ueh\n");
return EXCEPTION_CONTINUE_SEARCH;
}
int _tmain(int argc, _TCHAR* argv[])
{
AddVectoredContinueHandler(TRUE, vch);//vch
AddVectoredExceptionHandler(TRUE, veh);//veh
// 在64位系统下, 当程序被调试时,UEH不会被调用
// 不被调试才会被调用.
// 在32位系统下,被调试时也会被调用.
SetUnhandledExceptionFilter(ueh);
__try{
*(int*)0 = 0;
}
__except (seh(GetExceptionInformation())){
}
return 0;
}
======================
// 05_手工安装SEH节点.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
EXCEPTION_DISPOSITION NTAPI seh(struct _EXCEPTION_RECORD *ExceptionRecord,PVOID EstablisherFrame,struct _CONTEXT *ContextRecord,PVOID DispatcherContext)
{
printf("seh\n");
// 继续执行
return ExceptionContinueExecution;
}
int _tmain(int argc, _TCHAR* argv[])
{
// EXCEPTION_REGISTRATION_RECORD node;
/*
* 产生异常后 , 操作系统使用fs段寄存器找到TEB,
* 通过TEB.ExceptionList 找到SEH链表的头节点,
* 通过节点中记录的异常处理函数的地址调用该函数.
*/
// node.Handler = seh;
// node.Next = NULL;
_asm
{
push seh; // 将SEH异常处理函数的地址入栈
push fs:[0];//将SEH头节点的地址入栈
;// esp + 0 -- > [fs:0]; node.Next;
;// esp + 4 -- > [seh]; node.handler;
mov fs:[0], esp;// fs:[0] = &node;
}
*(int*)0 = 0;
// 平衡栈空间
// 还原FS:[0]原始的头节点
_asm{
pop fs : [0]; // 将栈顶的数据(原异常头节点的地址)恢复到FS:[0],然后再平衡4个字节的栈
add esp, 4; // 平衡剩下的4字节的栈.
}
return 0;
}
=========================
网友评论