首页
社区
课程
招聘
[原创]SEH源码赏析之C篇
发表于: 2007-10-24 12:12 31445

[原创]SEH源码赏析之C篇

2007-10-24 12:12
31445

SEH结构化异常处理源码赏析(C篇)
                                                                                        关键字: C SEH 结构化异常处理 _try _finally _except _except_handler3 VC
                                                                                        工程使用工具: VC7.1.3088  IDA pro 4.9.0.863  cdb Windbg Editplus

1.起因
    C++程序员对try,catch,throw都应该很熟悉,能知道VC怎么实现它的人就不多了,不过网络世界使很多人知道了它与SEH(structured exception      handling)有密切关系,我也不例外,也是在若干年前从网络知道了SEH,并且大致也知道SEH的流程.但是和多数人一样在我的实践也很少直接使用SEH,对SEH也就仅限于网络上一些文章的介绍.曾经在用Windbg对某些软件作分析,我遇到了断点失效的情况,查找资料介绍是SEH中的Handler清除了调试寄存器,在分析SEH代码中由于VC没有SEH的源码,于是我产生了一种想法,详细完整地翻译VC的SEH的代码,一劳永逸的解决问题.C和C++的SEH有所不同,C++的要复杂些,我在此介绍的仅为C的SEH代码,也就是__try,__finally,__except,__leave所产生的SEH代码,C++篇有时间的话我再作.我以前看过的资料大都以比较专业的语言介绍,在此我仅以我自己感觉比较通俗的语言介绍,希望能有更多的人能认识,认清SEH.
2.SEH术语:SEH中术语虽然不多,但由于没有SDK的明确定义有时很难区别,各家说的表述也不太统一,因此为此文特定义如下术语,这些术语可能与其它文献有点冲突或细微差别,有的也是我自己的定义:
        A.SEH(structured exception handling): 在C语言中关键字是__try(_try),__finally(_finally),_except(__except),而C++使用的关键字是try,catch.在以下的表述中所有的try均不再特别声明为C关键字,一律默认为C关键字.
        B. EH3_List:_EH3_EXCEPTION_REGISTRATION链表,表头位于FS:[0],0xFFFFFFFF为链表结束标志.编译器在编译一个函数时只要检测到含有_try或__try则为此函数生成一个_EH3_EXCEPTION_REGISTRATION节点,并插入到表头.因为每个函数编译只生成一个节点,因此在一个函数中C和C++的SEH不能同时存在,如果代码中同时有catch和except则不能通过编译就是此原因.
        C. EH3_ScopeTable:是一个由编译器在data section生成的一张表(数组),实质可看作是二叉树结构(可能有多个二叉树顺序存放),节点为_SCOPETABLE_ENTRY类型,其中_SCOPETABLE_ENTRY.ParentLevel是父节点在数组中的位置,EH3_ScopeTable[0]是根节点,_SCOPETABLE_ENTRY.ParentLevel=0xFFFFFFFF.由此可见ParentLevel很重要,是SEH判断try嵌套层次的唯一依据.编译器从函数入口点开始遍历try,每遇到一个try生成一个节点_SCOPETABLE_ENTRY,并放在表最后,注意节点的先后与try的嵌套层次无关.
        D. filter handler:是异常发生后让用户决定是否认识此异常,通过修改异常语句的上下文环境(CONTEXT)可使应用程序能继续正常运行.其返回值有三.
        EXCEPTION_EXECUTE_HANDLER(1): 去执行exception handler,然后进程终止,且不显示出错提示框.
    EXCEPTION_CONTINUE_SEARCH(0): 不执行exception handler,显示出错提示框,进程终止或者进入调试器进行调试.
    EXCEPTION_CONTINUE_EXECUTION(-1): 不执行exception handler,系统用CONTEXT重新设置CPU环境,进程继续执行,如果修改了EIP则从新的EIP开始执行,否则从原异常点开始执行.

    E. exception handler:是异常发生后检测到该异常无法被处理,而进程终止前提醒应用程序执行的收尾工作
        F. termination handler:如果try语句被过早终止,不管是正常离开或者是非正常离开,包括goto,leave及异常时均执行此handler,具体执行过程可查MSDN.
        G. 展开(unwind):这个名词很让人费解,翻译也确实不好命名,我也沿用此名.展开的目的是执行finally对应的termination handler,对照在下面的源代码中我们就能很容易理解MSDN关于finally的解释了.展开分为本地局部展开(_local_unwind)和全局展开(_global_unwind);本地展开:展开此try所在函数try嵌套关系并分别执行其finally对应的termination handler;全局展开:当exception handler不在本try函数时,执行exception handler前需要先执行这之前的termination handler,全局展开就是查找这些termination handler并执行它.需要说明的是全局展开不含本地展开.
