SEH 进阶(1)

来源:互联网 发布:美加净防晒霜 知乎 编辑:程序博客网 时间:2024/06/05 07:13

SHE进阶

了解了上一篇的文章之后,我们写一个简单的例子来验证我们的想法,并学习新的知识。

不同的编译器提供的增强版本SHE 可能不同,但是它们都是基于windows 底层SHE 的。我们使用Win10 1703 + VS2010 生成X86 Rlease 程序来验证已经学过的知识,后面使用XP x86 7600 来学习编译器版本的SHE。

编译如下程序

#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <stdio.h>DWORD  scratch;EXCEPTION_DISPOSITION__cdeclexcept_handler(    struct _EXCEPTION_RECORD *ExceptionRecord,    void * EstablisherFrame,    struct _CONTEXT *ContextRecord,    void * DispatcherContext ){    unsigned i;    // Indicate that we made it to our exception handler    printf( "Hello from an exception handler\n" );    // Change EAX in the context record so that it points to someplace    // where we can successfully write    ContextRecord->Eax = (DWORD)&scratch;    // Tell the OS to restart the faulting instruction    return ExceptionContinueExecution;}int main(){    DWORD handler = (DWORD)except_handler;    __asm    {                           // Build EXCEPTION_REGISTRATION record:        push    handler         // Address of handler function        push    FS:[0]          // Address of previous handler        mov     FS:[0],ESP      // Install new EXECEPTION_REGISTRATION    }    __asm    {        mov     eax,0           // Zero out EAX        mov     [eax], 1        // Write to EAX to deliberately cause a fault    }    printf( "After writing!\n" );    __asm    {                           // Remove our EXECEPTION_REGISTRATION record        mov     eax,[ESP]       // Get pointer to previous record        mov     FS:[0], EAX     // Install previous record        add     esp, 8          // Clean our EXECEPTION_REGISTRATION off stack    }    return 0;}

 

当我们使用VS 并添加我们自定义的异常处理程序,将有以下警告,且程序运行崩溃

warning C4733: 内联 asm 分配到“FS:0”: 处理程序未注册为安全处理程序

 

解决方案:

在 Visual Studio 开发环境中设置此链接器选项

1.      打开项目的“属性页”对话框。有关详细信息,请参见设置 Visual C++ 项目属性。

2.      选择 Linker 文件夹。

3.      选择“命令行”属性页。

4.      将该选项/SAFESEH:NO键入“附加选项”框中。



之后我们的程序正确执行并输出如下结果:


#include <windows.h>#include <stdio.h>EXCEPTION_DISPOSITION__cdecl_except_handler(struct _EXCEPTION_RECORD *ExceptionRecord,void * EstablisherFrame,struct _CONTEXT *ContextRecord,void * DispatcherContext ){printf( "Home Grown handler: Exception Code: %08X Exception Flags %X",ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionFlags );if ( ExceptionRecord->ExceptionFlags & 1 )printf( " EH_NONCONTINUABLE" );if ( ExceptionRecord->ExceptionFlags & 2 )printf( " EH_UNWINDING" );if ( ExceptionRecord->ExceptionFlags & 4 )printf( " EH_EXIT_UNWIND" );if ( ExceptionRecord->ExceptionFlags & 8 )printf( " EH_STACK_INVALID" );if ( ExceptionRecord->ExceptionFlags & 0x10 )printf( " EH_NESTED_CALL" );printf( "\n" );// Punt... We don't want to handle this... Let somebody else handle itreturn ExceptionContinueSearch;}void HomeGrownFrame( void ){DWORD handler = (DWORD)_except_handler;__asm{                           // Build EXCEPTION_REGISTRATION record:push    handler         // Address of handler functionpush    FS:[0]          // Address of previous handlermov     FS:[0],ESP      // Install new EXECEPTION_REGISTRATION}*(PDWORD)0 = 0;             // Write to address 0 to cause a faultprintf( "I should never get here!\n" );__asm{                           // Remove our EXECEPTION_REGISTRATION recordmov     eax,[ESP]       // Get pointer to previous recordmov     FS:[0], EAX     // Install previous recordadd     esp, 8          // Clean our EXECEPTION_REGISTRATION off stack}}int main(){_try {HomeGrownFrame(); }_except( EXCEPTION_EXECUTE_HANDLER ) {printf( "Caught the exception in main()\n" );}return 0;
}

运行后出现如下结果:



我们看到,except_handler函数被调用了两次,且传入的参数不同。

为什么同一个异常处理函数在这里被调用了两次?

首先我们观察两次调用的调用堆栈。




我们看到其调用堆栈并不相同。

从我们已经知道的知识来讲。

HomeGrownFrame 函数内部安装了一个自己的异常,在main函数中调用该函数之前已经进入一个异常函数中。因此我们可以得到类似于下面的堆栈:

