写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


华丽的分割线


概述

  当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接进行处理 ,而是修正3环EIP为KiUserExceptionDispatcher函数后就结束了。这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行,这个函数就是我们重点关注对象,我们先看一下它的流程:

  1. 调用RtlDispatchException,查找并执行异常处理函数。
  2. 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。
  3. 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发。

  看完上面的流程之后,我们看看其反汇编:

  1. ; void __stdcall __noreturn KiUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextFrame)
  2. public _KiUserExceptionDispatcher@8
  3. _KiUserExceptionDispatcher@8 proc near ; DATA XREF: .text:off_7C923428o
  4. var_C = dword ptr -0Ch
  5. var_8 = dword ptr -8
  6. var_4 = dword ptr -4
  7. ExceptionRecord = dword ptr 4
  8. ContextFrame = dword ptr 8
  9. mov ecx, [esp+ExceptionRecord]
  10. mov ebx, [esp]
  11. push ecx ; ContextRecord
  12. push ebx ; ExceptionRecord
  13. call _RtlDispatchException@8 ; RtlDispatchException(x,x)
  14. or al, al
  15. jz short loc_7C92E47A
  16. pop ebx
  17. pop ecx
  18. push 0
  19. push ecx
  20. call _ZwContinue@8 ; ZwContinue(x,x)
  21. jmp short loc_7C92E485
  22. ; ---------------------------------------------------------------------------
  23. loc_7C92E47A: ; CODE XREF: KiUserExceptionDispatcher(x,x)+10j
  24. pop ebx
  25. pop ecx
  26. push 0 ; FirstChance
  27. push ecx ; ContextRecord
  28. push ebx ; ExceptionRecord
  29. call _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
  30. loc_7C92E485: ; CODE XREF: KiUserExceptionDispatcher(x,x)+1Cj
  31. add esp, -14h
  32. mov [esp+EXCEPTION_RECORD.ExceptionCode], eax
  33. mov [esp+EXCEPTION_RECORD.ExceptionFlags], 1
  34. mov [esp+EXCEPTION_RECORD.ExceptionRecord], ebx
  35. mov [esp+EXCEPTION_RECORD.NumberParameters], 0
  36. push esp ; ExceptionRecord
  37. call _RtlRaiseException@4 ; RtlRaiseException(x)
  38. _KiUserExceptionDispatcher@8 endp ; sp-analysis failed

  可以看出该函数会调用RtlDispatchException,为了节省篇幅用伪代码如下:

  1. BOOLEAN __stdcall RtlDispatchException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
  2. {
  3. // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
  4. result = 0;
  5. if ( RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord) )
  6. return 1;
  7. RtlpGetStackLimits(&LowLimit, &HighLimit);
  8. ExceptionRecorda = 0;
  9. exRecord = RtlpGetRegistrationHead(); // ExceptionList
  10. if ( exRecord != -1 )
  11. {
  12. while ( 1 )
  13. {
  14. if ( exRecord < LowLimit
  15. || &exRecord[1] > HighLimit
  16. || (exRecord & 3) != 0
  17. || (handler = exRecord->Handler, handler >= LowLimit) && handler < HighLimit
  18. || !RtlIsValidHandler(exRecord->Handler) )
  19. {
  20. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  21. return result;
  22. }
  23. if ( byte_7C99B3FA < 0 )
  24. v11 = RtlpLogExceptionHandler(ExceptionRecord, ContextRecord, 0, exRecord, 0x10u);
  25. RtlpExecuteHandlerForException(ExceptionRecord, exRecord, ContextRecord, &a4, exRecord->Handler);
  26. v6 = v5;
  27. if ( byte_7C99B3FA < 0 )
  28. RtlpLogLastExceptionDisposition(v11, v5);
  29. if ( ExceptionRecorda == exRecord )
  30. {
  31. ExceptionRecord->ExceptionFlags &= 0xFFFFFFEF;
  32. ExceptionRecorda = 0;
  33. }
  34. if ( !v6 )
  35. break;
  36. if ( v6 == 1 )
  37. {
  38. if ( (ExceptionRecord->ExceptionFlags & 8) != 0 )
  39. return result;
  40. }
  41. else
  42. {
  43. if ( v6 != 2 )
  44. {
  45. e.ExceptionCode = EXCEPTION_INVALID_DISPOSITION;
  46. e.ExceptionFlags = 1;
  47. e.ExceptionRecord = ExceptionRecord;
  48. e.NumberParameters = 0;
  49. RtlRaiseException(&e);
  50. }
  51. v8 = a4;
  52. ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
  53. if ( v8 > ExceptionRecorda )
  54. ExceptionRecorda = v8;
  55. }
  56. exRecord = exRecord->Next;
  57. if ( exRecord == -1 )
  58. return result;
  59. }
  60. if ( (ExceptionRecord->ExceptionFlags & 1) != 0 )
  61. {
  62. e.ExceptionCode = EXCEPTION_NONCONTINUABLE_EXCEPTION;
  63. e.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  64. e.ExceptionRecord = ExceptionRecord;
  65. e.NumberParameters = 0;
  66. RtlRaiseException(&e);
  67. }
  68. result = 1;
  69. }
  70. return result;
  71. }

  RtlCallVectoredExceptionHandlers这个函数就是用来执行VEH的。如果返回假,则说明没有,后面的RtlpGetRegistrationHead就会获取SEH,如果有就执行,它是在堆栈中的。

  有了这些铺垫后,我们来介绍VEHSEH