3.SEH数据结构
        typedef struct  // (sizeof=0xC)
        {
                DWORD ParentLevel ;           // 当前Handler父层TRY在EH3_ScopeTable中的位置,根没有上一层,故值=-1
                                                           // 形成TRY层次的二叉树结构,与_EH3_EXCEPTION_REGISTRATION.TryLevel物理意义一样
                DWORD FilterFunc;      // 非NULL则HandlerFunc是exception handler,否则termination handler
                DWORD HandlerFunc;     // exception handler or termination handler
        } _SCOPETABLE_ENTRY;
        typedef struct  // (sizeof=0x10)
        {
                _EH3_EXCEPTION_REGISTRATION* pPrev;// 栈上一级EH3_List节点,=0xFFFFFFFF则为最后一个节点
                EXCE_HANDLER ExceptionHandler;           // VC7.1中统一指向_except_handler3
                _SCOPETABLE_ENTRY* pScopeTable;           // 指向一个_SCOPETABLE_ENTRY数组,函数有n个TRY,则数组有n个元素
                                                                                   // p[0]->Try0为根,p[1]->Try1,p[2]->Try2...
                DWORD TryLevel;     // 指示当前指令在Try的层次级别,-1未进入TRY,进第一个TRY为0,第二个为1,...
                                                        // 但它与嵌套层次无关,在编译时确定,从函数代码开始处开始计数
        } _EH3_EXCEPTION_REGISTRATION;
        typedef struct  // (sizeof=0x10)还有待进一步分析其用处
        {
                DWORD unKnown;                // 未知:被编译器赋值
                DWORD HandlerFunc;  // _SCOPETABLE_ENTRY.HandlerFunc
                DWORD firstPara;    // Try所在函数第一个参数:crtMain!EBP+8
                DWORD TryEBP;                // Try所在函数EBP
        }_NLGDestination;
        // 以下在MSDN中均有定义,不再作解释
        typedef struct _EXCEPTION_RECORD
        {
                DWORD    ExceptionCode;
                DWORD ExceptionFlags;
                struct _EXCEPTION_RECORD *ExceptionRecord;
                PVOID ExceptionAddress;
                DWORD NumberParameters;
                ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
        } EXCEPTION_RECORD,*PEXCEPTION_RECORD;
        typedef struct _CONTEXT
        {
                ...         // 依据CPU类型有不同定义,具体可见winnt.h
        } CONTEXT,*PCONTEXT;
        typedef struct _EXCEPTION_POINTERS
        {
                PEXCEPTION_RECORD ExceptionRecord;
                PCONTEXT ContextRecord;
        } EXCEPTION_POINTERS,*PEXCEPTION_POINTERS;
