复习

1. 如果所有的 SEH 都不能处理异常,那么最后会由谁处理异常

  • 如果 SEH 不能处理异常,异常就会传递给 UEH,UEH 实际上是系统默认的 SEH 调用的。

2. VCH 处理程序会在什么情况下被调用

  • 只有之前的 SEH VEH 或者 UEH 中的任何一个处理异常,这个函数才会被调用

3. SEH 是全局有效的么,它们被保存在什么地方

  • SEH 是线程相关的,保存在了 FS:[0] 内,对应的就是 TEB.NT_TIB.ExceptionList 字段。
  • UEH 是进程相关,如果任何一个异常处理程序无法处理,就被调用,实际被保存在一个全局的函数指针中。
  • VEH 和 VCH 是进程相关的,被保存在全局的链表中,两个函数只是保存的标志位不同。

4. 什么是异常,什么是中断

  • 中断通常是由外部设置产生的,例如鼠标键盘等,是一个异步的事件,可以不进行处理。
  • 异常通常是在CPU满足特定的条件时,内部产生的,是一个同步的事件,必须立即进行处理。

5. 异常的种类有哪些,有什么特点?

  • 错误类:通常可修复,异常产生时,eip 指向的是产生异常的指令。
  • 陷阱类:通常可修复,异常产生时,eip 指向的是下一条指令。
  • 终止类:无法修复的,寄存器的指向时没有意义的。

6. windbg 中分别使用哪些系列的指令查看数据、修改数据、设置断点。

  • 查看数据的指令:db dq da du dw dd dt(查看结构体)
  • 修改数据的指令:eb eq ea eu ew
  • 断点相关的指令:bp bu bl be bd bc
  • 为指定模块加载符号:.reload /f /i demo.exe -> ml
  • 流程相关的一些指令:t(F11) p(F10) g(F5)

7. 陷阱处理器被保存在哪里,windbg中使用什么指令可以查看它。

  • 在 windows 中中断和异常是统一管理的,所有的处理函数都被保存在 CPU相关的 IDT 中,使用 !IDT 可以进行查看。

8. 异常的产生方式有几种,分别是什么?

  • 指令满足特定的条件,CPU 自动触发
  • 用户使用 RaiseException 函数主动的抛出异常

异常分发过程使用的函数以及具体的功能(int 3)

异常分发流程-1.jpg
用户层的异常分发.png

一、KiTrap03

  • 谁调用的:CPU 产生了异常,从 IDT[3] 找到这个函数并且调用了
  • 调用了谁:ENTER_TRAP(宏) CommonDispatchException(函数)
  • 功能:
    1. 使用 ENTER_TRAP 宏填充了一个陷阱帧,目的是为了处理异常后继续执行
    2. 调用了 CommonDispatchException 并传递了相关的参数(异常类型\异常地址\异常参数)

二、 CommonDispatchException

  • 谁调用的:KiTrap03 调用的
  • 调用了谁:KiDispatchException
  • 功能:
    1. 构建了一个 EXCEPTION_RECORD 结构体,并使用接受的参数填充
    2. 调用了 KiDispatchException 并传递了相关的参数(先前模式\分发次数\陷阱帧\异常记录)

三、 KiDispatchException

  • 谁调用的:CommonDispatchException 调用的
  • 调用了谁:RtlDispatchException(R0) \ 间接调用 RtlDispatchException(R3) \ KiUserExceptionDispatcher
  • 功能:
    1. 当前接收到的异常产生于内核态
      1. 尝试将异常信息发送给内核调试器(KD\WINDBG)
      2. 通过 RtlDispatchException(R0) 函数调用 SEH
      3. 再次 尝试将异常信息发送给内核调试器(KD\WINDBG)
      4. 调用 KeDebugCheck 函数使系统蓝屏并显示错误码
    2. 当前接收到的异常产生于用户态
      1. 如果没有被R3调试,就尝试将异常发送给内核调试器
      2. 通过 DbgkForwardException 将异常传递给 R3 调试器(OD\x64)
      3. 在用户栈中填充一个 EXCEPTION_POINTER 结构
        • 设置 eip 指向 ntdll 中的函数 KiUserExceptionDispatcher
      4. 执行到用户代码,在 KiUserExceptionDispatcher 调用 RtlDispatchException[R3]
        • 在这个函数的内部会依次调用 VEH SEH UEH (VCH)
      5. 依然无法处理就执行第二次分发,将异常发送到 调试端口(调试器)\异常端口(子系统)
      6. 仍然无法处理,就结束进程

        _Kitrap03.asm

  1. ; 处理由单字节指令 INT 3 引起的软件断点异常,并且提供相应的附加参数
  2. ; 当异常产生的时候,寄存器 cs:eip 会立即指向 INT 3 的下一条指令。
  3. _KiTrap03 proc
  4. ; 填充一个 _KTRAP_FRAME 结构体,提供相关的变量,会被用于恢复执行
  5. ; 其中 INT 3 不提供错误代码,需要手动进行填充
  6. push 0
  7. ENTER_TRAP kit3_a, kit3_t
  8. kit03_01:
  9. ; 填充软件断点对应的异常信息
  10. mov eax, BREAKPOINT_BREAK(0)
  11. kit03_05:
  12. ; 开启中断标志位
  13. sti
  14. kit03_10: ; 当前函数的终点(重点)
  15. ; 设置断点类异常的附加参数
  16. mov esi, ecx ; 2: 保存产生异常的线程对应的 ETHREAD
  17. mov edi, edx ; 3: 没保存有意义的值,可能是 V86Ds
  18. mov edx, eax ; 1: 保存的是软件断点相关的异常信息
  19. ; 陷阱类异常发生时,eip 指向下一条,实际产生异常的位置在 eip - 1
  20. mov ebx, [ebp].Eip ; ebx = 异常地址(int 3)
  21. dec ebx
  22. mov ecx, 3 ; ecx = 参数个数
  23. mov eax, STATUS_BREAKPOINT ; eax = 异常类型
  24. ; 调用函数执行异常的分发,函数永远不会返回
  25. call CommonDispatchException ; Never return
  26. _KiTrap03 endp
  27. ;----------------------------------------------------------------------------
  28. ; 宏:用于构建陷阱帧并设置异常需要用到的寄存器
  29. ; - 保存: (非)易失性寄存器、段寄存器[DS\ES\GS\FS]、异常链表、先前模式等
  30. ; - 设置: 段寄存器[DS\ES\FS] 以及 DF 方向标志位等,esp=ebp=KTRAP_FRAME
  31. ENTER_TRAP macro kit3_a, kit3_t
  32. ; 当产生异常的时候,CPU会自动保存下列的寄存器
  33. ; +0x068 Eip : Uint4B
  34. ; +0x06c SegCs : Uint4B
  35. ; +0x070 EFlags : Uint4B
  36. mov word ptr [esp+2], 0 ; 清除 ErrorCode 的低两位
  37. push ebp ; 保存非易失性寄存器[基址\变址]
  38. push ebx
  39. push esi
  40. push edi
  41. push fs ; 保存 FS 并将 FS 指向 KPCR[R0]
  42. mov ebx, KGDT_R0_PCR ; 使 fs 指向 KPCR 结构体
  43. mov fs, bx
  44. ; 保存 SEH 异常链表
  45. mov ebx, fs:[PcExceptionList]
  46. push ebx
  47. sub esp, 4 ; 开辟空间,用于填充 [先前模式] 字段
  48. push eax ; 保存易失性寄存器
  49. push ecx
  50. push edx
  51. push ds ; 保存段寄存器
  52. push es
  53. push gs
  54. ; 直接开辟堆栈,跳过其它字段的填充并设置 ds es 段寄存器的值
  55. mov ax, KGDT_R3_DATA OR RPL_MASK
  56. sub esp, SegGs(偏移 0x30)
  57. mov ds, ax
  58. mov es, ax
  59. ; 此时 esp ebp 都指向 KTRAP_FRAME 结构体
  60. mov ebp, esp
  61. ; 获取对应当前的 ETHREAD 结构体的地址,并清除方向标志
  62. mov ecx, PCR[PcPrcbData+PbCurrentThread]
  63. cld
  64. ; 关闭所有的硬件断点,当 ThDebugActive 的值为 ff 时保存所有的调试寄存器(被调试)
  65. ; win8 以上取消了 ETHREAD.DISPATCHER_HEADER.DebugActive 这个字段的使用
  66. and dword ptr [ebp].Dr7, 0
  67. test byte ptr [ecx].DebugActive, 0ffh;
  68. jnz Dr_kit3_a
  69. Dr_kit3_t:
  70. ; 设置调试相关信息
  71. SET_DEBUG_DATA
  72. ENTER_TRAP endm

CommonDispatchException.asm

  1. ; 此函数在栈上分配并填充 ExceptionRecord 结构,最后调用异常分发函数
  2. ; 只有在 ExceptionCode = 0、参数个数小于 3 ExceptionRecord 为空才调用
  3. CommonDispatchException proc
  4. ; esi = 附加参数2 edi = 附加参数3
  5. ; edx = 附加参数1 ebx = 异常地址
  6. ; ecx = 参数个数 eax = 异常类型
  7. ; esp = EXCEPTION_RECORD ebp = _KTRAP_FRAME
  8. ; 在栈中开辟记录异常所需的栈空间 EXCEPTION_RECORD
  9. sub esp, ExceptionRecordLength
  10. ; 使用 _KiTrap03 传入的参数构建异常结构
  11. mov dword ptr [esp].ExceptionCode, eax ; *异常类型
  12. xor eax, eax
  13. mov dword ptr [esp].ExceptionFlags, eax ; 异常标志,为 0 则可以处理
  14. mov dword ptr [esp].ExceptionRecord, eax ; 指向下一个异常结构
  15. mov dword ptr [esp].ExceptionAddress, ebx ; *异常产生时的地址
  16. mov dword ptr [esp].NumberParameters, ecx ; 异常的附加参数个数
  17. ; 如果附加参数个数为 0,则跳转到目标位置
  18. cmp ecx, 0
  19. je short de00
  20. ; 如果存在附加参数,则填充异常的附加参数
  21. lea ebx, [esp + ErExceptionInformation]
  22. mov [ebx + 0], edx
  23. mov [ebx + 4], esi
  24. mov [ebx + 8], edi
  25. de00:
  26. mov ecx, esp ; (ecx)->exception record
  27. ; 如果当前不是 V86 模式下的异常,则获取 CS 寄存器(重点)
  28. de10: mov eax, [ebp].SegCs
  29. ; 获取 CS 寄存器的最低位用作先前模式,用于判断处于 R0 还是 R3
  30. de20: and eax, MODE_MASK(3)
  31. ; 将设置好的值作为参数调用 KiDispatchException 函数进行异常的分发
  32. push True ; first chance: 表示是否是第一次处理异常
  33. push eax ; PreviousMode: 先前模式,当前异常由 R0 产生还是 R3 产生
  34. push ebp ; TrapFrame: 陷阱帧,保存的是异常产生的寄存器环境
  35. push 0 ; ExceptionFrame: 空的异常栈帧(不重要)
  36. push ecx ; EXCEPTION_RECORD: 填充的 EXCEPTION_RECORD
  37. call KiDispatchException
  38. ; 结束异常处理,根据 _KTRAP_FRAME 通过 iret 指令返回用户层代码
  39. mov esp, ebp ; (esp) -> trap frame
  40. ; 用于返回 R3 并且根据陷阱帧设置 R3 的寄存器
  41. jmp Kei386EoiHelper(_KiExceptionExit)
  42. CommonDispatchException endp

KiDispatchException.c

  1. // 该函数是异常的分发函数,会根据异常产生的位置对异常进行相应的处理。
  2. VOID KiDispatchException (
  3. IN PEXCEPTION_RECORD ExceptionRecord, // 指向异常信息结构体
  4. IN PKEXCEPTION_FRAME ExceptionFrame, // 指向异常帧,0
  5. IN PKTRAP_FRAME TrapFrame, // 指向陷阱帧,寄存器环境
  6. IN KPROCESSOR_MODE PreviousMode, // 异常产生的位置 R0/R3
  7. IN BOOLEAN FirstChance // 是否是第一次分发异常
  8. )
  9. {
  10. // 递增异常分发计数器
  11. KeGetCurrentPrcb()->KeExceptionDispatchCount++;
  12. // 创建保存线程环境的结构体,第一个参数标识要操作的寄存器类型
  13. // 根据设置的标志,将指定类型的陷阱帧和异常帧信息传递到上下文中(关键)
  14. CONTEXT Context = { CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS };
  15. KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
  16. // 根据异常产生的位置选择不同的处理方式
  17. if (PreviousMode == KernelMode(0))
  18. {
  19. // 如果异常产生于内核模式下,先判断内核调试器是否存在,存在则交给内核调试器进行第一次处理,
  20. // 如果处理失败,则将异常传递给内核中的结构化异常处理程序进行处理,若仍然处理失败,交由内
  21. // 核调试器进行第二次的处理。如果还是无法处理,则调用 KeBugCheckEx 使系统崩溃。
  22. if (FirstChance == TRUE)
  23. {
  24. // KiDebugRoutine 是一个保存在内核中的全局指针,通常情况下被指向 KdpStub 函数,这
  25. // 个函数基本不执行任何操作。当存在内核调试器时(windbg\kd),该指针指向函数 KdpTrap,
  26. // 作用是将调用方传入的异常信息发送给内核调试器进行处理并获得处理结果
  27. if ((KiDebugRoutine != NULL) &&
  28. (((KiDebugRoutine)(TrapFrame,
  29. ExceptionFrame,
  30. ExceptionRecord,
  31. &Context,
  32. PreviousMode,
  33. FALSE)) != FALSE)) {
  34. // 如果异常处理成功,则跳转到函数末尾恢复执行
  35. goto Handled1;
  36. }
  37. // 如果不存在内核调试器或内核调试器没有处理成功,则传递给 SEH 处理
  38. if (RtlDispatchException(ExceptionRecord, &Context) == TRUE) {
  39. goto Handled1;
  40. }
  41. }
  42. // 如果调试器和SEH异常处理都没有处理得了异常, 则给内核调试器第二次的处理机会
  43. if ((KiDebugRoutine != NULL) &&
  44. (((KiDebugRoutine) (TrapFrame,
  45. ExceptionFrame,
  46. ExceptionRecord,
  47. &Context,
  48. PreviousMode,
  49. TRUE)) != FALSE)) {
  50. goto Handled1;
  51. }
  52. // 如果,异常仍然没有被处理,则调用 KeBugCheckEx 引发系统的蓝屏崩溃
  53. KeBugCheckEx(
  54. KERNEL_MODE_EXCEPTION_NOT_HANDLED,
  55. ExceptionRecord->ExceptionCode,
  56. (ULONG)ExceptionRecord->ExceptionAddress,
  57. (ULONG)TrapFrame,
  58. 0);
  59. }
  60. // 如果异常在用户模式下触发
  61. else
  62. {
  63. // 如果异常是第一次分发并且进程[具有调试端口](处于被调试状态), 则发送一个消息
  64. // 到调试端口,并等待回复. 如果调试器处理了这个异常, 则结束异常的分发. 否则,
  65. // 将异常信息拷贝到用户态的栈中, 并转到用户模式 , 在用户模式下尝试将异常派发
  66. // 给异常处理程序. 如果异常处理程序处理了异常, 则结束异常分发. 如果用户层的
  67. // 异常处理程序处理不了, 则调用 NtRaiseException 函数主动触发异常, 并将
  68. // FirstChance 设置为 TRUE. 这个函数(KiDispatchException) 将会被第二次调用以
  69. // 继续处理异常. 如果这次处理是第二次异常处理,并且进程有一个调试端口, 则发送
  70. // 一个消息到调试端口,并等待调试器回复.如果调试器回复已经处理了异常, 则结束异
  71. // 常分发. 否则发送给进程的异常端口, 并等其回复. 若异常被处理, 则异常分发结束
  72. // 否则直接结束掉当前进程.
  73. if (FirstChance == TRUE) {
  74. // 除非存在用户模式调试器或处理器忽略用户模式下的异常,(否则在确认当前进程
  75. // 不是一个调试服务后),将异常发送给内核调试器进行处理
  76. // 当进程处于被调试状态时 DebugPort 保存的是一个调试对象
  77. // (指针非空 && 当前没有被调试 && 没有忽略异常)
  78. if ((KiDebugRoutine != NULL) &&
  79. ((PsGetCurrentProcess()->DebugPort == NULL &&
  80. !KdIgnoreUmExceptions) ||
  81. (KdIsThisAKdTrap(ExceptionRecord, &Context, UserMode)))) {
  82. if ((((KiDebugRoutine) (TrapFrame,
  83. ExceptionFrame,
  84. ExceptionRecord,
  85. &Context,
  86. PreviousMode,
  87. FALSE)) != FALSE)) {
  88. // 如果没有被三环调试器调试调试,就发送异常信息给内核调试器
  89. goto Handled1;
  90. }
  91. }
  92. // 通过函数 DbgkForwardException 将异常发送给用户层调试器并等待用户调试器的处理结果,
  93. // 该函数用于组合消息,并在内部调用 DbgkpSendApiMessage() 进行消息的传递
  94. if (DbgkForwardException(ExceptionRecord, TRUE, FALSE)) {
  95. goto Handled2;
  96. }
  97. repeat:
  98. try {
  99. // 在栈上开辟一块空间用于存储 CONTEXT: sub esp, sizeof(CONTEXT)
  100. // 检查用户栈上对应的空间是否是可写的,不可写则产生异常
  101. // 如果可以写如,则将填充的异常线程上下文拷贝到对应的区域
  102. // 在栈中开辟一个 CONTEXT 结构并且填充
  103. // esp -> UserStack1 -> CONTEXT
  104. // --------------
  105. UserStack1 = (Context.Esp& ~CONTEXT_ROUND) - CONTEXT_ALIGNED_SIZE;
  106. ProbeForWrite((PCHAR)UserStack1, CONTEXT_ALIGNED_SIZE, CONTEXT_ALIGN);
  107. RtlCopyMemory((PULONG)UserStack1, &Context, sizeof(CONTEXT));
  108. // 继续在栈中已分配的空间后开辟空间用于存储 EXCEPTION_RECORD 结构体
  109. // 探测目标区域是否可写,并将数据填充到对应的栈空间位置
  110. // 在栈中再次开辟一个 EXCEPTION_RECORD 结构体并填充
  111. // UserStack2 -> EXCEPTION_RECORD
  112. // --------------
  113. // UserStack1 -> CONTEXT
  114. // --------------
  115. Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS -
  116. ExceptionRecord->NumberParameters) * sizeof(ULONG) + 3) & (~3);
  117. UserStack2 = UserStack1 - Length;
  118. // 探测的位置为结构体大小加8,因为还需要通过栈传递两个参数
  119. // UserStack2 - 8
  120. // UserStack2 - 4
  121. // UserStack2 -> EXCEPTION_RECORD
  122. // --------------
  123. // UserStack1 -> CONTEXT
  124. // --------------
  125. ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG));
  126. RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length);
  127. // 将两个结构体的地址作为函数(KiUserExceptionDispatcher)的参数传递到栈中
  128. // UserStack2 - 8 -> UserStack2(EXCEPTION_RECORD)
  129. // UserStack2 - 4 -> UserStack1(CONTEXT)
  130. // UserStack2 -> EXCEPTION_RECORD
  131. // --------------
  132. // UserStack1 -> CONTEXT
  133. // --------------
  134. *(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1;
  135. *(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2;
  136. // 设置新的 SS 寄存器和 ESP 寄存器到 KTRAP_FRAME 结构中,设置 R3 的环境
  137. KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA);
  138. KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2));
  139. // 为用户模式下的段寄存器填充指定的段选择子
  140. TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode);
  141. TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
  142. TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode);
  143. TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode);
  144. TrapFrame->SegGs = 0;
  145. // 将发生异常的线程 eip 地址设置为 KeUserExceptionDispatcher 函数的地址。
  146. // 然后程序会进行 return 操作,并根据新设置的线程环境切换到用户层继续执行。
  147. TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher;
  148. // KeUserExceptionDispatcher: 内核内的一个全局指针,总是会指向一个 ntdll
  149. // 中保存的函数 KiUserExceptionDispatcher, 这个函数主要被用于进行用于层
  150. // 异常处理函数的调用。
  151. return;
  152. } except (KiCopyInformation(&ExceptionRecord1,
  153. (GetExceptionInformation())->ExceptionRecord)) {
  154. // 如果这些代码产生了栈溢出错误,就构建一个栈溢出异常进行分发
  155. if (ExceptionRecord1.ExceptionCode == STATUS_STACK_OVERFLOW) {
  156. ExceptionRecord1.ExceptionAddress = ExceptionRecord->ExceptionAddress;
  157. RtlCopyMemory((PVOID)ExceptionRecord,
  158. &ExceptionRecord1, sizeof(EXCEPTION_RECORD));
  159. // 如果产生了栈异常,则再次尝试进行第二次操作
  160. goto repeat;
  161. }
  162. }
  163. }
  164. }
  165. // 如果是第二次进行异常分发,则分别将异常信息再次发送给异常程序的调试端口和异常端口。
  166. // DbgkForwardException 的第二个参数表示端口类型,第三个参数表示是否是第二次分发
  167. if (DbgkForwardException(ExceptionRecord, TRUE, TRUE)) {
  168. goto Handled2;
  169. } else if (DbgkForwardException(ExceptionRecord, FALSE, TRUE)) {
  170. goto Handled2;
  171. } else {
  172. // 如果两次异常处理都没有成功,则结束产生异常的进程
  173. ZwTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
  174. KeBugCheckEx(
  175. KERNEL_MODE_EXCEPTION_NOT_HANDLED,
  176. ExceptionRecord->ExceptionCode,
  177. (ULONG)ExceptionRecord->ExceptionAddress,
  178. (ULONG)TrapFrame,
  179. 0);
  180. }
  181. }
  182. Handled1:
  183. // 将线程上下文的改动填充到陷阱和异常帧,然后返回以恢复状态继续执行。
  184. KeContextToKframes(TrapFrame, ExceptionFrame, &Context,
  185. Context.ContextFlags, PreviousMode);
  186. // 如果用户调试器已经处理了异常,则不需要进行 eip 的设置
  187. Handled2:
  188. return;
  189. }

KiUserExceptionDispatcher.c

  1. VOID NTAPI KiUserExceptionDispatcher(
  2. PEXCEPTION_RECORD ExceptionRecord, //
  3. PCONTEXT Context)
  4. {
  5. EXCEPTION_RECORD NestedExceptionRecord;
  6. NTSTATUS Status;
  7. // 分发异常给 VEH SEH UEH 和 VCH 函数,并检查是否处理成功
  8. if (RtlDispatchException(ExceptionRecord, Context))
  9. {
  10. // 如果异常被处理了,通过 NtContinue 返回 R0 并继续执行
  11. Status = NtContinue(Context, FALSE);
  12. }
  13. else
  14. {
  15. // 通过将 NtRaiseException 的参数三设置为 FALSE 第二次抛出异常
  16. Status = NtRaiseException(ExceptionRecord, Context, FALSE);
  17. }
  18. // 如果上面的操作都执行失败,则生成一个新的异常并抛出
  19. NestedExceptionRecord.ExceptionCode = Status;
  20. NestedExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  21. NestedExceptionRecord.ExceptionRecord = ExceptionRecord;
  22. NestedExceptionRecord.NumberParameters = Status;
  23. RtlRaiseException(&NestedExceptionRecord);
  24. }

KiUserExceptionDispatcher[3].c

  1. VOID NTAPI KiUserExceptionDispatcher(
  2. PEXCEPTION_RECORD ExceptionRecord, //
  3. PCONTEXT Context)
  4. {
  5. EXCEPTION_RECORD NestedExceptionRecord;
  6. NTSTATUS Status;
  7. // 分发异常给 VEH SEH UEH 和 VCH 函数,并检查是否处理成功
  8. if (RtlDispatchException(ExceptionRecord, Context))
  9. {
  10. // 如果异常被处理了,通过 NtContinue 返回 R0 并继续执行
  11. Status = NtContinue(Context, FALSE);
  12. }
  13. else
  14. {
  15. // 通过将 NtRaiseException 的参数三设置为 FALSE 第二次抛出异常
  16. // 内部会直接调用 KiDispatchException 执行第二次分发
  17. Status = NtRaiseException(ExceptionRecord, Context, FALSE);
  18. }
  19. // 如果上面的操作都执行失败,则生成一个新的异常并抛出
  20. NestedExceptionRecord.ExceptionCode = Status;
  21. NestedExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  22. NestedExceptionRecord.ExceptionRecord = ExceptionRecord;
  23. NestedExceptionRecord.NumberParameters = Status;
  24. RtlRaiseException(&NestedExceptionRecord);
  25. }

RtlDispatchException[0].c

  1. // 此函数通过搜索结构化异常处理链表,尝试将异常分派给相应的处理程序。搜索从上下文记录中指定的位置开始
  2. // 并向后继续,直到找到处理异常的处理程序、发现堆栈无效(即超出限制或未对齐)或到达调用层次结构的末尾。
  3. BOOLEAN RtlDispatchException (
  4. IN PEXCEPTION_RECORD ExceptionRecord,
  5. IN PCONTEXT ContextRecord
  6. )
  7. {
  8. ULONG Index;
  9. BOOLEAN Completion = FALSE;
  10. EXCEPTION_RECORD ExceptionRecord1;
  11. DISPATCHER_CONTEXT DispatcherContext;
  12. PEXCEPTION_REGISTRATION_RECORD RegistrationPointer;
  13. PEXCEPTION_REGISTRATION_RECORD NestedRegistration = 0;
  14. // 从线程的 TIB(线程信息块) 中获取栈的基址和栈的结束位置
  15. // 从 TIB 中取出保存 SEH 函数的 ExceptionList 头节点
  16. ULONG LowLimit, HighLimit;
  17. RtlpGetStackLimits(&LowLimit, &HighLimit);
  18. // mov RegistrationPointer, fs:[0]
  19. RegistrationPointer = RtlpGetRegistrationHead();
  20. // 遍历 SEH,当值为 EXCEPTION_CHAIN_END(-1) 表示链表遍历结束
  21. while (RegistrationPointer != EXCEPTION_CHAIN_END) {
  22. // 计算出整个结构体所占用到的最大地址(结束位置)
  23. ULONG HighAddress = (ULONG)RegistrationPointer +
  24. sizeof(EXCEPTION_REGISTRATION_RECORD);
  25. // 如果起始地址小于栈底,结束位置大于栈顶,或者结构体地址没有对齐就尝试对地址进行修复
  26. if ( ((ULONG)RegistrationPointer < LowLimit) ||
  27. (HighAddress > HighLimit) ||
  28. (((ULONG)RegistrationPointer & 0x3) != 0))
  29. {
  30. // 退出当前函数,即异常处理失败
  31. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  32. break;
  33. }
  34. // 查看当前异常处理程序是否合理(能否访问到)
  35. if (!RtlIsValidHandler(RegistrationPointer->Handler)) {
  36. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  37. break;
  38. }
  39. // 调用异常处理函数,能够调用 except_handler4 -> 调用了 __except()/过滤函数
  40. EXCEPTION_DISPOSITION Disposition = RtlpExecuteHandlerForException(
  41. ExceptionRecord,
  42. (PVOID)RegistrationPointer,
  43. ContextRecord,
  44. (PVOID)&DispatcherContext,
  45. (PEXCEPTION_ROUTINE)RegistrationPointer->Handler);
  46. // 根据返回的不同值进行处理
  47. switch (Disposition) {
  48. // 如果返回的是继续执行,则在没有产生异常的情况下返回
  49. case ExceptionContinueExecution:
  50. if ((ExceptionRecord->ExceptionFlags &
  51. EXCEPTION_NONCONTINUABLE) != 0) {
  52. ExceptionRecord1.ExceptionCode =
  53. STATUS_NONCONTINUABLE_EXCEPTION;
  54. ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  55. ExceptionRecord1.ExceptionRecord = ExceptionRecord;
  56. ExceptionRecord1.NumberParameters = 0;
  57. RtlRaiseException(&ExceptionRecord1);
  58. } else {
  59. Completion = TRUE;
  60. goto DispatchExit;
  61. }
  62. // 如果返回向上搜索,则继续进行循环
  63. case ExceptionContinueSearch :
  64. if (ExceptionRecord->ExceptionFlags & EXCEPTION_STACK_INVALID)
  65. goto DispatchExit;
  66. break;
  67. // 如果返回的不是以上任意一个值,则抛出 EXCEPTION_NONCONTINUABLE 异常
  68. default :
  69. ExceptionRecord1.ExceptionCode = STATUS_INVALID_DISPOSITION;
  70. ExceptionRecord1.ExceptionFlags = EXCEPTION_NONCONTINUABLE(1);
  71. ExceptionRecord1.ExceptionRecord = ExceptionRecord;
  72. ExceptionRecord1.NumberParameters = 0;
  73. RtlRaiseException(&ExceptionRecord1);
  74. break;
  75. }
  76. // 如果链表遍历过程中没有发生任何错误,则继续遍历下一层
  77. RegistrationPointer = RegistrationPointer->Next;
  78. }
  79. Completion = FALSE;
  80. DispatchExit:
  81. return Completion;
  82. }

RtlDispatchException[3].c

  1. BOOLEAN RtlDispatchException (
  2. IN PEXCEPTION_RECORD ExceptionRecord,
  3. IN PCONTEXT ContextRecord
  4. )
  5. {
  6. PEXCEPTION_REGISTRATION_RECORD RegistrationFrame
  7. PEXCEPTION_REGISTRATION_RECORD NestedFrame = NULL;
  8. DISPATCHER_CONTEXT DispatcherContext;
  9. EXCEPTION_RECORD ExceptionRecord2;
  10. EXCEPTION_DISPOSITION Disposition;
  11. ULONG_PTR StackLow, StackHigh;
  12. ULONG_PTR RegistrationFrameEnd;
  13. // 调用向量化异常处理程序(VEH)
  14. if (RtlCallVectoredExceptionHandlers(ExceptionRecord, Context))
  15. {
  16. // 异常被处理了就调用向量化异常处理程序(VCH)
  17. RtlCallVectoredContinueHandlers(ExceptionRecord, Context);
  18. // 返回已处理
  19. return TRUE;
  20. }
  21. // 获取当前栈顶以及栈限长
  22. RtlpGetStackLimits(&StackLow, &StackHigh);
  23. RegistrationFrame = RtlpGetExceptionList();
  24. // 不断循环遍历所有的 SEH 结构
  25. while (RegistrationFrame != EXCEPTION_CHAIN_END)
  26. {
  27. // 检查是否为空,一个被注册的异常结构永远不为空
  28. ASSERT(RegistrationFrame != NULL);
  29. // 获取异常结构的结束位置
  30. RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame +
  31. sizeof(EXCEPTION_REGISTRATION_RECORD);
  32. // 检查异常结构是否在栈内,并且是否对齐
  33. if ((RegistrationFrameEnd > StackHigh) ||
  34. ((ULONG_PTR)RegistrationFrame < StackLow) ||
  35. ((ULONG_PTR)RegistrationFrame & 0x3) ||
  36. !RtlIsValidHandler(SehBase->Handler))
  37. {
  38. // 如果不满足条件
  39. ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
  40. // 返回处理失败
  41. return FALSE;
  42. }
  43. // 如果启用了调试异常的记录,则记录异常
  44. RtlpCheckLogException(ExceptionRecord,
  45. Context,
  46. RegistrationFrame,
  47. sizeof(*RegistrationFrame));
  48. // 调用异常处理函数, exception_handler4
  49. Disposition = RtlpExecuteHandlerForException(ExceptionRecord,
  50. RegistrationFrame,
  51. Context,
  52. &DispatcherContext,
  53. RegistrationFrame->Handler);
  54. // 如果是嵌套异常处理结构体
  55. if (RegistrationFrame == NestedFrame)
  56. {
  57. // 屏蔽嵌套标志位
  58. ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL;
  59. NestedFrame = NULL;
  60. }
  61. // 根据 SEH 函数的处理结果进行性对应的操作
  62. switch (Disposition)
  63. {
  64. // 继续执行
  65. case ExceptionContinueExecution:
  66. // 如果是一个不可继续的异常
  67. if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
  68. {
  69. // 设置异常信息
  70. ExceptionRecord2.ExceptionRecord = ExceptionRecord;
  71. ExceptionRecord2.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
  72. ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  73. ExceptionRecord2.NumberParameters = 0;
  74. RtlRaiseException(&ExceptionRecord2);
  75. }
  76. else
  77. {
  78. // 调用向量化异常处理程序(VCH)
  79. RtlCallVectoredContinueHandlers(ExceptionRecord,
  80. Context);
  81. return TRUE;
  82. }
  83. // 搜索下一层异常处理函数
  84. case ExceptionContinueSearch:
  85. break;
  86. // 如果是嵌套异常
  87. case ExceptionNestedException:
  88. ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
  89. if (DispatcherContext.RegistrationPointer > NestedFrame)
  90. {
  91. NestedFrame = DispatcherContext.RegistrationPointer;
  92. }
  93. break;
  94. // 其它操作
  95. default:
  96. // 设置异常信息结构
  97. ExceptionRecord2.ExceptionRecord = ExceptionRecord;
  98. ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION;
  99. ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  100. ExceptionRecord2.NumberParameters = 0;
  101. RtlRaiseException(&ExceptionRecord2);
  102. break;
  103. }
  104. // 获取下一个异常处理函数
  105. RegistrationFrame = RegistrationFrame->Next;
  106. }
  107. // 如果没有处理就返回 FALSE
  108. return FALSE;
  109. }