VEH

  对于VEH,这个是XP及其之后才有的,中文为向量化异常结构处理。我们先看看它的处理流程:

  1. CPU捕获异常信息;
  2. 通过KiDispatchException进行分发;
  3. KiUserExceptionDispatcher调用RtlDispatchException
  4. RtlDispatchException查找VEH处理函数链表 并调用相关处理函数;
  5. 代码返回到KiUserExceptionDispatcher
  6. 调用ZwContinue再次进入0环(ZwContinue调用NtContinue,主要作用就是恢复_TRAP_FRAME然后通过KiServiceExit返回到3环);
  7. 线程再次返回3环后,从修正后的位置开始执行;

  如下是执行VEH的伪代码:

  1. BOOLEAN __stdcall RtlCallVectoredExceptionHandlers(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
  2. {
  3. PRTL_VECTORED_HANDLER_ENTRY p; // esi
  4. int (__stdcall *VectoredHandler)(EXCEPTION_POINTERS *); // eax
  5. EXCEPTION_POINTERS ExceptionInfo; // [esp+4h] [ebp-8h] BYREF
  6. BOOLEAN v6; // [esp+17h] [ebp+Bh]
  7. if ( IsListEmpty(&RtlpCalloutEntryList) )
  8. return 0;
  9. ExceptionInfo.ExceptionRecord = ExceptionRecord;
  10. ExceptionInfo.ContextRecord = ContextRecord;
  11. RtlEnterCriticalSection(&RtlpCalloutEntryLock);
  12. for ( p = RtlpCalloutEntryList.Flink; ; p = p->ListEntry.Flink )
  13. {
  14. if ( p == &RtlpCalloutEntryList )
  15. {
  16. v6 = 0;
  17. goto EndProc;
  18. }
  19. VectoredHandler = RtlDecodePointer(p->VectoredHandler);
  20. if ( VectoredHandler(&ExceptionInfo) == -1 )
  21. break;
  22. }
  23. v6 = 1;
  24. EndProc:
  25. RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
  26. return v6;
  27. }

  剩余的细节将会在总结与提升进行讲解,下面我们来看看如何使用VEH,如下是实验代码:

  1. #include "stdafx.h"
  2. #include <windows.h>
  3. #include <stdlib.h>
  4. typedef PVOID (NTAPI *VectoredExceptionHandler)(ULONG,_EXCEPTION_POINTERS*);
  5. LONG NTAPI MyVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
  6. {
  7. puts("进入异常处理函数……");
  8. if (pExceptionInfo->ExceptionRecord->ExceptionCode==0xC0000094)
  9. {
  10. puts("异常函数处理了……");
  11. pExceptionInfo->ContextRecord->Ecx = 1;
  12. return EXCEPTION_CONTINUE_EXECUTION;
  13. }
  14. return EXCEPTION_CONTINUE_SEARCH;
  15. }
  16. int main(int argc, char* argv[])
  17. {
  18. HMODULE lib = LoadLibrary("kernel32.dll");
  19. VectoredExceptionHandler AddVectoredExceptionHandler = (VectoredExceptionHandler)GetProcAddress(lib,"AddVectoredExceptionHandler");
  20. AddVectoredExceptionHandler(1,(_EXCEPTION_POINTERS*)&MyVectoredExceptionHandler);
  21. _asm
  22. {
  23. xor edx,edx;
  24. xor ecx,ecx;
  25. mov eax,0x10;
  26. idiv ecx;
  27. }
  28. puts("继续执行……");
  29. system("pause");
  30. return 0;
  31. }

  执行后会正常执行,并显示异常处理信息。

SEH

  SEH意为结构化异常处理,它的结构如下图所示:

  也就是说包装的异常处理项目是以单向链表的形式管理的。必须具有两个如上图所示的成员,也就是说,这个结构是可以扩展的,有关扩展的将会在后续介绍,下面我们来看实验代码:

  1. #include "stdafx.h"
  2. #include <windows.h>
  3. #include <stdlib.h>
  4. struct MyException
  5. {
  6. MyException* prev;
  7. DWORD handle;
  8. };
  9. EXCEPTION_DISPOSITION MyExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord,void* Establisherframe,CONTEXT* context,void* DispatcherContext)
  10. {
  11. puts("进入异常处理……");
  12. if (ExceptionRecord->ExceptionCode==0xC0000094)
  13. {
  14. puts("开始处理异常……");
  15. context->Eip+=2;
  16. return ExceptionContinueExecution;
  17. }
  18. return ExceptionContinueSearch;
  19. }
  20. int main(int argc, char* argv[])
  21. {
  22. DWORD tmp;
  23. //初始化异常结构
  24. MyException ex={(MyException*)tmp,(DWORD)MyExceptionHandler};
  25. //加入 SEH
  26. _asm
  27. {
  28. mov eax,fs:[0];
  29. mov tmp,eax;
  30. lea ecx,ex;
  31. mov fs:[0],ecx;
  32. }
  33. //制造异常
  34. _asm
  35. {
  36. xor edx,edx;
  37. xor ecx,ecx;
  38. mov eax,0x10;
  39. idiv ecx;
  40. }
  41. //撤掉 SEH
  42. _asm
  43. {
  44. mov eax,tmp;
  45. mov fs:[0],eax;
  46. }
  47. puts("正常运行……");
  48. system("pause");
  49. return 0;
  50. }

  该程序正常执行,并打印异常处理结果。

编译器扩展 SEH

初识

  前面我们用自己的方式实现了SEH的使用。异常处理很重要,但是,这个对于开发者很不友好。每次都要构造SEH,退出函数要撤掉。编译器提供了关键字,并对SEH进行了扩充,使用如下图所示:

  1. _try // 挂入 SEH 链表
  2. {
  3. }
  4. _except(/*过滤表达式*/) //异常过滤
  5. {
  6. //异常处理程序
  7. }

  对于过滤表达式的结果值,只能是-101,它们表示的含义如下:

  1. EXCEPTION_EXECUTE_HANDLER (1) 执行except里面的代码
  2. EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个异常处理函数
  3. EXCEPTION_CONTINUE_EXECUTION (-1) 返回出错位置重新执行

  我说只能是这三值,并没有说只能写这三个数字,你可以写入表达式或者函数,使其得到的结果或者返回值是这仨值其中之一就可以,如下是我们的实验程序:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. _try
  6. {
  7. _asm
  8. {
  9. xor edx,edx;
  10. xor ecx,ecx;
  11. mov eax,0x10;
  12. idiv ecx;
  13. }
  14. puts("继续跑……");
  15. }_except(1)
  16. {
  17. puts("异常处理……");
  18. }
  19. system("pause");
  20. return 0;
  21. }

  运行该程序,只打印了except里面的,得到正确结果。

初步深入

  我们接下来在汇编层面查看它是如何实现的,首先我们查看一下编译器为我们扩展的结构,否则看代码是看不懂的。

  1. struct _EXCEPTION_REGISTRATION
  2. {
  3. struct _EXCEPTION_REGISTRATION *prev;
  4. void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
  5. struct scopetable_entry *scopetable;
  6. int trylevel;
  7. int _ebp;
  8. };

  然后我们所谓的结构就成立这样子:

  图中的_except_handler3是啥我们看它的反汇编是什么就知道了:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. 00401010 push ebp
  6. 00401011 mov ebp,esp
  7. 00401013 push 0FFh
  8. 00401015 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed"+0Ch (00424030)
  9. 0040101A push offset __except_handler3 (00401400)
  10. 0040101F mov eax,fs:[00000000]
  11. 00401025 push eax
  12. 00401026 mov dword ptr fs:[0],esp
  13. 0040102D add esp,0B8h
  14. 00401030 push ebx
  15. 00401031 push esi
  16. 00401032 push edi
  17. 00401033 mov dword ptr [ebp-18h],esp
  18. 00401036 lea edi,[ebp-58h]
  19. 00401039 mov ecx,10h
  20. 0040103E mov eax,0CCCCCCCCh
  21. 00401043 rep stos dword ptr [edi]
  22. _try
  23. 00401045 mov dword ptr [ebp-4],0
  24. {
  25. _asm
  26. {
  27. xor edx,edx;
  28. 0040104C xor edx,edx
  29. xor ecx,ecx;
  30. 0040104E xor ecx,ecx
  31. mov eax,0x10;
  32. 00401050 mov eax,10h
  33. idiv ecx;
  34. 00401055 idiv eax,ecx
  35. }
  36. puts("继续跑……");
  37. 00401057 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed" (00424024)
  38. 0040105C call puts (004011e0)
  39. 00401061 add esp,4
  40. }_except(1)
  41. 00401064 mov dword ptr [ebp-4],0FFFFFFFFh
  42. 0040106B jmp $L865+17h (0040108a)
  43. $L864:
  44. 0040106D mov eax,1
  45. $L866:
  46. 00401072 ret
  47. $L865:
  48. 00401073 mov esp,dword ptr [ebp-18h]
  49. {
  50. puts("异常处理……");
  51. 00401076 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00425140)
  52. 0040107B call puts (004011e0)
  53. 00401080 add esp,4
  54. }
  55. 00401083 mov dword ptr [ebp-4],0FFFFFFFFh
  56. system("pause");
  57. 0040108A push offset string "pause" (0042401c)
  58. 0040108F call system (004010d0)
  59. 00401094 add esp,4
  60. return 0;
  61. 00401097 xor eax,eax
  62. }
  63. 00401099 mov ecx,dword ptr [ebp-10h]
  64. 0040109C mov dword ptr fs:[0],ecx
  65. 004010A3 pop edi
  66. 004010A4 pop esi
  67. 004010A5 pop ebx
  68. 004010A6 add esp,58h
  69. 004010A9 cmp ebp,esp
  70. 004010AB call __chkesp (004012d0)
  71. 004010B0 mov esp,ebp
  72. 004010B2 pop ebp
  73. 004010B3 ret

  看不懂吗?我们来画个堆栈图,如下所示:

  标注*的表示原来的值,是不是和结构体的成员对应起来了?注意不要以为只有黄色的区域,由于通常的函数采用ebp寻址,所以我没有把ebp*打上黄色底色。

  下面我们来看看scopetable成员,它的结构如下:

  1. struct scopetable_entry
  2. {
  3. DWORD previousTryLevel; //上一个try{}结构编号
  4. PDWRD lpfnFilter; //过滤函数的起始地址
  5. PDWRD lpfnHandler; //异常处理程序的地址
  6. }

  我们来看看这个结构的内容是啥,最终它的成员如下:

  1. scopetable.previousTryLevel = -1;
  2. scopetable.lpfnFilter = 0x40106D;
  3. scopetable.lpfnHandler = 0x401073;

  正好把代码指令和地址逐个对应起来了。

继续深入

  如果异常处理有嵌套调用的情况会是怎么样呢?如下是测试代码:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. _try
  6. {
  7. _try
  8. {
  9. _asm
  10. {
  11. xor edx,edx;
  12. xor ecx,ecx;
  13. mov eax,0x10;
  14. idiv ecx;
  15. }
  16. }_except(1)
  17. {
  18. puts("测试");
  19. }
  20. puts("继续跑……");
  21. }_except(1)
  22. {
  23. puts("异常处理……");
  24. }
  25. system("pause");
  26. return 0;
  27. }

  然后查看反汇编结果:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. 00401010 push ebp
  6. 00401011 mov ebp,esp
  7. 00401013 push 0FFh
  8. 00401015 push offset string "\xb2\xe2\xca\xd4"+0Ch (00424050)
  9. 0040101A push offset __except_handler3 (00401450)
  10. 0040101F mov eax,fs:[00000000]
  11. 00401025 push eax
  12. 00401026 mov dword ptr fs:[0],esp
  13. 0040102D add esp,0B8h
  14. 00401030 push ebx
  15. 00401031 push esi
  16. 00401032 push edi
  17. 00401033 mov dword ptr [ebp-18h],esp
  18. 00401036 lea edi,[ebp-58h]
  19. 00401039 mov ecx,10h
  20. 0040103E mov eax,0CCCCCCCCh
  21. 00401043 rep stos dword ptr [edi]
  22. _try
  23. 00401045 mov dword ptr [ebp-4],0
  24. {
  25. _try
  26. 0040104C mov dword ptr [ebp-4],1
  27. {
  28. _asm
  29. {
  30. xor edx,edx;
  31. 00401053 xor edx,edx
  32. xor ecx,ecx;
  33. 00401055 xor ecx,ecx
  34. mov eax,0x10;
  35. 00401057 mov eax,10h
  36. idiv ecx;
  37. 0040105C idiv eax,ecx
  38. }
  39. }_except(1)
  40. 0040105E mov dword ptr [ebp-4],0
  41. 00401065 jmp $L872+17h (0040f5d4)
  42. $L871:
  43. 00401067 mov eax,1
  44. $L873:
  45. 0040106C ret
  46. $L872:
  47. 0040106D mov esp,dword ptr [ebp-18h]
  48. {
  49. puts("测试");
  50. 00401070 push offset string "\xb2\xe2\xca\xd4" (00424044)
  51. 00401075 call puts (00401230)
  52. 0040107A add esp,4
  53. }
  54. 0040107D mov dword ptr [ebp-4],0
  55. puts("继续跑……");
  56. 00401084 push offset string "\xbc\xcc\xd0\xf8\xc5\xdc\xa1\xad\xa1\xad" (00424034)
  57. 00401089 call puts (00401230)
  58. 0040108E add esp,4
  59. }_except(1)
  60. 00401091 mov dword ptr [ebp-4],0FFFFFFFFh
  61. 00401098 jmp $L868+17h (004010b7)
  62. $L867:
  63. 0040109A mov eax,1
  64. $L869:
  65. 0040109F ret
  66. $L868:
  67. 004010A0 mov esp,dword ptr [ebp-18h]
  68. {
  69. puts("异常处理……");
  70. 004010A3 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
  71. 004010A8 call puts (00401230)
  72. 004010AD add esp,4
  73. }
  74. 004010B0 mov dword ptr [ebp-4],0FFFFFFFFh
  75. system("pause");
  76. 004010B7 push offset string "pause" (0042401c)
  77. 004010BC call system (00401120)
  78. 004010C1 add esp,4
  79. return 0;
  80. 004010C4 xor eax,eax
  81. }
  82. 004010C6 mov ecx,dword ptr [ebp-10h]
  83. 004010C9 mov dword ptr fs:[0],ecx
  84. 004010D0 pop edi
  85. 004010D1 pop esi
  86. 004010D2 pop ebx
  87. 004010D3 add esp,58h
  88. 004010D6 cmp ebp,esp
  89. 004010D8 call __chkesp (00401320)
  90. 004010DD mov esp,ebp
  91. 004010DF pop ebp
  92. 004010E0 ret

  看代码发现还是只是挂了一次,我们得看看scopetable的内容是啥了:

  1. 00425168 FFFFFFFF 0040109A 004010A0
  2. 00425174 00000000 00401067 0040106D
  3. 00425180 00000000 00000000 00000000
  4. 0042518C 00000000 00000000 00000000

  可以看到,这里有两个成员了。

finally 关键字

  当然不仅仅有try_except,还可以使用finally,该关键字的作用就是只要退出try就执行里面的函数,无论通过那种方式,如下是我们的实验代码:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. _try
  6. {
  7. return 0;
  8. }__finally
  9. {
  10. puts("异常处理……");
  11. system("pause");
  12. }
  13. return 0;
  14. }

  执行结果如下:

  1. 异常处理……
  2. 请按任意键继续. . .

  然后我们看看它在汇编层面是如何实现的,其反汇编如下:

  1. #include "stdafx.h"
  2. #include <stdlib.h>
  3. int main(int argc, char* argv[])
  4. {
  5. 00401010 push ebp
  6. 00401011 mov ebp,esp
  7. 00401013 push 0FFh
  8. 00401015 push offset string "stream != NULL"+10h (00425168)
  9. 0040101A push offset __except_handler3 (00401450)
  10. 0040101F mov eax,fs:[00000000]
  11. 00401025 push eax
  12. 00401026 mov dword ptr fs:[0],esp
  13. 0040102D add esp,0B4h
  14. 00401030 push ebx
  15. 00401031 push esi
  16. 00401032 push edi
  17. 00401033 lea edi,[ebp-5Ch]
  18. 00401036 mov ecx,11h
  19. 0040103B mov eax,0CCCCCCCCh
  20. 00401040 rep stos dword ptr [edi]
  21. _try
  22. 00401042 mov dword ptr [ebp-4],0
  23. 00401049 push 0FFh
  24. 0040104B mov dword ptr [ebp-1Ch],0
  25. {
  26. 00401052 lea eax,[ebp-10h]
  27. 00401055 push eax
  28. 00401056 call __local_unwind2 (0040139a)
  29. 0040105B add esp,8
  30. return 0;
  31. 0040105E mov eax,dword ptr [ebp-1Ch]
  32. 00401061 jmp $L865+2 (00401080)
  33. }__finally
  34. {
  35. puts("异常处理……");
  36. 00401063 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
  37. 00401068 call puts (00401230)
  38. 0040106D add esp,4
  39. system("pause");
  40. 00401070 push offset string "pause" (0042401c)
  41. 00401075 call system (00401120)
  42. 0040107A add esp,4
  43. $L863:
  44. 0040107D ret
  45. }
  46. 17: return 0;
  47. 0040107E xor eax,eax
  48. }
  49. 00401080 mov ecx,dword ptr [ebp-10h]
  50. 00401083 mov dword ptr fs:[0],ecx
  51. 0040108A pop edi
  52. 0040108B pop esi
  53. 0040108C pop ebx
  54. 0040108D add esp,5Ch
  55. 00401090 cmp ebp,esp
  56. 00401092 call __chkesp (00401320)
  57. 00401097 mov esp,ebp
  58. 00401099 pop ebp
  59. 0040109A ret

  可以看到在调用return 0;之前,被插入了调用__local_unwind2函数,正是这个函数能够调用finally里面的代码的:

  1. __local_unwind2:
  2. 0040139A push ebx
  3. 0040139B push esi
  4. 0040139C push edi
  5. 0040139D mov eax,dword ptr [esp+10h]
  6. 004013A1 push eax
  7. 004013A2 push 0FEh
  8. 004013A4 push offset __global_unwind2+20h (00401378)
  9. 004013A9 push dword ptr fs:[0]
  10. 004013B0 mov dword ptr fs:[0],esp
  11. 004013B7 mov eax,dword ptr [esp+20h]
  12. 004013BB mov ebx,dword ptr [eax+8]
  13. 004013BE mov esi,dword ptr [eax+0Ch]
  14. 004013C1 cmp esi,0FFh
  15. 004013C4 je __NLG_Return2+2 (004013f4)
  16. 004013C6 cmp esi,dword ptr [esp+24h]
  17. 004013CA je __NLG_Return2+2 (004013f4)
  18. 004013CC lea esi,[esi+esi*2]
  19. 004013CF mov ecx,dword ptr [ebx+esi*4]
  20. 004013D2 mov dword ptr [esp+8],ecx
  21. 004013D6 mov dword ptr [eax+0Ch],ecx
  22. 004013D9 cmp dword ptr [ebx+esi*4+4],0
  23. 004013DE jne __NLG_Return2 (004013f2)
  24. 004013E0 push 101h
  25. 004013E5 mov eax,dword ptr [ebx+esi*4+8]
  26. 004013E9 call __NLG_Notify (0040142e)
  27. 004013EE call dword ptr [ebx+esi*4+8]
  28. __NLG_Return2:
  29. 004013F2 jmp __local_unwind2+1Dh (004013b7)
  30. 004013F4 pop dword ptr fs:[0]
  31. 004013FB add esp,0Ch
  32. 004013FE pop edi
  33. 004013FF pop esi
  34. 00401400 pop ebx
  35. 00401401 ret

  关键调用在call dword ptr [ebx+esi*4+8],执行这个就会调用finally里的代码。具体详细的其他细节将会在总结与提升进行介绍。

下一篇

  异常篇——总结与提升

异常篇—— VEH 与 SEH的更多相关文章

  1. 羽夏看Win系统内核——异常篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 关于C++异常机制的笔记(SEH, try-catch)

    昨天晚上加班解决了一个问题,是由于无法正确的捕获到异常导致的.刚开始用try-catch,但是没法捕获到异常:后面改成SEH异常才解决.因此今天将这个问题重新梳理了一遍,关于try-catch, SE ...

  3. linux上安装fastdfs+nginx+ngin-module实践并解决多个异常篇

    为什么选择Nginx Nginx 是一个很牛的高性能Web和反向代理服务器, 它具有有很多非常优越的特性: 在高连接并发的情况下,Nginx是Apache服务器不错的替代品:Nginx在美国是做虚拟主 ...

  4. Python之异常篇 [待更新]

    简介 当你的程序中出现某些 异常的 状况的时候,异常就发生了.例如,当你想要读某个文件的时候,而那个文件不存在.或者在程序运行的时候,你不小心把它删除了.上述这些情况可以使用异常来处理. 假如你的程序 ...

  5. Java面试题(异常篇)

    异常 74.throw 和 throws 的区别? throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理.而throw则是指 ...

  6. hive 学习之异常篇

    一.刚装上hive在执行hive启动的过程中出现 [hadoop@localhost hive-0.6.0]$ hive Invalid maximum heap size: -Xmx4096m Th ...

  7. hadoop 学习之异常篇

    本文旨在给予自己在学习hadoop过程中遇到的问题的一个记录和解决方法. 一. copyFromLocal: java.io.IOException: File /user/hadoop/slaves ...

  8. [异常篇]001.MySQL数据库忘记root密码解决办法[转载]

    MySQL数据库忘记root密码解决办法 1.在运行输入services.msc打开服务窗体,找到MYSQL服务.右键停止将其关闭.如图: 2.在运行输入cmd打开终端. 3.找到MYSQL的安装目录 ...

  9. java代码异常篇

    总结:掌握流.缓冲区类的方法 package com.b; import java.io.BufferedReader; import java.io.File; import java.io.Fil ...

随机推荐

  1. 手把手教你分析解决MySQL死锁问题

    在生产环境中如果出现MySQL死锁问题该如何排查和解决呢,本文将模拟真实死锁场景进行排查,最后总结下实际开发中如何尽量避免死锁发生. 一.准备好相关数据和环境 当前自己的数据版本是8.0.22 mys ...

  2. 在Rainbond上使用Locust进行压力测试

    Locust简介 Locust 是一种易于使用.可编写脚本且可扩展的性能测试工具.并且有一个用户友好的 Web 界面,可以实时显示测试进度.甚至可以在测试运行时更改负载.它也可以在没有 UI 的情况下 ...

  3. 2022GDUT寒假专题学习-1 B,F,I,J题

    专题链接:专题学习1 - Virtual Judge (vjudge.net) B - 全排列 题目 思想 这道题可以用DFS进行求解,但是一看到全排列,其实可以立刻想到一个STL函数:next_pe ...

  4. 【webpack4.0】---base.config.js基本配置(五)

    一.创建项目初始化 1.初始化项目npm init -y 2.创建 src (用来存放开发环境的代码)文件夹.  config (用来存放webpack的配置项)文件夹 3.安装webpack  We ...

  5. K8S集群架构

  6. python 元组tuple介绍,使用。

    原文 https://blog.csdn.net/ruanxingzi123/article/details/83184909 一  是什么? # python 元组tuple? ''' 元祖tupl ...

  7. String Reversal

    Educational Codeforces Round 96 (Rated for Div. 2) - E. String Reversal 跳转链接 题目描述 定义一个操作为交换字符串中相邻的两个 ...

  8. 布客&#183;ApacheCN 编程/大数据/数据科学/人工智能学习资源 2020.1

    公告 我们正在招募项目负责人,完成三次贡献可以申请,请联系片刻(529815144).几十个项目等你来申请和参与,不装逼的朋友,我们都不想认识. 薅资本主义羊毛的 CDNDrive 计划正式启动! 我 ...

  9. 如何为Windows服务增加Log4net和EventLog的日志功能。

    一.简介 最近在做一个项目的时候,需要该项目自动启动.自动运行,不需要认为干预.不用说,大家都知道用什么技术,那就是 Windows服务.在以前的Net Framework 平台下,Windows 服 ...

  10. MySQL 新增表分区很慢,转移大表数据

    问题: MySQL (version 5.7.26) 数据库有一批表 xxx_yyy,由于评估的数据量可能比较大,因此每张表都设置了表分区,把每个月的数据保存在单独的分区里. 那么如果每年年末,没有提 ...