0xFFFFFFFEH

__except_handler4

_try _catch 对应的异常处理函数

HomeGrownFrame 函数内部安装的异常块,except_handler

当该异常出现的时候,except_handler返回ExceptionContinueSearch,然后函数依然向下搜索,直到有异常处理函数声明了可以处理它。但是,为什么这里显示调用了两次except_handler函数。

 

为了搞清楚这个问题,我们要学习编译器提供的SHE 的机制,这里我们用Ring0 学习,其实机制是相同的。

Ring0层的实验

最简单的单个try 块的情况

#include "SEHDemo.h"void SEHTest() {int i = 0;__try{i++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}NTSTATUSDriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString){SEHTest();return STATUS_SUCCESS;}

代码逆向如下

.text:00011010 ; _DWORD __stdcall SEHTest().text:00011010 _SEHTest@0      proc near               ; CODE XREF: DriverEntry(x,x)+5 p.text:00011010.text:00011010 i               = dword ptr -1Ch.text:00011010 ms_exc          = CPPEH_RECORD ptr -18h

对应结构体如下

struct EH3_EXCEPTION_REGISTRATION{DWORDNext;DWORDExceptionHandler;DWORDScopeTable;DWORDTryLevel;};struct CPPEH_RECORD{DWORDOleEsp;DWORDExcPtr;EH3_EXCEPTION_REGISTRATION registration;};stru_12108 对应的结构体.rdata:00012108 stru_12108      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012108                                         ; DATA XREF: SEHTest()+7 o.rdata:00012108                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012108                 dd 0                    ; EHCookieXOROffset.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN5          ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN6          ; ScopeRecord.HandlerFunc

.text:00011010.text:00011010                 mov     edi, edi.text:00011012                 push    ebp.text:00011013                 mov     ebp, esp.text:00011015                 push    0FFFFFFFEh.text:00011017                 push    offset stru_12108.text:0001101C                 push    offset __except_handler4.text:00011021                 mov     eax, large fs:0.text:00011027                 push    eax.text:00011028                 add     esp, -0Ch.text:0001102B                 push    ebx.text:0001102C                 push    esi.text:0001102D                 push    edi

对应堆栈如下

 

 

.text:0001102E                 mov     eax, ___security_cookie.text:00011033                 xor     [ebp+ms_exc.registration.ScopeTable], eax.text:00011036                 xor     eax, ebp.text:00011038                 push    eax.text:00011039                 lea     eax, [ebp+ms_exc.registration].text:0001103C                 mov     large fs:0, eax.text:00011042                 mov     [ebp+ms_exc.old_esp], esp.text:00011045                 mov     [ebp+i], 0.text:0001104C                 mov     [ebp+ms_exc.registration.TryLevel], 0

对应堆栈如下


 

;这三条指令是内部执行的指令 i++

.text:00011053                 mov     eax, [ebp+i].text:00011056                add     eax, 1  .text:00011059                 mov     [ebp+i], eax


;下面是异常的卸载

 