4.SEH汇编要点
        A. 函数中try的数据模型:VC将会为有try函数在栈上首先建立三个变量:
                EBP-18h:                SaveESP                                        // TRY前保存当时ESP,因此有:SaveESP<&Eh3Exception
                EBP-14h:                pExceInfo                                // GetExceptionPointers(),在handler中调用filter前赋值
                EBP-10h:                Eh3Exception                        // 一个_EH3_EXCEPTION_REGISTRATION,其大小刚好为10h哦
                EBP+00h:                ebp                                                // 上一frame的EBP
                EBP+04h:                EIP                                                // CALL返回地址
                在这里我们应该注意到这个公式是成立的:
                                        EBP=&Eh3Exception+10h
                而在_except_handler3中参数: pEh3Exce刚好为&Eh3Exception,所以每当在_except_handler3中要回调filter,exception,termination handler时,在这之前汇编中均有一句:
                                        lea     ebp, [ebx+10h]                // ebx=_except_handler3!arg_pEh3Exception
                明白了这点就不难明白如何在_except_handler3中访问pExceInfo及SaveESP了!
        B. try块的终止:异常发生,goto,return,leave,正常终止.goto和return可能跨过其它try,所以必须要展开到目的地所在的TryLevel,但是leave关键字不会跨过其它try,只是跳出自己这层try,如果编译器检测到这层try有termination handler,则用CALL xxx直接调用.当然也有例外,这也是VC聪明的地方,如果函数只有一个try,则根本不用展开而直接使用CALL了,这种情况有时可见.这样就不难理解如下代码:
                goto 004013fb 终止代码:
                        // ---------------goto跳出try----------------------------------------
                                        goto try0;
                        push    0                                                        或者        (仅一个try时)                CALL 00401070        // 直接调termination handler
                        lea     eax, [ebp+var_Eh3Exce]                                                                jmp loc_4013FB
                        push    eax
                        call    __local_unwind2                                        // 展开到0(即第一个try内,goto目的地肯定在第一个try内)
                        add     esp, 8
                        jmp     loc_4013FB

                return       终止代码:(无返回值)
                        // ---------------return跳出try--------------------------------------
                        push        0FFFFFFFFh
                        lea         eax,[ebp-10h]
                        push        eax  
                        call        __local_unwind2 (401786h)        // 展开到-1(即所有try外)
                        add         esp,8
                                        return ;
                        jmp         $L19800 (401139h)

                return var_i 终止代码:(有返回值)
                        // ---------------return(带返回值)跳出try----------------------------
                        mov         eax,dword ptr [i]
                        mov         dword ptr [ebp-100h],eax        // 返回值先被保存
                        push        0FFFFFFFFh
                        lea         ecx,[ebp-10h]
                        push        ecx  
                        call        __local_unwind2 (4017D6h)        // 再展开,即使finally中改变了i,也不会改变返回值!!!
                        add         esp,8
                                        return i;
                        mov         eax,dword ptr [ebp-100h]        // 取保存的返回值来保存
                        jmp         $L19800 (40117Bh)                        // 跳到复原先前SEH链处

                leave和正常退出的代码:
                        // ---------------leave跳出try---------------------------------------
                                        if (x > 18)
                        004010FD  cmp         dword ptr [x],12h
                        00401101  jle         FunC+55h (401105h)
                                                __leave;
                        00401103  jmp         FunC+66h (401116h) // 直接调用termination handler
                                        printf("%s Try!\n",fun);
                        00401105  mov         eax,dword ptr [fun]
                        00401108  push        eax  
                        00401109  push        offset string "%s Try!\n" (410130h)
                        0040110E  call        printf (401650h)
                        00401113  add         esp,8
                        00401116  mov         dword ptr [ebp-4],0FFFFFFFFh // 退出try
                        // ---------------正常退出try----------------------------------------
                        0040111D  call        $L19798 (401124h) // 直接调用termination handler
                        00401122  jmp         $L19801 (40113Fh)

        C. _except_handler3执行两次原因,实际上理解了展开就能理解它,举例解释如下:
                EH3_List如:FS[0]->E1->E2->E3->E4,在E4中发生异常,依次搜索E1,E2,E3,最后在E4中找到能处理此异常的filter handler,这样E4,E3,E2,E1的_except_handler3均执行了一次,这是第一次.
                第二次:在执行E4的exception handler前要先调用全局展开(_global_unwind2)以运行E1,E2,E3的termination handler在全局展开(_global_unwind2)中将为EXCEPTION_RECORD.ExceptionFlags增加标志_EH_UNWINDING(2),再依次调用它们的_except_handler3,这就是E1,E2,E3的_except_handler3的第二次调用,但E4只有一次.
