PE结构、SEH相关知识学习笔记

来源:互联网 发布:js url编码 编辑:程序博客网 时间:2024/05/21 19:30

原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题

PE结构的学习

原文中用fasm自己构造了一个pe,这里贴一个用masm的,其实是使用WriteFile API将编写的PE数据写成文件~也没啥好说的,PE结构在这里没有仔细介绍,需要可以另外查询,剩下要说的的基本都在代码注释里了

参考:点击打开链接(PEDIY技术之新思路(二)_用'高级'编译器MASM实现自定义PE文件结构)

Pe.asm:

REMOTE_CODE_START equ this BYTE;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; DOS Header;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>PE_HEADER_START equ this BYTEDOS_HEADER:;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; 事实上,从这里到e_res2无用部分可以用一句DUP(0)来完成; 在这里编译器不会检查你结构的定义是否正确, 这些变量名也是没有意义的; 这样写只是有利于我们认识PE结构而已,除了关键字,你可以任意写; 因为它相当于写二进制型汇编代码, 事实上,和直接写二进制是一样的; 只是这样就轻松多了。当然了, 这种方式的定义给我们带来的问题就是重定位; 所以需要重定位的地方要小心了, 你也可以把PE头部提上来到DOS头部里面来变形PE头; 或者整个最小PE什么的, 随你怎么高兴怎么玩,但是要保证e_lfanew定位的正确; 你想怎么来就怎么来,你自由了!但是,没有绝对的自由,俗话说得好; 任悟空本领再高,他也跳不出如来佛(OS)的手掌心啊;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>e_magicdb 'MZ'e_cplpdw 0e_cpdw 0e_crlcdw 0e_cparhdrdw 0e_minallocdw 0e_maxallocdw 0e_ssdw 0e_spdw 0e_csumdw 0e_ipdw 0e_csdw 0e_lfarlcdw 0e_ovno dw 0e_resdw 4 dup(0)e_oemiddw 0e_oeminfodw 0e_res2dw 10 dup(0)e_lfanewdd NT_HEADERS - 00401000h;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Dos小程序,写不写都行;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Dos_Stub:mov ah, 4chint 21h;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Nt Header;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>NT_HEADERS:Signaturedd 4550h; 'PE'Machinedw 14chNumberOfSectionsdw 2TimeDateStampdd 0PointerToSymbolTabledd 0NumberOfSymbolsdd 0SizeOfOptionalHeaderdw 0e0hCharacteristicsdw 010fhMagicdw 10bhMajorLinkerVersiondb 0MinorLinkerVersiondb 0SizeOfCodedd 200hSizeOfInitializedDatadd 0SizeOfUninitializedDatadd 0AddressOfEntryPointdd 1000h; oepBaseOfCodedd 1000h; Code Section RVABaseOfDatadd 0; Data Section RVAImageBasedd 00400000hSectionAlignmentdd 1000h; Section Mem AlignFileAlignmentdd 200h; Section Disk AlignMajorOperSystemVersiondw 0MinorOperSystemVersiondw 0MajorImageVersiondw 0MinorImageVersiondw 0MajorSubsystemVersiondw 4MinorSubsystemVersiondw 0Win32VersionValuedd 0; Reserved 0SizeOfImagedd 3000h; PE加载到内存后的映像大小SizeOfHeadersdd 200h; DosHeaders + DosStub + NtHeader + Section Header_CheckSumdd 0SubSystemdw 2; GUIDllCharacteristicsdw 0SizeOfStackReservedd 100000hSizeOfStackCommitdd 1000h; Stack = 4kbSizeOfHeapReservedd 100000hSizeOfHeapCommitdd 1000h; Heap = 4kbLoaderFlagsdd 0NumberOfRvaAndSizesdd 10h; 16DirectoryData1dq 0; 没有输出表,填0ImportTableAddressdd IMPORT_START - 00401000h - 400h + 2000hImportTableSizedd IMPORT_LENGTHDirectoryData2dq 14 dup(0); 余下的数据目录填0;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Section 1;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>SECTION_HEADER1:Name1db 'CODE', 0, 0, 0, 0; 节区名,8字节大小VirtualSizedd CODE_LENGTHVirtualAddressdd 1000h; 内存中的偏移SizeOfRawDatadd 200h; 这里的CODE_START - 00401000h = 00401200h - 00401000h; 00401000h为Pe.asm, PE头200h,CODE节区在PE头后面,所以为; 00401200h,这里这样做可以求出代码段所在文件偏移PointerToRawDatadd CODE_START - 00401000h; 文件中的偏移,也可以直接成为200h,因为定义的PE头大小为200PointerToRelocationsdd 0PointerToLinenumbersdd 0NumberOfRelocationsdw 0NumberOfLinenumbersdw 0_Characteristicsdd 0e0000020h; 节区属性;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Section 2;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>SECTION_HEADER2:Name2db 'IMPORT', 0, 0; 这个块可以去掉,将输入表放入代码块里VirtualSize2dd IMPORT_LENGTHVirtualAddress2dd 2000h; 内存中的偏移SizeOfRawData2dd 200h; 同上,这里IMPORT_START = 00401400h; PE头 + CODE节区 = 400hPointerToRawData2dd IMPORT_START - 00401000h; 文件中的偏移,也可以直接写成400hPointerToRelocations2dd 0PointerToLinenumbers2dd 0NumberOfRelocations2dw 0NumberOfLinenumbers2dw 0_Characteristics2dd 0e0000020hPE_HEADER_END equ this BYTEPE_HEADER_LENGTH equ offset PE_HEADER_END - offset PE_HEADER_STARTZeroSpace1db 200h - PE_HEADER_LENGTH dup(0); 不足200h, 间隙填0;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Code Start;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>CODE_START equ this BYTEmov eax, OFFSET IAT_1lea eax, [szContextR - 200h]; 401200h为code段开始,szContextR = 401220h, 这是在PE.ASM包含在MakePe.asm时的情况lea ebx, [szCaptionR - 200h]; 如果要单独作为程序,其起始地址00401000h,要减去200hpush MB_OKpush ebxpush eaxpush 0; IAT_1(0040143ch) - 1000h为不在CODE段 = (0040043ch); 0040043ch - 400h = 0040003ch这样就等于ImageBase + 在导入表(400h)中的偏移(文件偏移); 再加上2000h = 0040203ch为实际内存地址call dword ptr [IAT_1 - 1000h - 400h + 2000h]push 0call dword ptr [IAT_2 - 1000h - 400h + 2000h]szContextRdb 'Congratulations! You make it!', 0dh, 0ah, 0szCaptionRdb 'OK', 0CODE_END equ this BYTECODE_LENGTH equ offset CODE_END - offset CODE_STARTZeroSpace2db 200h - CODE_LENGTH dup(0);>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; Import Start;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>IMPORT_START equ this BYTEIID_1:; IAT_1 - 401000h = 获得IAT_1的文件偏移, 再减400h为获得在导入表中的偏移(文件中); 加2000h获得在内存中的偏移(RVA), 下面的一些和这个一个道理OriginalFirstThunkdd IAT_1 - 401000h - 400h + 2000hTimeDateStempdd 0ForwarderChaindd 0DllNamedd DllName1 - 401000h - 400h + 2000h; 这里为了方便,把它和OriginalFirstThunk指向同一个地址的IAT结构FirstThunkdd IAT_1 - 401000h - 400h + 2000hIID_2:OriginalFirstThunk2  dd IAT_2-401000h-400h+2000hTimeDateStemp2   dd 0ForwarderChain2  dd 0DllName2         dd _DllName2-401000h-400h+2000hFirstThunk2      dd IAT_2-401000h-400h+2000hIID_END:IIDEND  dd 5 dup(0); 用一个全0的结构作为结束IAT_1:AddressOfData1  dd IIBN_1-401000h-400h+2000hAddressOfDataEnd1  dd 0IAT_2:AddressOfData2  dd IIBN_2-401000h-400h+2000hAddressOfDataEnd2  dd 0IIBN_1:Hint1  dw 0Nama1  db 'MessageBoxA',0DllName1 db 'user32.dll',0,0IIBN_2:Hint2  dw 0Nama2  db 'ExitProcess',0_DllName2 db 'kernel32.dll',0,0IMPORT_END   equ  this BYTE IMPORT_LENGTH  equ offset IMPORT_END - offset IMPORT_START ZeroSpace3 db 200h - IMPORT_LENGTH dup(0);>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; THE_PE_END;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>REMOTE_CODE_END equ this byteREMOTE_CODE_LENGTH equ offset REMOTE_CODE_END - offset REMOTE_CODE_START

MakePe.asm:

.386.model flat, stdcalloption casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.datahOutFiledd 0BytesWritedd 0.constszCaptiondb 'Info', 0szContextdb 'Success', 0szOutFileNamedb 'Pe.exe', 0.code;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>; 将PE数据代码引入,编译并产生Pe.exe文件;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>include Pe.asmstart:invoke CreateFile, offset szOutFileName, GENERIC_READ or GENERIC_WRITE, \FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULLmov hOutFile, eaxinvoke WriteFile, hOutFile, offset REMOTE_CODE_START, REMOTE_CODE_LENGTH, \ addr BytesWrite, NULLinvoke MessageBox, NULL, offset szContext, offset szCaption, MB_OKinvoke ExitProcess, 0end start


SEH的学习


发生异常时系统的处理顺序(by Jeremy Gordon):

    1. 系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统
    挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?

    2. 如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果
    你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.

    3. 每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,
    可交由链起来的其他例程处理.

    4. 如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.

    5. 如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异
    常处理例程的话,系统转向对它的调用.

    6. 如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,
    你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统
    就调用ExitProcess终结程序.

    7. 不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.


有两种类型的异常处理句柄,一种是final型的,这是在你的异常未能得到线程相关处理例程处理操作系统在即将关闭程序之前会
回调的例程,这个例程是进程相关的而不是线程相关的,因此无论是哪个线程发生异常未能被处理,都会调用这个例程.

.386.model flat, stdcalloption casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.dataszCaptiondb 'TestSEH', 0szMsgOKdb 'OK, the exception was handled by final handler!', 0szMsgERR1db 'It would never Get here!', 0buffdb 200 dup(0).codestart:lea eax, Final_Handlerinvoke SetUnhandledExceptionFilter, eaxxor ecx, ecxmov eax, 200cdqdiv ecx; 以下不会运行invoke MessageBox, NULL, addr szMsgERR1, addr szCaption, \MB_OK or MB_ICONEXCLAMATIONinvoke ExitProcess, NULLFinal_Handler:invoke MessageBox, NULL, addr szMsgOK, addr szCaption, \MB_OK or MB_ICONEXCLAMATIONmov eax, EXCEPTION_EXECUTE_HANDLER; 1, 这时不出现非法操作的对话框;mov eax, EXCEPTION_CONTINUE_SEARCH; 0, 出现,这时是调用系统默认的异常处理过程,程序被终结;mov eax, EXCEPTION_CONTINUE_EXECUTION; -1, 不断出现对话框,你将先去死循环; 因为我们并没有修复ecx,所以不断产生异常,然后不断调用这个例程retend start

windows根据你的异常处理程序的返回值来决定如何进一步处理
EXCEPTION_EXECUTE_HANDLER            equ 1    表示我已经处理了异常,可以优雅地结束了
EXCEPTION_CONTINUE_SEARCH            equ 0    表示我不处理,其他人来吧,于是windows调用默认的处理程序
                                                                                           显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTION     equ -1   表示错误已经被修复,请从异常发生处继续执行


另一种是per_Thread Exception Handler->线程相关的异常处理,通常每个线程初始化准备好运行时fs指向一个TIB结构 (THREAD INFORMATION BLOCK)

这个结构的第一个元素fs:[0]指向一个_EXCEPTION_REGISTRATION结构, 后面_EXCEPTION_REGISTRATION为了简化,用ERR来代替这个结构...不要说没见过啊...
fs:[0]->
    _EXCEPTION_REGISTRATION struc
    prev dd ?                      ;前一个_EXCEPTION_REGISTRATION结构
    handler dd ?                  ;异常处理例程入口....呵呵,现在明白该怎么作了吧
    _EXCEPTION_REGISTRATION ends
    我们可以建立一个ERR结构然后将fs:[0]换成指向他的指针,当然最常用的是堆栈,如果你用静态内存区也可以,没有人阻止你
    在asm世界,放心地干吧,除了多S几次之外,通常不会有更大的损失
    把handler域换成你的程序入口,就可以在发生异常时调用你的代码了

Ps: 下面的代码运行后会弹出异常处理例程中的对话框,然后弹出系统错误报告对话框,点击关闭后,又会弹出异常处理例程中的对话框,具体原因参见<Windows环境32位汇编语言程序设计>14.3.4节

注意和final返回值的含义不同

.386.model flat, stdcalloption casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.dataszCaptiondb 'TestSEH', 0szMsgOKdb "It's now in the Per_Thread handler!", 0szMsgERR1db 'It would never Get here!', 0buffdb 200 dup(0).codestart:assume fs:nothingpush offset perThread_Handlerpush fs:[0]mov fs:[0], espxor ecx, ecxmov eax, 200cdqdiv ecx; 以下不会运行invoke MessageBox, NULL, addr szMsgERR1, addr szCaption, \MB_OK or MB_ICONINFORMATIONpop fs:[0]add esp, 4invoke ExitProcess, NULLperThread_Handler:invoke MessageBox, NULL, addr szMsgOK, addr szCaption, \MB_OK or MB_ICONINFORMATIONmov eax, 1; ExceptionContinueSearch, 不处理,由其他例程或系统处理;mov eax, 0; ExceptionContinueExecution, 表示修复CONTEXT, 可以从已成发生处继续执行; 如果返回0,你会陷入死循环,不断跳出对话框retend start

当异常发生时,系统给了我们一个处理异常的机会,他首先会调用我们自定义的seh处理例程,当然也包括
了相关信息,在调用之前,系统把包含这些信息结构的指针压入stack,供我们的异常处理例程调用,
传递给例程的参数通常是四个,其中只有三个有明确意义,另一个到现在为止还没有发现有什么作用,
这些参数是:pExcept:DWORD,pErr:DWORD,pContext:DWORD,pDispatch意义如下:
pExcept:  ---  EXCEPTION_RECORD结构的指针
pErr:    ---  前面ERR结构的指针
pContext: --- CONTEXT结构的指针
pDispatch:---没有发现有啥意义

ERR结构是前面介绍的_EXCEPTION_REGISTRATION结构,往前翻翻,Dispatch省略,下面介绍
EXCEPTION_RECORD和CONTEXT结构的定义(Windows核心编程第五版P657, Windows环境下32位汇编程序设计第二版P508):

        EXCEPTION_RECORD STRUCT          ExceptionCode        DWORD      ?      ;//异常码          ExceptionFlags        DWORD      ?      ;//异常标志          pExceptionRecord      DWORD      ?      ;//指向另外一个EXCEPTION_RECORD的指针          ExceptionAddress      DWORD      ?      ;//异常发生的地址          NumberParameters      DWORD      ?      ;//下面ExceptionInformation所含有的dword数目          ExceptionInformation  DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)        EXCEPTION_RECORD ENDS                      ;//EXCEPTION_MAXIMUM_PARAMETERS ==1

ExceptionCode 异常类型,SDK里面有很多类型,你可以在windows.inc里查找STATUS_来找到更多
的异常类型,下面只给出hex值,具体标识定义请查阅windows.inc,你最可能遇到的几种类型如下:

              C0000005h----读写内存冲突
              C0000094h----非法除0
              C00000FDh----堆栈溢出或者说越界
              80000001h----由Virtual Alloc建立起来的属性页冲突
              C0000025h----不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常
              C0000026h----在异常处理过程中系统使用的代码,如果系统从某个例程莫名奇妙的返回,则出现此代码,
                          如果RtlUnwind时没有Exception Record参数也同样会填入这个代码
              80000003h----调试时因代码中int3中断
              80000004h----处于被单步调试状态

              注:也可以自己定义异常代码,遵循如下规则:
              _____________________________________________________________________+

              位:      31~30            29~28          27~16          15~0
              _____________________________________________________________________+
              含义:    严重程度          29位            功能代码        异常代码
                        0==成功        0==Mcrosoft    MICROSOFT定义  用户定义
                        1==通知        1==客户
                        2==警告          28位
                        3==错误        被保留必须为0
ExceptionFlags 异常标志
              0----可修复异常
              1----不可修复异常
              2----正在展开,不要试图修复什么,需要的话,释放必要的资源
pExceptionRecord 如果程序本身导致异常,指向那个异常结构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号为C0000005h即内存异常时含义如下
              第一个dword 0==读冲突 1==写冲突
              第二个dword 读写冲突地址

        CONTEXT STRUCT                    ; _          ContextFlags  DWORD      ?      ;  |            +0          iDr0          DWORD      ?      ;  |            +4          iDr1          DWORD      ?      ;  |            +8          iDr2          DWORD      ?      ;  >调试寄存器  +C          iDr3          DWORD      ?      ;  |            +10          iDr6          DWORD      ?      ;  |            +14          iDr7          DWORD      ?      ; _|            +18          FloatSave    FLOATING_SAVE_AREA <>  ;浮点寄存器区 +1C~~~88h          regGs        DWORD      ?      ;--|            +8C          regFs        DWORD      ?      ;  |\段寄存器    +90           regEs        DWORD      ?      ;  |/            +94                     regDs        DWORD      ?      ;--|            +98          regEdi        DWORD      ?      ;____________    +9C          regEsi        DWORD      ?      ;      |  通用  +A0          regEbx        DWORD      ?      ;      |  寄    +A4          regEdx        DWORD      ?      ;      |  存    +A8          regEcx        DWORD      ?      ;      |  器    +AC          regEax        DWORD      ?      ;_______|___组_  +B0               regEbp        DWORD      ?      ;++++++++++++++++ +B4          regEip        DWORD      ?      ;    |控制        +B8          regCs        DWORD      ?      ;    |寄存        +BC          regFlag      DWORD      ?      ;    |器组        +C0          regEsp        DWORD      ?      ;    |            +C4          regSs        DWORD      ?      ;++++++++++++++++ +C8          ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)        CONTEXT ENDS 

 I、传递给final句柄的参数,只有两个可描述为EXCEPTION_POINTERS结构,定义如下:
            EXCEPTION_POINTERS STRUCT
              pExceptionRecord  DWORD      ?             
              ContextRecord    DWORD      ?
            EXCEPTION_POINTERS ENDS

            在call xHandler之前,堆栈结构如下:
            esp    -> *EXCEPTION_RECORD
            esp+4  -> *CONTEXT record  ;//具体结构见下面

                然后执行call _Final_Handler,这样在程序里要调用什么不轻而易举了吗?

II、 传递给per_thread句柄的参数,如下:
    在call xHandler之前,在堆栈中形成如下结构
            esp    -> *EXCEPTION_RECORD
            esp+4  -> *ERR                    ;//注意这也就是fs:[0]的指向
            esp    -> *CONTEXT record          ;//point to registers
            esp    -> *Param                    ;//呵呵,没有啥意义

          然后执行 call _Per_Thread_Handler

调用handler的原型是这样
        invoke HANDLER,*EXCEPTION_RECORD,*_EXCEPTION_REGISTRATION,*CONTEXT record,*Param
        即编译代码如下:
        PUSH *Param                    ;//通常不重要,没有什么意义
        push *CONTEXT record            ;//上面的结构
        push *ERR                      ;//the struc above
        push *EXCEPTION_RECORD          ;//see above
        CALL HANDLER
        ADD ESP,10h

原创粉丝点击