.text:0001105C                 mov    [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011063                 jmp     short loc_11082.text:00011065.text:00011065 $LN5:                                   ; DATA XREF:.rdata:stru_12108o.text:00011065                 mov     eax, 1          ; Exception filter 0 for function11010.text:0001106A.text:0001106A $LN7:.text:0001106A                 retn.text:0001106B ;---------------------------------------------------------------------------.text:0001106B.text:0001106B $LN6:                                   ; DATA XREF: .rdata:stru_12108o.text:0001106B                 mov     esp, [ebp+ms_exc.old_esp] ; Exceptionhandler 0 for function 11010.text:0001106E                 push    offset Format   ; "Hello\r\n".text:00011073                 call    _DbgPrint.text:00011078                 add     esp, 4.text:0001107B                 mov     [ebp+ms_exc.registration.TryLevel],0FFFFFFFEh


对应的堆栈


.text:00011082 loc_11082:                              ; CODE XREF: SEHTest()+53 j.text:00011082                 mov     ecx, [ebp+ms_exc.registration.Next].text:00011085                 mov     large fs:0, ecx


;恢复fs:0 即原来的异常处理函数

;堆栈平衡


.text:0001108C                 pop     ecx.text:0001108D                 pop     edi.text:0001108E                 pop     esi.text:0001108F                 pop     ebx.text:00011090                 mov     esp, ebp.text:00011092                 pop     ebp.text:00011093                 retn.text:00011093 _SEHTest@0      endp

 

上面是仅仅一个try 语句时候的情况,那么,当有多个try 语句,是怎样的情况呢?

void SEHTest(){int i = 0;__try {i++;}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}__try{i--;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}

前面简历堆栈的操作是相同的,而且其第一个try 块的操作也是相同的,不同的如下所示。在将结构体的TryLevel 设置为 0FFFFFFFEh 之后,原来的函数已经开始了结束函数的操作,而这里是跳转到了下一个目标地址。


.text:0001105C                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011063                 jmp     short loc_11082.text:00011065 $LN6:                                   ; DATA XREF: .rdata:stru_12108 o.text:00011065                 mov     eax, 1          ; Exception filter 0 for function 11010.text:0001106A.text:0001106A $LN8:.text:0001106A                 retn.text:0001106B ; ---------------------------------------------------------------------------.text:0001106B.text:0001106B $LN7:                                   ; DATA XREF: .rdata:stru_12108 o.text:0001106B                 mov     esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 11010.text:0001106E                 push    offset Format   ; "Hello\r\n".text:00011073                 call    _DbgPrint.text:00011078                 add     esp, 4.text:0001107B                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:00011082

该目标地址处的操作如下所示,将TryLevel 设置为1,而上面为0



.text:00011082 loc_11082:                              ; CODE XREF: SEHTest()+53 j.text:00011082                 mov     [ebp+ms_exc.registration.TryLevel], 1.text:00011089                 mov     ecx, [ebp+i].text:0001108C                 sub     ecx, 1.text:0001108F                 mov     [ebp+i], ecx
进行i—操作

.text:00011092                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh


同样是将TryLevel 设置为 0FFFFFFFEh:


.text:00011099                 jmp     short loc_110B8.text:0001109B ; ---------------------------------------------------------------------------.text:0001109B.text:0001109B $LN10:                                  ; DATA XREF: .rdata:stru_12108 o.text:0001109B                 mov     eax, 1          ; Exception filter 1 for function 11010.text:000110A0.text:000110A0 $LN12:.text:000110A0                 retn.text:000110A1 ; ---------------------------------------------------------------------------.text:000110A1.text:000110A1 $LN11:                                  ; DATA XREF: .rdata:stru_12108 o.text:000110A1                 mov     esp, [ebp+ms_exc.old_esp] ; Exception handler 1 for function 11010.text:000110A4                 push    offset Format   ; "Hello\r\n".text:000110A9                 call    _DbgPrint.text:000110AE                 add     esp, 4.text:000110B1                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:000110B8.text:000110B8 loc_110B8:                              ; CODE XREF: SEHTest()+89 j

到这里向下函数的执行是一样的,即恢复原来的fs:0,然后恢复堆栈。  

另外,上下两个程序中引用的结构体,也发生了变化。上面的结构体的长度和下面的结构体长度不同。很明显,这是一个不定长的结构


.rdata:00012108 stru_12108      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012108                                         ; DATA XREF: SEHTest()+7 o.rdata:00012108                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012108                 dd 0                    ; EHCookieXOROffset第一个异常块.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN6          ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN7          ; ScopeRecord.HandlerFunc第二个异常块.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN10         ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN11         ; ScopeRecord.HandlerFunc

我们再次增加函数内try结构的个数如下

void SEHTest(){    int i = 0;    __try{        i++;    }    __except(EXCEPTION_EXECUTE_HANDLER){        DbgPrint("Hello\r\n");    }    __try{        i--;    }    __except(EXCEPTION_EXECUTE_HANDLER){        DbgPrint("Hello\r\n");    }    __try{        i += 2;    }    __except(EXCEPTION_EXECUTE_HANDLER){        DbgPrint("Hello\r\n");    }}

我们发现其添加了如下结构


loc_110B8:mov     [ebp+ms_exc.registration.TryLevel], 2mov     edx, [ebp+i]add     edx, 2mov     [ebp+i], edxmov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEhjmp     short loc_110EE

上述变长结构体变为这样:

.rdata:00012108 stru_12108      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012108                                         ; DATA XREF: SEHTest()+7 o.rdata:00012108                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012108                 dd 0                    ; EHCookieXOROffset.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN7          ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN8          ; ScopeRecord.HandlerFunc.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN11         ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN12         ; ScopeRecord.HandlerFunc.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN15         ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN16         ; ScopeRecord.HandlerFunc


似乎发现了什么,每添加一个try 块,添加一个代码块,且其TryLevel递增,上面的结构体增加一个单位的子结构。

然后我们再添加嵌套形式的SHE,使其格式如下:

void SEHTest() {int i = 0;__try {i++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}__try {i--;__try {i += 2;}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("Hello\r\n");}}

上面第一个代码块的执行依然不变,但是,当执行下面的结构的时候,我们来看。

.text:00011082 loc_11082:                              ; CODE XREF: SEHTest()+53 j.text:00011082                 mov     [ebp+ms_exc.registration.TryLevel], 1.text:00011089                 mov     ecx, [ebp+i].text:0001108C                 sub     ecx, 1.text:0001108F                 mov     [ebp+i], ecx


i-- 对应的代码块,TryLevel 为 1,但是其运行结束后没有将TryLevel 恢复到0FFFFFFFEh。而是直接执行了下面的嵌套的try 块


.text:00011092                 mov     [ebp+ms_exc.registration.TryLevel], 2.text:00011099                 mov     edx, [ebp+i].text:0001109C                 add     edx, 2.text:0001109F                 mov     [ebp+i], edx.text:000110A2                 mov     [ebp+ms_exc.registration.TryLevel], 1

i += 2 对应的代码块,TryLevel为 2,然后其执行结束后,自动将TryLevel 设置为1,也不是0FFFFFFFEh。


.text:000110A9                 jmp     short loc_110C8

最后函数跳转到如下地址,然后将其TryLevel 设置为 0FFFFFFFEh ,然后恢复fs:0 恢复堆栈等操作。


.text:000110C8 loc_110C8:                              ; CODE XREF: SEHTest()+99 j.text:000110C8                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh.text:000110CF                 jmp     short loc_110EE

我们可以看到,try 块与其包含的代码块的对应是通过TryLevel 来对应的,在进入一个新块之前会设置这个块的等级,然后在执行完这个块之后会恢复到之前TryLevel。而这里的TryLevel可以理解为对应的变长结构体的子结构体的下标。

 

下面我们来看这个子结构有什么变化,因为与上个程序相比,尽管块的个数没有改变,但是块的结构改变了------增加了嵌套的try 块这样的结构。

.rdata:00012108 stru_12108      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012108                                         ; DATA XREF: SEHTest()+7 o.rdata:00012108                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012108                 dd 0                    ; EHCookieXOROffset.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN7          ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN8          ; ScopeRecord.HandlerFunc.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN11         ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN12         ; ScopeRecord.HandlerFunc.rdata:00012108                 dd 1                    ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN15         ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN16         ; ScopeRecord.HandlerFunc

左边是之前的,右边是现在的。很容易发现,第三个结构的EnclosingLevel成员不同,综合代码我们发现,其值跟代码中“进入3号try 块之前的TryLevel 状态以及,出3号try块之后恢复的TryLevel 状态相同”如果在对比前面两个try 块对应的子结构发现,这个EnclosingLevel 就是try 块对应的外层的try 块的下标。而0FFFFFFFEh 代表的是外层没有try 块(这个编译器生成的try 块)。


掌握了单个函数内部的try 嵌套之后我们来看try 块内部调用函数的情况。

void SubFunc() {int j;__try {j++;}__except(EXCEPTION_EXECUTE_HANDLER){DbgPrint("World\r\n");}}void SEHTest() {int i = 0;__try {SubFunc();}__except(EXCEPTION_EXECUTE_HANDLER) {DbgPrint("Hello\r\n");}}

函数SEHTest 内部是最简单的形式了。如下


.text:00011017                 push    offset stru_12108.text:0001101C                 push    offset __except_handler4

另外,看那个变长结构:

.rdata:00012104                 dd rva _unwind_handler4.rdata:00012108 stru_12108      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012108                                         ; DATA XREF: SubFunc()+7 o.rdata:00012108                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 11010.rdata:00012108                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012108                 dd 0                    ; EHCookieXOROffset.rdata:00012108                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012108                 dd offset $LN5          ; ScopeRecord.FilterFunc.rdata:00012108                 dd offset $LN6          ; ScopeRecord.HandlerFunc.rdata:00012128 stru_12128      dd 0FFFFFFFEh           ; GSCookieOffset.rdata:00012128                                         ; DATA XREF: SEHTest()+7 o.rdata:00012128                 dd 0                    ; GSCookieXOROffset ; SEH scope table for function 110A0.rdata:00012128                 dd 0FFFFFFD4h           ; EHCookieOffset.rdata:00012128                 dd 0                    ; EHCookieXOROffset.rdata:00012128                 dd 0FFFFFFFEh           ; ScopeRecord.EnclosingLevel.rdata:00012128                 dd offset $LN5_0        ; ScopeRecord.FilterFunc.rdata:00012128                 dd offset $LN6_0        ; ScopeRecord.HandlerFunc


两个函数分别有两个该结构。

到这里我们就了解了编译器的SHE 的结构了。一个函数如果有SEH 结构,将只进行一次注册,即使用编译器提供的__except_handler4函数,并用一个变长结构来表示单个函数内部try 块的嵌套结构。如果try 块中有函数调用,且该函数内部也有SHE 结构,其嵌套是通过系统SHE 机制来确保的。即,当子函数中的SHE 不能满足需要,将遍历其父函数的SHE 函数(对于同一编译器来说是同一个异常处理函数)。这样可以确保不同编译器编译的模块之间的调用不会因为SHE 的实现不同而导致错误。下一节我们来看看这个__except_handler4 函数,来解决文章开头出现的问题。


0 0