SEH

SEH机制学习

SEH机制学习

Posted by gxkyrftx on January 7, 2019

0.SEH简介

SEH(“Structured Exception Handling”),即结构化异常处理·是windows操作系统处理程序错误或异常的技术。SEH使windows操作系统的一种系统机制,与特定的程序设计语言无关。

1.中断与异常

中断:外部硬件设备或者异步产生
异常:内部事件产生,分为故障(可恢复)、陷阱(可恢复)、终止(不可恢复,系统必须重启) # 2.产生异常
硬件异常:cpu执行指令时引发的异常。举例:int 3 可以引发断点中断。
软件异常:函数调用引发的异常。举例:RaiseException

RaiseException的详细定义

void WINAPI RaiseException(
  _In_ DWORD dwExceptionCode,   //引发异常的应用程序定义的异常代码。

  _In_ DWORD dwExceptionFlags,   //异常标志。

  _In_ DWORD nNumberOfArguments,  //lpArguments数组中的参数数量。可以为null

  _In_  const ULONG_PTR * lpArguments   //参数,可以为null

);

使用RaiseException实现异常:

#include <stdio.h>
#include <windows.h>

DWORD FilterFunction() 
{ 
    printf("1 ");                     // printed first 
    
    return EXCEPTION_EXECUTE_HANDLER; 
} 
VOID main(VOID) 
{ 
    __try 
    { 
        __try 
        { 
            RaiseException( 
                RaiseException( 
                MessageBox(NULL,TEXT("123"),TEXT("WinMain"),MB_OK), // exception code 
                
                EXCEPTION_NONCONTINUABLE,  // continuable exception 
                
                0, NULL);     
        __finally 
        { 
            printf("2 ");             // this is printed second 
            
        } 
    } 
    __except ( FilterFunction() ) 
    { 
        printf("3\n");                // this is printed last 
        
    } 
}

在vs2010中编译,可以看到成功运行,但是拖入od之后运行会直接退出

3.异常处理流程

发生异常时系统的处理顺序

1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.调试器可能处理,也可能不处理异常。
2.如果程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.
3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,可交由链起来的其他例程处理.
4.如果这些线程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.
5.如果程序未处于被调试状态或者debugger没有能够处理,并且调用SetUnhandledExceptionFilter安装了最后异常处理例程的话,系统转向对它的调用。
6.如果没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序。
7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会。其后程序就终结了。

划重点:seh是系统发现异常或错误的时候,在终结应用程序之前,在这个回调函数里执行一些代码,给予程序,给系统的最后的修正机会。

4.seh的几个数据结构

4.1 teb结构

teb,Thread Environment Block,线程环境块,记录线程的信息,每个线程对应一个teb结构。

typedef struct _NT_TIB          //sizeof  1ch  
{  
 00h   struct _EXCEPTION_REGISTRATION_RECORD  *ExceptionList; //异常处理相关的结构体
 04h   PVOID                            StackBase;  //栈基址
 08h   PVOID                            StackLimit; 
 0ch   PVOID                            SubSystemTib;  
       union { 
           PVOID                FiberData;  
 10h       DWORD                Version; 
       };  
 14h   PVOID                            ArbitraryUserPointer;  
 18h   struct _NT_TIB                   *Self; //自引用指针,即为NtCurrentTeb() 函数所读出的TEB结构体指针

}NT_TIB;

windows创建线程时,操作系统会分配teb给线程,将fs段选择器指向当前线程的teb数据,这样就可以让程序通过[FS:0],读取到teb

4.2 EXCEPTION_REGISTRATIO

EXCEPTION_REGISTRATION struc
    prev    dd   ?      //指向前一个EXCEPTION_REGISTRATIO的指针
    handler dd   ?      //异常回调函数的地址
EXCEPTION_REGISTRATION ends

如果系统遇到一个不知道如何处理的异常的时候,会去查找异常处理链表。

4.3 应用

使用od打开含有seh的程序,把所有的调试异常全部设置为不可忽略,f9运行。

1

esi为0,程序试图读取内存为0的地址空间,这是访问违规的,可以看到状态栏的提示

2

而当把状态栏的忽略非法访问选中,则程序不会出现提示,从调试的角度来看,有利于程序流程的追踪。 划重点:seh机制,和tls函数很像,都改变了程序原来的执行流程,然后再返回执行。

4.4 EXCEPTION_POINTERS

一个异常发生时,操作系统向引起异常的线程堆栈中压入的结构,包含两个指针。

typedef struct _EXCEPTION_POINTERS {
    PEXCEPTION_RECORD ExceptionRecord   DWORD;      //指向ExceptionRecord
    
    PCONTEXT ContextRecord      DWORD;              //指向Context结构
    
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

其中ExceptionRecord包含有关最近发生异常的详细信息

typedef struct _EXCEPTION_RECORD {
   DWORD ExceptionCode;         //异常代码
   
   DWORD ExceptionFlags;        //异常标志
   
   struct _EXCEPTION_RECORD *ExceptionRecord;       //指向上一个异常记录的指针
   
   PVOID ExceptionAddress;      //异常发生的地址
   
   DWORD NumberParameters;      //异常信息的参数的个数    
   
   DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];        
} EXCEPTION_RECORD;

其中Context是线程在运行时处理器各主要寄存器的完整镜像,用于保存线程运行时的环境。

typedef struct _CONTEXT
{
    DWORD ContextFlags;     //说明该结构中哪些域有效,需要时恢复更新
    
    DWORD Dr0;
    DWORD Dr1;
    DWORD Dr2;
    DWORD Dr3;
    DWORD Dr6;
    DWORD Dr7;
    FLOATING_SAVE_AREA FloatSave;       //浮点寄存器区
    
    DWORD SegGs;    //各种段寄存器
    
    DWORD SegFs;
    DWORD SegEs;
    DWORD SegDs;
    DWORD Edi;      //各种寄存器
    
    DWORD Esi;
    DWORD Ebx;
    DWORD Edx;
    DWORD Ecx;
    DWORD Eax;
    DWORD Ebp;
    DWORD Eip;
    DWORD SegCs;
    DWORD EFlags;
    DWORD Esp;
    DWORD SegSs;
} CONTEXT;

程序可以通过修改context的成员,达到反调试的目的。 一个小的demo,具体功能见注释。

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
DWORD scratch;
EXCEPTION_DISPOSITION
__cdecl
_except_handler( struct _EXCEPTION_RECORD *ExceptionRecord,
                void * EstablisherFrame,
                struct _CONTEXT *ContextRecord,
                void * DispatcherContext )
{
    unsigned i;
    // 指明是我们让流程转到我们的异常处理程序的
    
    printf( "Hello from an exception handler\n" );
    // 改变CONTEXT结构中EAX的值,以便它指向可以成功进写操作的位置
    
    ContextRecord->Eax = (DWORD)&scratch;
    // 告诉操作系统重新执行出错的指令
    
return ExceptionContinueExecution;
}
int main()
{
    DWORD handler = (DWORD)_except_handler;
    __asm
    { 
        // 创建EXCEPTION_REGISTRATION结构:
        
        push handler // handler函数的地址
        
        push FS:[0] // 前一个handler函数的地址
        
        mov FS:[0],ESP // 安装新的EXECEPTION_REGISTRATION结构
        
    }
    __asm
    {
        mov eax,0     // 将EAX清零
        
        mov [eax], 1 // 写EAX指向的内存从而故意引发一个错误
        
    }
    printf( "After writing!\n" );
    __asm
    { 
        // 移去我们的EXECEPTION_REGISTRATION结构
        
        mov eax,[ESP]    // 获取前一个结构
        
        mov FS:[0], EAX // 安装前一个结构
        
        add esp, 8       // 将我们的EXECEPTION_REGISTRATION弹出堆栈
        
    }
    return 0;
}

运行结果:

3

5.异常回调函数

handlerproc{
    _lpExceptionRecord,     //指向一个ExceptionRecord结构
    _lpSEH,                 //指向Exception_Registration结构的地址
    _lpContext结构,         //指向Context结构
    _lpDispatcherContext 
}

四种返回值及含义

1.ExceptionContinueExecution(0):回调函数处理了异常,可以从异常发生的指令处重新执行。
2.ExceptionContinueSearch(1):回调函数不能处理该异常,需要要SEH链中的其他回调函数处理。
3.ExceptionNestedException(2):回调函数在执行中又发生了新的异常,即发生了嵌套异常
4.ExceptionCollidedUnwind(3):发生了嵌套的展开操作

在回调函数中,指向context字段,可以通过修改context内容去执行程序,如:清除断点,改变流程。下面的一段asm程序达到了清除断点实现反追踪的目的


.code

_start:
; 在堆栈中构造一个 EXCEPTION_REGISTRATION 结构
        Assume FS:NOTHING
        push  offset perThread_Handler
        push  fs:[0]       
        mov   fs:[0],esp  
; 会引发异常的指令            
    mov   esi,0
    mov   eax,[esi] 

 WouldBeOmit:                                  
        invoke  MessageBox,0,addr Text,addr Caption,MB_OK   ;这一句将无法被执行                 

; 异常处理完毕后,从这里开始执行
 
 ExecuteHere:                           
        invoke   MessageBox,0,addr TextSEH,addr Caption,MB_OK

; 恢复原来的 SEH 链

        pop     fs:[0]                      
        add     esp,4 


        invoke  ExitProcess,NULL        

; 将 EIP 指向ExecuteHere处的位置并恢复堆栈

perThread_Handler proc uses ebx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD        

        mov     eax,pContext 
        Assume  eax:ptr CONTEXT
        lea     ebx, ExecuteHere  ; 异常后准备从ExecuteHere后开始执行
        mov     [eax].regEip,ebx  
        xor     ebx,ebx
        mov     [eax].iDr0,ebx    ; 对Drx调试寄存器清零,使断点失效(反跟踪)
        mov     [eax].iDr1,ebx
        mov     [eax].iDr2,ebx
        mov     [eax].iDr3,ebx
        mov     [eax].iDr7,341     
        mov     eax,0             ; ExceptionContinueExecution,表示已经修复
        ret                    
perThread_Handler endp
end _start

本文访问量: