首页
论坛
专栏
课程

[调试逆向] [系统底层] [原创]windows-SEH详解

2019-2-22 20:06 5356

[调试逆向] [系统底层] [原创]windows-SEH详解

2019-2-22 20:06
5356
       SEH是window操作系统默认的异常处理机制,逆向分析中,SEH除了基本的异常处理功能外,还大量用于反调试程序(这里SEH时保存在栈中的,漏洞利用的时候会用到)

1.SEH
    SEH是windows操作系统异常处理机制,在程序源代码中使用__try,__except,__finally关键字来具体实现。

2.OS异常处理的办法
     2.1正常运行时候的异常处理方法
     进程运行过程中若发生异常,OS会委托进程进行处理。若进程代码中存在具体的异常处理(如SEH异常处理器)代码,则能够顺利处理相关异常,程序继续运行,但如果进程内部没有具体实现SEH,那么相关异常就无法处理,OS就会启动默认的异常处理机制,终止进程运行
     2.2 调试运行时的异常处理方法
     被调试的进程内部发生异常,OS会首先把异常抛给调试进程处理。调器拥有被调试者的所有权限。被调试者内部发生的异常都由调试器处理。调试过程中的所有异常都先由调试器管理。被调试者发生异常时,调试器会停止运行,必须采取相应的措施来处理异常,完成后续的调试。遇到异常的时候的处理方法如下。
      1)直接修改异常:代码、寄存器、内存
      2)将异常泡杯被调试程序,使用od的shift+f7/f8/f9直接将异常抛还给被调试者
      3)OS默认异常处理机制

3.异常
     操作系统中常见的异常
EXCEPTION_ACCESS_VIOLATION 	0xC0000005 	程序企图读写一个不可访问的地址时引发的异常。例如企图读取0地址处的内存。
EXCEPTION_ARRAY_BOUNDS_EXCEEDED 	0xC000008C 	数组访问越界时引发的异常。
EXCEPTION_BREAKPOINT 	                        0x80000003 	触发断点时引发的异常。
EXCEPTION_DATATYPE_MISALIGNMENT 	0x80000002 	程序读取一个未经对齐的数据时引发的异常。
EXCEPTION_FLT_DENORMAL_OPERAND 	0xC000008D 	如果浮点数操作的操作数是非正常的,则引发该异常。所谓非正常,即它的值太小以至于不能用标准格式表示出来。
EXCEPTION_FLT_DIVIDE_BY_ZERO         	        0xC000008E 	浮点数除法的除数是0时引发该异常。
EXCEPTION_FLT_INEXACT_RESULT 	        0xC000008F 	浮点数操作的结果不能精确表示成小数时引发该异常。
EXCEPTION_FLT_INVALID_OPERATION 	        0xC0000090 	该异常表示不包括在这个表内的其它浮点数异常。
EXCEPTION_FLT_OVERFLOW 	                        0xC0000091 	浮点数的指数超过所能表示的最大值时引发该异常。
EXCEPTION_FLT_STACK_CHECK 	                0xC0000092 	进行浮点数运算时栈发生溢出或下溢时引发该异常。
EXCEPTION_FLT_UNDERFLOW 	                0xC0000093 	浮点数的指数小于所能表示的最小值时引发该异常。
EXCEPTION_ILLEGAL_INSTRUCTION 	        0xC000001D 	程序企图执行一个无效的指令时引发该异常。
EXCEPTION_IN_PAGE_ERROR             	        0xC0000006 	程序要访问的内存页不在物理内存中时引发的异常。
EXCEPTION_INT_DIVIDE_BY_ZERO 	                0xC0000094 	整数除法的除数是0时引发该异常。
EXCEPTION_INT_OVERFLOW 	                        0xC0000095 	整数操作的结果溢出时引发该异常。
EXCEPTION_INVALID_DISPOSITION         	        0xC0000026 	异常处理器返回一个无效的处理的时引发该异常。
EXCEPTION_NONCONTINUABLE_EXCEPTION 	0xC0000025 	发生一个不可继续执行的异常时,如果程序继续执行,则会引发该异常。
EXCEPTION_PRIV_INSTRUCTION 	                0xC0000096 	程序企图执行一条当前CPU模式不允许的指令时引发该异常。
EXCEPTION_SINGLE_STEP 	                        0x80000004 	标志寄存器的TF位为1时,每执行一条指令就会引发该异常。主要用于单步调试。
EXCEPTION_STACK_OVERFLOW 	                0xC00000FD 	栈溢出时引发该异常。
       调试的时候经常触发5中最具代表性的异常
       3.1  EXCEPTION_ACCESS_VIOLATION     0xC0000005    
       程序企图读写一个不可访问的地址时引发的异常(不存在,或者不具有访问权限)。例如企图读取0地址处的内存。
       3.2EXCEPTION_BREAKPOINT  0x80000003  
       触发断点时引发的异常。在运行的代码中设置断点以后,cpu尝试执行该处的指令时将触发队形的EXCEPTION_BREAKPOINT异常
      INT3,设置断点对应的汇编指令为INT3,对应的机器指令为0xCC.灵活运用这个原理可以为程序运行带来很大的便利。比如使用hex editor打开PE文件,修改EP地址对应的文件偏移处的第一个字节为0xCC,然后运行该PE文件就会发生EXCEPTION_BREAKPOINT异常,经过OS的默认异常处理后会终止程序运行,若在操作系统的注册表中将默认调试器设置为OllyDbg,那么发生以上异常时OS会自动运行ollydbg调试器,附加发生异常的进程。
      3.3EXCEPTION_ILLEGAL_INSTRUCTION  0xC000001D    
      程序企图执行一个无效的指令时引发该异常。
      3.4EXCEPTION_INT_DIVIDE_BY_ZERO   0xC0000094   
      整数除法的除数是0时引发该异常。
      3.5EXCEPTION_SINGLE_STEP   0x80000004    
      标志寄存器的TF位为1时,每执行一条指令就会引发该异常。主要用于单步调试。

4.SEH详细说明
      4.1SEH链
      SEH以链的形式存在。第一个异常处理中未处理相关异常,它就会被传递到下一个异常处理器,直到得到处理。SEH是由_EXCEPTION_REGISTRATION_RECORD结构体组成的链表
ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next             : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler          : Ptr32 _EXCEPTION_DISPOSITION 
}
      Next成员指向下一个_EXCEPTION_REGISTRATION_RECORD结构体指针,handler成员是异常处理函数(异常处理器)。若Next成员的值为FFFFFFFF,则表示它是链表最后一个结点
               
        发生异常的时候会按照(A)->(B)->(C)的顺序依次传递,直到由异常处理器处理
     4.2异常处理函数定义
EXCEPTION_DISPOSITION __cdecl _except_handler (
  EXCEPTION_RECORD      *pRecord,
  EXCEPTION_REGISTRATION_RECORD *pFrame,
  CONTEXT        *pContext,
  PVOID          pValue
);
       由系统调用,是一个回调函数,第一个参数是一个指向EXCEPTION_RECORD结构体的指针
typedef struct _EXCEPTION_RECORD {
    DWORD ExceptionCode;   //异常代码
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;   //异常发生地址
    DWORD NumberParameters;
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
      异常处理函数的第三个参数是指向CONTEXT结构体的指针,CONTEXT结构体的定义如下,   CONTEXT结构体用来备份CPU的值。 
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;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;
       异常发生的时候,执行异常代码的线程就会发生中断,转而运行SEH,此时OS会把线程 CONTEXT结构体的指针传递给异常处理函数的相应参数。里面有个eip成员,在异常处理函数中将参数传递过来的CONTEXT.eip设置为其他地址,然后返回处理函数。这样之前暂停的线程会执行新的EIP地址处的代码(反调试中经常使用这个技术)  
        4.3  TEB.NtTib.ExceptionList
        通过TEB结构体的NtTib成员可以很容易的访问进程的SEH链,TEB。
                          NtTib.ExceptionList=FS:[0]
       4.4 SEH安装/删除方法
       汇编中安装使用
push @MyHandler  ;异常处理程序
push FS:[0]       ;SEH Linked List头
mov dword ptr fs:[0],esp  ;添加链表
      汇编中的删除SEH代码   
POP DWORD PRT FS:[0] ;读取栈值并将其放入FS:[0],这里的栈值存放的下一个SEH的起始地址,执行该命令之后,就可以从栈中删除对应的SEH。
ADD ESP,4

     od中有查看SEH链的功能

5  od中的SEH
      程序在正常运行与调试运行的时候有不同的分支代码,借助SEH实现的反调试及时很多,这为代码的调试带来了很多不便,使调试更加困难。OD提供了很多调试选项,调试中发生异常的时候,调试器不会暂停,会自动将异常派送给被调试者。od中选择options-debugging options-.exception选项卡:灵活使用od的excettion选项,可以在不暂停调试器的前提下自动规避使用SEH的反调试“花招”,从而继续调试。


[招聘]欢迎市场人员加入看雪学院团队!

最后于 2019-2-22 21:10 被wwzzww编辑 ,原因: 补充内容
打赏 + 6.00
打赏次数 2 金额 + 6.00
收起 
赞赏  Editor   +1.00 2019/02/25
赞赏  kanxue   +5.00 2019/02/23
最新回复 (6)
刘铠文 2019-2-22 20:41
2
0
顶一下
Editor 2019-2-25 09:49
3
0
支持一下,感谢楼主分享!
hedilict 2019-5-17 17:42
4
0
支持支持,讲得很详细!
Tennn 5 2019-5-17 19:38
5
0
自己编写的try except 异常处理是由什么注册管理的 ?
wwzzww 2019-5-25 13:39
6
0
Tennn 自己编写的try except 异常处理是由什么注册管理的 ?
try...catch是编译器对__try ... __except的一个包装,该包装仅处理C++异常类型,在windows下,仍然是通过SEH机制实现。
Tennn 5 2019-5-27 07:46
7
0
我没问try catch是不是回错了……
游客
登录 | 注册 方可回帖
返回