windows异常

2018-10-10  本文已影响0人  MagicalGuy

异常处理


image.png

异常和中断是由CPU触发的.
操作系统怎么接收到异常的?
IDT表, 操作系统在启动时,就会将中断处理的地址存入到IDT中.
当产生中断的时候,CPU(硬件)就会调用IDT中的函数(软件).
Windows的异常处理机制, 都是由Windows操作系统提供.

  1. SEH
  2. VEH

SEH的原理

  1. SEH的异常处理函数是怎么被调用?
    产生异常后 , 操作系统使用fs段寄存器找到TEB, 通过TEB.ExceptionList 找到SEH链表的头节点, 通过节点中记录的异常处理函数的地址调用该函数.

  2. 异常过滤函数是怎么被调用的?
    由编译器提供的异常处理函数(except_handler4)内部所调用的,
    except_handler4这个函数被充当为注册SEH节点时的异常处理函数.
    系统调用的是except_handler4函数,except_handler4 调用我们在except块的异常过滤表达式中给出的异常过滤函数.

  3. except语句块怎么被执行的?
    也是由except_handler4函数调用的.

  4. seh是怎么找到上一层的异常处理块的.
    通过节点的Next找到下一个节点, 然后找到节点的异常出来函数.

=====================

  1. 异常是在哪里产生的?
    1.1 CPU外部硬件。
    1.2 CPU内部中断(中断指令)
  1. SEH异常处理函数是被谁调用的?异常过滤函数呢?
    异常处理函数 :保存在SEH节点中的,被操作系统调用。是系统的原生SEH异常处理。
    异常过滤函数 : 保存在编译器所提供异常机制的某个结构中(《软件调试》),最终被exceptHandler4所调用。是对编译器在原生SEH异常处理机制的封装。

  2. SEH节点保存在内存中的什么位置?
    保存在栈中, 一般和函数是相关, 一般在进入到函数的时候,SEH的节点被安装在栈中,在离开函数之后就会删除节点。

  1. 从哪可以找到SEH的头节点?
    TEB.TIB.ExceptionList,也就是FS:[0]
  1. 异常是怎么从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,并调用异常处理函数

  1. 用户异常处理程序怎么接收到的异常
  2. 分析操作系统处理异常的源码
    3.1 系统异常处理的步骤
    3.2 系统对调试机制的支持
    3.2.1 反调试
  3. 反调试和反反调试

=====================

// 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;
}

=========================

上一篇下一篇

猜你喜欢

热点阅读