5. VC7.1下的SEH的C代码:
        // 源码基本以汇编为蓝本,没有进行优化,主要是为了方便大家与汇编对照阅读
        #define MAX_PAGES 0x10
        int ValidateEH3RN(_EH3_EXCEPTION_REGISTRATION* pEh3Exce)
        {
                // 1.验证_EH3_EXCEPTION_REGISTRATION.pScopeTable的合法性:4字节对齐,且不在栈空间内,因为它是编译器生成的全局变量
                _SCOPETABLE_ENTRY* pScopeTable=pEh3Exce->pScopeTable;
                if (((DWORD)pScopeTable & 0x3) == 0)
                        return 0;
                NT_TIB *pTeb = (NT_TIB*)FS[0x18];
                DWORD nStackLimit = pTeb->StackLimit;
                if (pScopeTable >= pTeb->StackLimit && pScopeTable < pTeb->StackBase)
                        return 0;// pScopeTable在栈内肯定不合法
                // 2.判断二叉树pScopeTable是否合法,并统计exception handler数量
                if (pEh3Exce->TryLevel == -1)
                        return 1;// 表示语句不在try中
                DWORD nFilters,count;
                nFilters=count=0;
                for(;nFilters <= pEh3Exce->TryLevel; nFilters++)
                {// LOOP1:验证已进入的TRY的SCOPETABLE_ENTRY是否都合法
                        if(pScopeTable[nFilters].ParentLevel!=-1 && pScopeTable[nFilters].ParentLevel>=nFilters)
                                return 0;// EnclosingLevel在二叉树中不合法:不是根结点且ParentLevel>=nFilters
                                                 // ParentLevel>=nFilters不合法原因:二叉树存贮不可能儿子先存
                        if (pScopeTable[nFilters].FilterFunc == NULL)
                                continue;// termination handler不统计
                        count++;
                }
                if (count != 0)
                {// 有异常处理,验证saveESP
                        PVOID saveESP =0;// 运行时真实值 = crtMain!saveESP=crtMain!(ebp-18h);
                        if (saveESP < nStackLimit || saveESP >= pEh3Exce)
                                return 0;// 不在栈内,或不在TRY函数内,saveESP为TRY函数的栈顶
                }

                // 3.找g_rgValidPages中是否已经有pScopeTable的页基址
                static int g_nValidPages=0;// g_rgValidPages数组有效元素个数
                static int g_rgValidPages[MAX_PAGES]={0}; // MaxPages=0x10
                DWORD nPageBase = (DWORD)pScopeTable & 0xfffff000;// 取pScopeTable所在页基址
                int nFind=0;
                if(g_nValidPages > 0)
                {
                        do
                        {// LOOP2
                                if(nPageBase==g_rgValidPages[nFind])
                                        goto Finded;// 找到
                                nFind++;
                        }
                        while(nFind<g_nValidPages);
                }

                // 4.没有过在g_rgValidPages找到nPageBase则判断pScopeTable是否在合法的EXE映像内
                MEMORY_BASIC_INFORMATION memInfo;
                if (VirtualQuery(pScopeTable,&memInfo,sizeof(memInfo)) == 0)
                        return -1;
                if (memInfo.Type != 0x01000000)// MEM_IMAGE
                        return -1;
                DWORD protect = 0xCC;// PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY;
                if (memInfo.Protect & protect)
                {// 有指定属性
                        IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)memInfo.AllocationBase;
                        if (pDosHeader->e_magic != 'ZM')
                                return -1;        // 非法DOS头,pScopeTable不为编译器分配的空间
                        IMAGE_NT_HEADERS* pPeHeader = (IMAGE_NT_HEADERS*)((char*)pDosHeader+pDosHeader->e_lfanew);
                        if (pPeHeader->Signature != 'ZM')
                                return -1;        // 非法PE头,pScopeTable不为编译器分配的空间
                        IMAGE_OPTIONAL_HEADER32* pOptHeader = &pPeHeader->OptionalHeader;
                        if (pOptHeader->Magic != 0x10b)
                                return -1; // 非WIN32 EXE
                        DWORD rvaScope = (DWORD)pScopeTable-(DWORD)pDosHeader; // 计算pScopeTable的RAV
                        if (pPeHeader->FileHeader.NumberOfSections <=0 )
                                return -1;
                        IMAGE_SECTION_HEADER* pSection = (IMAGE_SECTION_HEADER*)(pPeHeader+1);
                        if (rvaScope >= pSection->VirtualAddress && rvaScope < pSection->VirtualAddress+pSection->Misc.VirtualSize)
                        {// rvaScope在代码节内
                                if (pSection->Characteristics & 0x80000000)// 0x80000000=IMG_SCN_MEM_WRITE
                                        return 0;
                        }
                }

                // 5.对新验证的nPageBse插入到数组中
                static int g_nLock=0; // 1:上锁,0:解锁
                if(InterlockedExchange(&g_nLock,1)!=0)// 写线程锁
                        return 1;// 其它线程已经进入,则不能再进入
                int k=g_nValidPages;
                if(k > 0)
                {
                        do
                        {// LOOP4:判断其它线程是否写入这个页基址
                                if(nPageBase==g_rgValidPages[k-1])
                                        break;// 找到,k>0
                                k--;
                        }
                        while(k>0);
                }
                if (k==0)
                {// 没有找到,nPageBase插入到g_rgValidPages头
                        int pages = 0x0F;
                        if(g_nValidPages <= pages)
                                pages = g_nValidPages;
                        k=0;
                        if(pages >= 0)
                        {
                                do
                                {// LOOP5
                                        int temp=g_rgValidPages[k];
                                        g_rgValidPages[k]=nPageBase;
                                        nPageBase=temp;
                                        k++;
                                }
                                while(k<=pages);
                        }
                        if (g_nValidPages<0x10h)
                                g_nValidPages++;
                }
                InterlockedExchange(&g_nLock,0);// 解锁
                return 1;

                // 6.找到的nPageBase移到头
                //   但前面找到的结果也可能被其它线程改变位置甚至推出数组,但无论如何这个nPageBase合法可不再验证
        Finded:
                if (nFind <= 0)// 相当于if(nFind == 0)
                        return 1;// nPageBase已经在头,可直接返回
                if(InterlockedExchange(&g_nLock,1)!=0)// 写线程锁
                        return 1;// 其它线程已经进入,则不能再进入
                if(g_rgValidPages[nFind] != nPageBase)
                {// 再次对找到的pos进行比较,因为其它线程可能又修改了这个元素的值
                        nFind = g_nValidPages-1;
                        if (nFind>=0)
                        {
                                while(nFind>=0)
                                {// LOOP3
                                        if (g_rgValidPages[nFind]==nPageBase)
                                                break;
                                        nFind--;
                                }
                                if (nFind>=0)
                                        goto End1;
                        }
                        // 没找到,新增加
                        if(g_nValidPages < 0x10)
                                g_nValidPages++;
                        nFind = g_nValidPages-1;
                        goto end2;
                }
                else
                        goto end2;
        end1:
                if (nFind != 0)
                {
        end2:
                        if (nFind >= 0)
                        {
                                for(int j = 0;j<=nFind;j++)
                                {// LOOP6:g_rgValidPages中找到的nPageBase移到头或新nPageBase插入头,其余每个元素向后推
                                        int temp=g_rgValidPages[j];
                                        g_rgValidPages[j]=nPageBase;
                                        nPageBase=temp;
                                }
                        }
                }
                InterlockedExchange(&g_nLock,0);// 解线程锁
                return 1;
        }
        void _global_unwind2(_EH3_EXCEPTION_REGISTRATION*pEh3Exce)
        {// 对调用RtlUnwind的封装
                RtlUnwind(pEh3Exce,offset exit,0,0);
        exit:
                return;
        }
        int _UnwindHandler(EXCEPTION_RECORD*pExceRec,_EH3_EXCEPTION_REGISTRATION*pEh3Exce,
                                           void*,_EH3_EXCEPTION_REGISTRATION** ppEh3Exce)
        {
                if(pExceRec->ExceptionFlags && 6 == 0)
                        return 1;// ExceptionContinueSearch
                *ppEh3Exce = pEh3Exce;
                return 3;// ExceptionCollidedUnwind
        }
        // NLG == "non-local-goto"
        _NLGDestination g_NLGDestination;// 此全局变量我至始至终未见到任何其它EXE处或者DLL使用,通知是何意义暂无知!!!!!!!
        void _NLG_Notify1(int x)// --------------------------CallSettingFrame调用
        {// g_NLGDestination全局分配变量:0x19930520
                g_NLGDestination.dwInCode = EBP+8;// Try函数中第一个参数???
                g_NLGDestination.HandlerFunc = EAX;// 调用前通过EAX传入
                g_NLGDestination.TryEBP = EBP;// Try中的EBP???
        }
        void _NLG_Notify(int x)// --------------------------_except_handler3,_local_unwind2调用
        {// 传入参数未用
                // g_NLGDestination.unKnown全局分配变量:0x19930520
                g_NLGDestination.dwInCode = EBP+8;// Try函数中第一个参数,如:FunB(i,j)!i,crtMain!ebp+8
                g_NLGDestination.HandlerFunc = EAX;// 调用前通过EAX传入:pScopeTable.HandlerFunc
                g_NLGDestination.TryEBP = EBP;// Try中的EBP
        }
        // nToLevel:展开到此为止(不含nToLevel),举例:在goto中nToLevel由目标所在try决定,因为系统规定跳入TRY是不合法的,因此
        // goto不能跳入其它TRY(嵌套的上层TRY是合法的,很明显平级层是不合法的)层,这时编译是通不过的.
        void _local_unwind2(_EH3_EXCEPTION_REGISTRATION*pEh3Exce,int nToLevel)
        {// 用ESP,未用EBP,之前的EBP传入内调的_NLG_Notify,__try中的goto将用此函数展开得到其handler
                _EH3_EXCEPTION_REGISTRATION  eh3Unwind;
                eh3Unwind.TryLevel = (int)pEh3Exce;
                eh3Unwind.pScopeTable = (_SCOPETABLE_ENTRY*)-2;
                eh3Unwind.ExceptionHandler = (EXCE_HANDLER)_UnwindHandler;
                eh3Unwind.pPrev = (_EH3_EXCEPTION_REGISTRATION*)FS[0];
                FS[0]=ESP;
                int nLevel;//
                while(1)
                {
                        _SCOPETABLE_ENTRY *pScope=pEh3Exce->pScopeTable;
                        ESI = pEh3Exce->TryLevel;
                        if (ESI == -1)
                                break;
                        if (ESI == nToLevel)
                                break;
                        nLevel=pScope[ESI].ParentLevel;
                        pEh3Exce->TryLevel=nLevel;       
                        if (pScope[ESI].FilterFunc != NULL) // 有FilterFunc则是exception handler
                                continue;
                        EAX=pScope[ESI].HandlerFunc;                // 无FilterFunc则是termination handler
                        _NLG_Notify(0x101);                        // 无返回值
                        CALL(EAX);                                        // 这就是展开所求得的Handler,可以是termination handler,exception Handler
                }
                FS[0]=(DWORD)eh3Unwind.pPrev;
        }

        // _except_handler3执行两次原因:
        // 举例:EH3_List如 FS[0]->E1->E2->E3->E4,在E4中发生异常,依次搜索E1,E2,E3,最后在E4中找到能处理此异常的filter handler,这样E4,E3,E2,E1的_except_handler3均执行了一次,这是第一次.
        //     第二次:在执行E4的exception handler前要先调用全局展开(_global_unwind2)以运行E1,E2,E3的termination handler
        // 在全局展开(_global_unwind2)中将为EXCEPTION_RECORD.ExceptionFlags增加标志_EH_UNWINDING(2),再依次调用它们的
        // _except_handler3,这就是E1,E2,E3的_except_handler3的第二次调用,但E4只有一次.
        int _except_handler3(EXCEPTION_RECORD*pExceRec,_EH3_EXCEPTION_REGISTRATION*pEh3Exce,
                                                 CONTEXT* pContextRecord/*,void * DispatcherContext*/)
        {
                if ((pExceRec->ExceptionFlags & 6) == 0)
                {// 无_EH_UNWINDING and _EH_EXIT_UNWIND
                        EXCEPTION_POINTERS ePointer;
                        ePointer.ExceptionRecord = pExceRec;
                        ePointer.ContextRecord = pContextRecord;
                        // pEh3Exce=&EhRecord
                        // lea     EAX, [EBP+var_ExcePointers]
                        // mov     [ebx-4], EAX ;EBX=pEh3Exce
                        *((DWORD*)pEh3Exce-1)=(DWORD)&ePointer;        // 给_XcptFilter的参数pExceptPointers赋值
                        if (ValidateEH3RN(pEh3Exce))
                        {
                                _SCOPETABLE_ENTRY* pScopeTable=pEh3Exce->pScopeTable;
                                int i = pEh3Exce->TryLevel;
                                while(1)
                                {
                                        if (i==-1)
                                                return 1;// handler拒绝处理这个异常
                                        EAX=pScopeTable[i].FilterFunc;
                                        if(EAX!=NULL)
                                        {// 属于exception handler
                                                EBP=(DWORD)pEh3Exce+0x10;// 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
                                                CALL(EAX);// 没有参数,ePointer已传过去
                                                if(EAX != 0)
                                                {
                                                        if ((int)EAX < 0)
                                                                return 0;                                // 如果在filter中返回值<0,此返回后会引起代码为
                                                                                                                // EXCEPTION_NONCONTINUABLE_EXCEPTION(0xC0000025)的异常,反复如此栈溢出
                                                        _global_unwind2(pEh3Exce);        // 调用handler前,展开前级函数中的termination handler
                                                        EBP=(DWORD)pEh3Exce+0x10;        // 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
                                                        _local_unwind2(pEh3Exce,i);        // 此函数内不直接用EBP,但暗传EBP给可能执行的_NLG_Notify
                                                                                                                // 并展开本函数中的termination handler
                                                        EAX=pScopeTable[i].HandlerFunc;
                                                        _NLG_Notify(1);// 此函数内要用EAX
                                                        pEh3Exce->TryLevel=pScopeTable[i].ParentLevel;// 修改节点所在层次使EIP在TRY中位置表示正确
                                                        EAX = pScopeTable[i].HandlerFunc;
                                                        CALL(EAX);// 进入异常处理,不再返回此函数(内部可接受异常并修改环境,继续执行)
                                                }
                                        }
                                        i=pScopeTable[i].ParentLevel;
                                }
                                return 0;
                        }
                        else
                        {
                                pExceRec->ExceptionFlags |= 8;// 置pEh3Exce无效标志,它不属于此handler
                                return 1;// handler拒绝处理这个异常,系统转调上一级
                        }
                }
                else
                {// 有_EH_UNWINDING or _EH_EXIT_UNWIND
                        EBP=(DWORD)pEh3Exce+0x10;// 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
                        _local_unwind2(pEh3Exce,-1);
                }
                return 1;
        }
6. 结语
    SEH代码还有很多,它们有许多包含在NTDLL.DLL中,比如RtlUnwind我曾经非常想翻译它,但至今我也还未完成,因此我也就没有粘出来.实际上从我以上粘出的初步代码,我们应该能看出SEH并不复杂,翻译DLL中的相关代码也并不困难,我估计我们唯一无法看见的代码就是中断处理的起始部分,把它想像成一个黑匣子就成了.附件有我的IDA注释说明,注释由于是一个动态过程,特别是起初的注释可能有错,我没有精力再去一一阅读更正,希望大家理解,一并奉献上,希望对大伙有用.
      在这里,我只是作了SEH源码的一部分翻译工作,在翻译过程中我也在网上查看了很多关于SEH方面的文章,在此我不再一一列出,一并致谢这些无私奉献的网友和同行们!看了此文相信再看其它SEH专著就应该容易多了!
      今天就是我们的嫦娥飞天的日子,谨以此文预祝她能回宫成功!顺便也希望此文是我下一个工作的起始点!
                                       
                                                                                                                                                                2007.10.24 于北京


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (21)
雪    币: 58
活跃值: (20)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
我本有一个与此文相关的附件,内有IDA文件,实例文件,ASM列表的,可是我没有过权限上传,请版主开恩让我上传!
2007-10-24 12:18
0
雪    币: 177
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
好文章,mark一下,有空再看
2007-10-24 14:44
0
雪    币: 47147
活跃值: (20460)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
4
[quote=xinlin;374216]  今天就是我们的嫦娥飞天的日子,谨以此文预祝她能回宫成功!顺便也希望此文是我下一个工作的起始点!
         
                                        2007.07.24 于北京

...[/quote]
日期是不是搞错了?
2007-10-24 14:56
0
雪    币: 7361
活跃值: (4562)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
都是高手啊!我什么也看不懂! 还需要努力学习啊!
2007-10-24 14:57
0
雪    币: 267
活跃值: (1913)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
哈,看雪看的好认真啊,貌似楼主搞错了.差了3个月
2007-10-24 15:21
0
雪    币: 334
活跃值: (22)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
好文章!@_@
2007-10-24 16:16
0
雪    币: 87
活跃值: (47)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
8
顶一下。有空仔细学习一下。
2007-10-24 21:38
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
精品收藏ing  
2007-10-24 23:04
0
雪    币: 223
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
设置了精华后好像就有权限上传附件了
2007-10-24 23:30
0
雪    币: 177
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
晕,我说怎么找不到帖子了,原来被老大移了地盘了
2007-10-25 10:32
0
雪    币: 58
活跃值: (20)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
IDA源文件上传成功!
谢谢版主!
上传的附件:
2007-10-25 11:14
0
雪    币: 58
活跃值: (20)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
是写错了?:) 原贴就不改啦
应该是:
                                                     2007.10.24   于北京
2007-10-25 11:17
0
雪    币: 1919
活跃值: (901)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
14
厉害,学习了~
2007-10-25 12:20
0
雪    币: 8764
活跃值: (5240)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
学习了。这里牛人还真不少。。。
2007-10-25 12:59
0
雪    币: 58
活跃值: (20)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
16
今天搜到好多引用的地方,都看到我落笔时间写错了,今天还是把它修改过来吧!
看来不修改不行啊
2007-11-2 19:13
0
雪    币: 154
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
膜拜
2010-12-3 09:56
0
雪    币: 421
活跃值: (83)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
大牛,期待下一篇关于C++的SEH分析。
2010-12-3 10:50
0
雪    币: 564
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
文章很详细, 赞、
2012-7-22 05:14
0
雪    币: 3
活跃值: (81)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
精华啊一定要收藏研究
2012-7-22 07:07
0
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
收藏,到时候看
2012-7-22 09:29
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
22
从程序员的角度来说,文中有个非常大的错误:

C++中的try-catch跟SEH是完全不同的两回事。

try{}
catch(){}



_try{}
_catch(){}

本质上完全不同。

目前在C++范畴以及windows系统平台范畴内异常处理有三种:
1.C++ Exception Handle(语言依赖性)
2.SEH(系统依赖性)
3.VEH(系统依赖性)
2013-8-11 17:48
0
游客
登录 | 注册 方可回帖
返回
//