Windows调试流程分析

Windows调试流程分析

Gat1ta 2,588 2021-01-23

想要调试一个程序有两种方法,第一种是使用CreateProcess函数创建进程并调试它,另一种是使用DebugActiveProcess函数附加到目标进程调试。这两个函数内部都会创建一个调试对象,将调试对象的句柄保存在调试线程的TEB中,将调试对象保存在目标进程的debugport中,这样两个进程就建立起了联系。当发生调试事件的时候,操作系统会将调试事件结构体挂入调试对象的事件链表中,调试器通过获取调试事件数据来达到调试的目的。

至此,我们已经与目标进程建立起了调试关系,接下来,我们主要来分析操作系统是怎么处理用户态调试事件的。

系统:Win10 1511 Build:10586.

0x0:IDT表

在保护模式下,当有中断或异常发生时,CPU是通过中断描述符表(Interrupt Descriptor Table,简称IDT)来寻找处理函数的。因此,可以说IDT表是CPU(硬件)与操作系统(软件)交接中断和异常的关口(Gate)。

简单来说,IDT表是一张位于物理内存中的线性表,共有256个表项。在IA-32E(64位)模式下,每个IDT表项长度是16个字节,IDT表的总长的是4096字节(4KB)。在32位模式下,每个IDT表项长度是8个字节,IDT表的总长度是2048字节(2KB)。32位与64位主要差异在于地址长度的变化。

IDT表的每个表项是一个所谓的门描述符(Gate Descriptor)结构。之所以这样称呼,是因为IDT表项的基本用途就是引领CPU从一个空间到另一个空间去执行,每个表项好像一个从一个空间到另一个空间的大门(Gate)。在穿越这扇门的时候,CPU会做必要的安全检查和准备工作。

IDT表可以包含以下3种描述符。

任务门(Task-gate)描述符:用于任务切换,里面包含用于选择任务状态段(TSS)的段选择子。可以使用JMP或CALL指令通过任务门来切换到任务门所指向的任务,当CPU因为中断或异常转移到任务门的时候,也会切换到指定的任务。

中断门(interrupt-gate)描述符:用于描述中断处理例程的入口。

陷阱们(trap-gate)描述符:用于描述异常处理例程的入口。

下图描述了3种门描述符内容布局:
gate

0x1:调试的本质

调试的本质其实就是异常的处理,当异常发生时,会调用IDT表中对应的异常处理函数。

日常调试中主要用的有三种异常,硬件断点(设置调试寄存器产生的断点),软件断点(int3断点),内存断点(内存异常产生的断点)。不同类型的异常调用的处理程序略有不同,但是差别不大。

不同的异常(或中断)有不同的中断向量号。

当异常发生时,处理器会根据向量号去IDT表中查找对应的门描述符并且调用其处理程序。调用处理程序时,会根据门描述符的 TYPE位来确定要压栈的参数,根据CPL和DPL来判断需不需要切换栈。

当处理器调用异常处理程序时,如果CPL小于DPL,则会发生栈切换,也就是从用户栈切换到内核栈,内核栈的SS,ESP从TSS中获取。有关这方面的细节,请参考保护模式相关知识,这里不再详细说明。

本篇文章的重点在于异常发生后,操作系统的处理流程是什么样的,下面以int3断点的处理流程进行分析。

0x2:异常处理的起点

当代码运行到Int3时,会引发一个int3中断,CPU会去IDT找到3号中断所对应的IDT表项,执行对应的处理代码:

                sub     rsp, 8
                push    rbp
                sub     rsp, 158h
                lea     rbp, [rsp+80h]
                mov     byte ptr [rbp-_KTRAP_FRAME.ExceptionActive], 1
                mov     [rbp-_KTRAP_FRAME.rax], rax
                mov     [rbp-_KTRAP_FRAME.rcx], rcx
                mov     [rbp-_KTRAP_FRAME.rdx], rdx
                mov     [rbp-_KTRAP_FRAME.r8], r8
                mov     [rbp-_KTRAP_FRAME.r9], r9
                mov     [rbp-_KTRAP_FRAME.r10], r10
                mov     [rbp-_KTRAP_FRAME.r11], r11
                test    byte ptr [rbp+_KTRAP_FRAME.SegCs], 1 ; 判断CPL确定当前特权级别,如果是内核模式就跳转
                jz      short loc_14014A3E1
                swapgs                  ; 切换GS寄存器,相当于x32的FS寄存器,在应用层指向的是TEB,在内核模式下应指向KPCR
                mov     r10, gs:_KPCR.Prcb.CurrentThread
                test    byte ptr [r10+CurrentThread.Header.DebugActive], 80h ; 判断UmsPrimary是否置位
                jz      short loc_14014A3CC
                mov     ecx, 0C0000102h
                rdmsr
                shl     rdx, 20h
                or      rax, rdx
                cmp     [r10+CurrentThread.Teb], rax
                jz      short loc_14014A3CC
                cmp     [r10+CurrentThread.TebMappedLowVa], rax
                jz      short loc_14014A3CC
                mov     rdx, [r10+CurrentThread.Ucb]
                bts     dword ptr [r10+.CurrentThread.SystemAffinityActive], 8
                dec     word ptr [r10+CurrentThread.SpecialApcDisable]
                mov     [rdx+80h], rax
loc_14014A3CC:                          ; CODE XREF: KiBreakpointTrap+4E↑j
                                        ; KiBreakpointTrap+65↑j ...
                test    byte ptr [r10+CurrentThread.Header.DebugActive], 3 ; 判断ActiveDR7或Instrumented是否置位
                mov     word ptr [rbp+_KTRAP_FRAME.ErrorCode], 0
                jz      short loc_14014A3E1 ; 如果ActiveDR7和Instrumented都没置位,则不保存调试寄存器
                call    KiSaveDebugRegisterState
loc_14014A3E1:                          ; CODE XREF: KiBreakpointTrap+3B↑j 内核模式
                                        ; KiBreakpointTrap+9A↑j
                cld                     ; 
                stmxcsr dword ptr [rbp-_KTRAP_FRAME.MxCsr]
                ldmxcsr dword ptr gs:_KPCR.Prcb
                movaps  xmmword ptr [rbp-_KTRAP_FRAME.Xmm0], xmm0
                movaps  xmmword ptr [rbp+_KTRAP_FRAME.xmm1], xmm1
                movaps  xmmword ptr [rbp+_KTRAP_FRAME.xmm2], xmm2
                movaps  xmmword ptr [rbp+_KTRAP_FRAME.xmm3], xmm3
                movaps  xmmword ptr [rbp+_KTRAP_FRAME.xmm4], xmm4
                movaps  xmmword ptr [rbp+_KTRAP_FRAME.xmm5], xmm5
                test    dword ptr [rbp+_KTRAP_FRAME.EFlags], 200h ; 判断Eflags寄存器的IF位,是否响应可屏蔽中断,置位则响应
                jz      short loc_14014A414
                sti                     ; 将Eflags寄存器的IF位置位
loc_14014A414:                          ; CODE XREF: KiBreakpointTrap+D1↑j
                mov     ecx, 80000003h  ; 参数1:异常代码
                mov     edx, 1            参数2
                mov     r8, [rbp+0E8h]  ; 触发异常的下一条指令
                dec     r8                参数3:重新指向Int3那条指令
                mov     r9d, 0            参数4
                <strong>call    KiExceptionDispatch</strong>
                nop
                retn</span>

可以看到,处理函数的主要工作就是构建了一个_KTRAP_FRAME结构体,操作系统用这个结构体保存异常处的执行环境,当异常处理完成的时候操作系统要根据这个结构体返回三环。

第一个sub rsp,8是因为要空出来一个ErrorCode的空间,因为处理器调用中断处理程序时会自动像栈中压入一些参数,int3中断压入的参数是SS,RSP,Eflags,CS,RIP,但是有的中断会在此之上在压入一个ErrorCode,所以Int3的处理程序为了统一,需要在入口处sub Rsp,8空出来一个ErrorCode的位置。

然后将rbp压栈,继续提升栈顶开始构建_KTRAP_FRAME结构体,保存好一些通用寄存器后,判断CPL是否为内核态,如果不是内核态则切换GS寄存器。在X64中,由GS寄存器替代了FS寄存器的作用,在应用层GS指向TEB,到内核层GS指向KPCR,所以如果不是应用层的话就需要切换GS寄存器。

然后根据CurrentThread.Header.DebugActive的ActiveDR7和Instrumented来判断是否要保存调试寄存器,当结构体构建完成后,就准备调用KiExceptionDispatch函数进行异常分发。

0x3:KiExceptionDispatch

需要指出的是nt!KiExceptionDispatch (和nt!KiBreakpointTrap一样)是用手工汇编写成。它假设ecx包含异常代码,edx是异常参数的数量(最多3个),r8包含异常发生的地址,r9是第一个异常参数(如果有存在),r10是第二个异常参数(如果存在),r11是第三个异常参数(如果存在),rbp指向_KTRAP_FRAME结构体中的一个段(位于偏移+0x80处)

                sub     rsp, 1D8h       ; 提升栈顶,构建_KEXCEPTION_FRAME和_EXCEPTION_RECORD结构体
                lea     rax, [rsp+100h]
                movaps  xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm6], xmm6
                movaps  xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm7], xmm7
                movaps  xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm8], xmm8
                movaps  xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm9], xmm9
                movaps  xmmword ptr [rsp+_KEXCEPTION_FRAME.xmm10], xmm10
                movaps  xmmword ptr [rax-_KEXCEPTION_FRAME.xmm11], xmm11
                movaps  xmmword ptr [rax-_KEXCEPTION_FRAME.xmm12], xmm12
                movaps  xmmword ptr [rax-_KEXCEPTION_FRAME.xmm13], xmm13
                movaps  xmmword ptr [rax-_KEXCEPTION_FRAME.xmm14], xmm14
                movaps  xmmword ptr [rax-_KEXCEPTION_FRAME.xmm15], xmm15
                mov     [rax+_KEXCEPTION_FRAME.rbx], rbx
                mov     [rax+_KEXCEPTION_FRAME.rdi], rdi
                mov     [rax+_KEXCEPTION_FRAME.rsi], rsi
                mov     [rax+_KEXCEPTION_FRAME.r12], r12
                mov     [rax+_KEXCEPTION_FRAME.r13], r13
                mov     [rax+_KEXCEPTION_FRAME.r14], r14
                mov     [rax+_KEXCEPTION_FRAME.r15], r15
                mov     rax, gs:_KPCR.Prcb.CurrentThread
                bt      dword ptr [rax+CurrentThread.SystemAffinityActive], 8
                jnb     short loc_14014CB7D
                test    byte ptr [rbp+_KTRAP_FRAME.SegCs], 1 ; 通过CPL判断Previous mode,如果是内核模式则跳转
                jz      short loc_14014CB7D
                call    KiUmsExceptionEntry ; ums线程相关
loc_14014CB7D:                          ; CODE XREF: KiExceptionDispatch+6D↑j
                                        ; KiExceptionDispatch+76↑j
                lea     rax, [rsp+138h] ; rax指向_EXCEPTION_RECORD结构体
                mov     [rax+_EXCEPTION_RECORD.ExceptionCode], ecx
                xor     ecx, ecx
                mov     [rax+_EXCEPTION_RECORD.ExceptionFlags], ecx
                mov     [rax+_EXCEPTION_RECORD.ExceptionRecord], rcx
                mov     [rax+_EXCEPTION_RECORD.ExceptionAddress], r8
                mov     [rax+_EXCEPTION_RECORD.NumberParameters], edx
                mov     [rax+_EXCEPTION_RECORD.ExceptionInformation[0]], r9
                mov     [rax+_EXCEPTION_RECORD.ExceptionInformation[1]], r10
                mov     [rax+_EXCEPTION_RECORD.ExceptionInformation[2]], r11
                mov     r9b, [rbp+_KTRAP_FRAME.SegCs]
                and     r9b, 1          ; PreviousMode
                mov     byte ptr [rsp+20h], 1 ;  BOOLEAN FirstChance
                lea     r8, [rbp-80h]   ; PKTRAP_FRAME TrapFrame。Rbp指向的还是IDT函数中封装的TrapFrame结构体的腰部,RBP-80指向的就是TrapFrame结构体头部
                mov     rdx, rsp        ; PKEXCEPTION_FRAME ExceptionFrame
                mov     rcx, rax        ; PEXCEPTION_RECORD ExceptionRecord
                call    KiDispatchException    异常分发主函数

这个函数的逻辑很简单,函数入口首先就是提升栈顶,留出两个结构体的空间,分别是_KEXCEPTION_FRAME结构体和_EXCEPTION_RECORD结构体。构建完结构体后就调用了KiDispatchException函数用来进行异常分发。

0x4:KiDispatchException

KiDispatchException是异常分发的主函数,函数代码也比较复杂,汇编层次不清晰,所以直接放IDA伪代码了。

__fastcall KiDispatchException(_EXCEPTION_RECORD *ExceptionRecord, KEXCEPTION_FRAME *ExceptionFrame, KTRAP_FRAME *TrapFrame, char PreviousMode, char FirstChance)
{
  char PreviousMode_1; // r15
  KTRAP_FRAME *TrapFrame_1; // rsi
  KEXCEPTION_FRAME *ExceptionFrame_1; // r12
  _EXCEPTION_RECORD *ExceptionRecord_1; // rbx
  signed int Contextflag; // er13
  unsigned __int64 v10; // rcx
  signed __int64 v11; // rcx
  void *v12; // rsp
  void *v13; // rsp
  int v14; // edx
  unsigned __int64 result; // rax
  __int64 v16; // r8
  bool DebugService; // al
  __int64 user.rsp; // r12
  unsigned __int64 user.rsp_1; // rdx
  _BYTE *user_context; // r15
  SIZE_T size; // rcx
  _QWORD *v22; // rdx
  _OWORD *v23; // rcx
  signed __int64 v24; // rcx
  __int64 v25; // rbx
  __int64 CurrentProcess_2; // r14
  __int64 v27; // r8
  ULONG_PTR PreviousMode_2; // [rsp+20h] [rbp-10h]
  __int64 SecondChance; // [rsp+28h] [rbp-8h]
  CONTEXT *Context; // [rsp+30h] [rbp+0h]
  __int64 CurrentProcess; // [rsp+38h] [rbp+8h]
  int Contextflag_1; // [rsp+40h] [rbp+10h]
  __int64 context.P4Home; // [rsp+48h] [rbp+18h]
  int v35; // [rsp+50h] [rbp+20h]
  unsigned int ContextLength; // [rsp+54h] [rbp+24h]
  __int64 ExceptionRecord_2; // [rsp+60h] [rbp+30h]
  __int64 context.SegCs; // [rsp+68h] [rbp+38h]
  __int64 ContextEx; // [rsp+70h] [rbp+40h]
  unsigned __int64 user.rsp_2; // [rsp+78h] [rbp+48h]
  __int64 TrapFrame_2; // [rsp+88h] [rbp+58h]
  _BYTE *user_context_1; // [rsp+90h] [rbp+60h]
  CONTEXT **v43; // [rsp+98h] [rbp+68h]
  __int64 CurrentProcess_1; // [rsp+A0h] [rbp+70h]
  MACHINE_FRAME *MachineFrame; // [rsp+A8h] [rbp+78h]
  __int64 ExceptionFrame_2; // [rsp+B0h] [rbp+80h]
  __int64 pDesBuffer; // [rsp+C0h] [rbp+90h]
  _OWORD *context.Rsp; // [rsp+C8h] [rbp+98h]
  int v49; // [rsp+E0h] [rbp+B0h]
  __int64 Context.rip; // [rsp+128h] [rbp+F8h]
  __int64 a3[2]; // [rsp+180h] [rbp+150h]
  __int64 v52; // [rsp+190h] [rbp+160h]
  PreviousMode_1 = PreviousMode;
  TrapFrame_1 = TrapFrame;
  ExceptionFrame_1 = ExceptionFrame;
  ExceptionRecord_1 = ExceptionRecord;
  ExceptionRecord_2 = (__int64)ExceptionRecord;
  ExceptionFrame_2 = (__int64)ExceptionFrame;
  TrapFrame_2 = (__int64)TrapFrame;
  LOBYTE(Context) = PreviousMode;
  CurrentProcess = *(_QWORD *)(__readgsqword(0x188u) + 0xB8);// CurrentThread.ApcState.Process
  CurrentProcess_1 = CurrentProcess;
  __incgsdword(0x5CB4u);                        // kpcr.kprcb.KeExceptionDispatchCount++
  Contextflag = 0x10001F;
  Contextflag_1 = 0x10001F;
  if ( PreviousMode )
  {
    if ( KeFeatureBits & 0x800000 )
      Contextflag = 0x10005F;
    Contextflag_1 = Contextflag;
  }
  RtlGetExtendedContextLength(Contextflag);     // 获取ExtendContext长度
  v10 = ContextLength + 15i64;
  if ( v10 <= ContextLength )
    v10 = 0xFFFFFFFFFFFFFF0i64;
  v11 = v10 & 0xFFFFFFFFFFFFFFF0ui64;
  v12 = alloca(v11);                            // 分配CONTEXT_EX内存
  v13 = alloca(v11);
  v43 = &Context;
  v35 = RtlInitializeExtendedContext((__int64)&Context, Contextflag, (int **)&ContextEx);
  KeContextFromKframes(TrapFrame_1, ExceptionFrame_1, &Context);// 根据ExceptionFrame和TrapFrame结构体构造Context结构体
  if ( ExceptionRecord_1->ExceptionCode == 0x80000003 )
    --Context.rip;
  if ( PreviousMode_1 && *(_QWORD *)(CurrentProcess + 0x6F8) )// CurrentProcess.PicoContext 可能用于windows下的linux子系统
  {
    LOBYTE(result) = PspPicoProviderRoutines_0(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 0i64, PreviousMode_1);// PspPicoProviderRoutines函数
    if ( (_BYTE)result )
      return result;                            // 如果异常解决了,就将Context转换成KFrames并返回
  }
  else if ( (unsigned __int8)KiPreprocessFault(ExceptionRecord_1) )// 如果异常是内部通用保护错误、无效操作码或整数除以0,则尝试在不实际引发异常的情况下解决问题。
  {
LABEL_39:
    LOBYTE(result) = KeContextToKframes((__int64)TrapFrame_1);
    return result;
  }
  if ( !PreviousMode_1 )                        // 判断PreviousMode,正式开始异常分发。
  {
    if ( !FirstChance
      || (LOBYTE(SecondChance) = 0,
          LOBYTE(PreviousMode_2) = 0,
          !(unsigned __int8)KiDebugRoutine(     // 第一次分发先交给调试器
                              TrapFrame_1,
                              ExceptionFrame_1,
                              ExceptionRecord_1,
                              &Context,
                              PreviousMode_2,
                              SecondChance))
      && !(unsigned __int8)RtlDispatchException(ExceptionRecord_1, &Context) )// 如果调试器不出来开始调用异常处理函数处理异常
    {                                           // 二次分发执行块
      LOBYTE(SecondChance) = 1;
      LOBYTE(PreviousMode_2) = 0;
      if ( !(unsigned __int8)KiDebugRoutine(
                               TrapFrame_1,
                               ExceptionFrame_1,
                               ExceptionRecord_1,
                               &Context,
                               PreviousMode_2,
                               SecondChance) )
        KeBugCheckEx(                           // 二次分发还没被调试器处理则蓝屏
          0x1Eu,
          ExceptionRecord_1->ExceptionCode,
          (ULONG_PTR)ExceptionRecord_1->ExceptionAddress,
          ExceptionRecord_1->ExceptionInformation[0],
          ExceptionRecord_1->ExceptionInformation[1]);
    }
    goto LABEL_39;
  }
  v14 = (signed int)context.Rsp;
  context.P4Home = (__int64)context.Rsp;
  context.SegCs = (__int64)context.Rsp;         // 这里IDA有错误,动态调试是存储在其他变量中的
  if ( !(*(_DWORD *)(CurrentProcess + 0x6B4) & 1) )// CurrentProcess.Flags3.Minimal
  {
    result = __readgsqword(0x188u);
    if ( *(_QWORD *)(*(_QWORD *)(result + 0xB8) + 0x428i64)// CurrentThread.Apcstate.Process.WoW64Process
      && ExceptionRecord_1->ExceptionCode == 0x80000002
      && TrapFrame_1->EFlags & 0x40000 )
    {
      _disable();
      TrapFrame_1->EFlags &= 0xFFFBFFFF;
      _enable();
      return result;
    }
    if ( (context.SegCs & 0xFFF8) == 0x20 )     // 如果在执行32位代码时发生异常,则将异常转换为wow64异常
    {
      if ( ExceptionRecord_1->ExceptionCode == 0x80000003 )
      {
        ExceptionRecord_1->ExceptionCode = 0x4000001F;
      }
      else if ( ExceptionRecord_1->ExceptionCode == 0x80000004 )
      {
        ExceptionRecord_1->ExceptionCode = 0x4000001E;
      }
      context.P4Home = v14 & 0xFFFFFFF0;
      context.SegCs = v14 & 0xFFFFFFF0;
    }
  }
  memset(
    (char *)ExceptionRecord_1 + 8 * (ExceptionRecord_1->NumberParameters + 4i64),
    0,
    -8 * (ExceptionRecord_1->NumberParameters + 4i64) + 0x98);// 将exceptionrecord参数后面的内存清零,为了KdIsThisAKdTrap做准备
  if ( FirstChance )                            // 用户态第一次分发
  {
    DebugService = KdIsThisAKdTrap(ExceptionRecord_1);
    if ( !*(_QWORD *)(*(_QWORD *)(__readgsqword(0x188u) + 0xB8) + 0x420i64) && !KdIgnoreUmExceptions || DebugService )// 如果当前进程没有被调试,并且没有设置忽略用户态异常,或者这是一个调试服务则执行下面代码
    {
      LOBYTE(SecondChance) = 0;
      LOBYTE(PreviousMode_2) = PreviousMode_1;
      if ( (unsigned __int8)KiDebugRoutine(     // 如果异常被调试器处理则返回
                              TrapFrame_1,
                              ExceptionFrame_1,
                              ExceptionRecord_1,
                              &Context,
                              PreviousMode_2,
                              SecondChance) )
        goto LABEL_39;
    }
    LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, 0i64);
    if ( !(_BYTE)result )                       // 如果异常没被处理
    {
      if ( !*(_QWORD *)(CurrentProcess + 0x6F8) // CurrentProcess.PicoContext
        || (LOBYTE(PreviousMode_2) = PreviousMode_1,
            LOBYTE(result) = PspPicoProviderRoutines_0(// PspPicoProviderRoutines
                               ExceptionRecord_1,
                               ExceptionFrame_1,
                               TrapFrame_1,
                               1i64,
                               PreviousMode_2), // CurrentProcess.PicoContext如果为零或者PspPicoProviderRoutines返回True执行
            !(_BYTE)result) )
      {
        _disable();
        TrapFrame_1->EFlags &= 0xFFFFFEFF;      // 清空TF标志位,调试标志位
        _enable();
        v49 = 0xC0000005;
        user.rsp = context.P4Home;
        user.rsp_1 = context.P4Home;
        user.rsp_2 = context.P4Home;
        if ( (Contextflag & 0x100040) == 0x100040 )
        {
          user.rsp_1 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64;
          user.rsp_2 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64;
        }
        CurrentProcess = (user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64;
        MachineFrame = (MACHINE_FRAME *)((user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64);// 这里是开辟用户栈空间 -0x28是_MACHINE_FRAME大小
        context.P4Home = CurrentProcess - 0xA0; // MachineFrame - EXCEPTION_RECORD_LENGTH;EXCEPTION_RECORD_LENGTH=sizeof(EXCEPTION_RECORD) + STACK_ROUND) & ~STACK_ROUND
        context.Rsp = (_OWORD *)(CurrentProcess - 0xA0);
        pDesBuffer = CurrentProcess - 0xC0;
        user_context = (_BYTE *)(CurrentProcess - 0x590);// context
        user_context_1 = (_BYTE *)(CurrentProcess - 0x590);
        LODWORD(a3[0]) = 0xFFFFFB30;
        size = user.rsp - (CurrentProcess - 0x590);// 计算出一共栈顶提升了多少字节
        HIDWORD(a3[0]) = user.rsp - (CurrentProcess - 0x590);
        a3[1] = 0x4D0FFFFFB30i64;
        LODWORD(v52) = user.rsp_1 - (CurrentProcess - 0xC0);
        HIDWORD(v52) = user.rsp - user.rsp_1;
        if ( (unsigned __int64)(user.rsp - (CurrentProcess - 0x590) - 1) > 0xFFE )
        {
          ProbeForWrite(user_context, size, 0x10u);// 检测内存地址是否正常,可写,并且不越界
          v23 = (_OWORD *)context.P4Home;
          v22 = (_QWORD *)CurrentProcess;
        }
        else
        {
          if ( ((_BYTE)CurrentProcess + 0x70) & 0xF )
            ExRaiseDatatypeMisalignment();
          if ( user_context >= MmUserProbeAddress )
            user_context = MmUserProbeAddress;
          *user_context = *user_context;
          user_context[size - 1] = user_context[size - 1];
          v22 = &MachineFrame->Rip;
          v23 = context.Rsp;
          user_context = user_context_1;
        }
        v22[3] = user.rsp;
        *v22 = Context.rip;
        *v23 = *(_OWORD *)&ExceptionRecord_1->ExceptionCode;
        v23[1] = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;
        v23[2] = *(_OWORD *)ExceptionRecord_1->ExceptionInformation;
        v23[3] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2];
        v23[4] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4];
        v23[5] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6];
        v23[6] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8];
        v24 = (signed __int64)(v23 + 8);
        *(_OWORD *)(v24 - 16) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10];
        *(_OWORD *)v24 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12];
        *(_QWORD *)(v24 + 16) = ExceptionRecord_1->ExceptionInformation[14];
        v25 = pDesBuffer;
        v35 = RtlpCopyExtendedContext(1, pDesBuffer, (__int64)a3, Contextflag, ContextEx, 0i64);
        *(_OWORD *)v25 = *(_OWORD *)a3;
        *(_QWORD *)(v25 + 16) = v52;
        _disable();
        TrapFrame_1->Rsp = (ULONG64)user_context;// TrapFrame中的RSP设置为新的栈顶
        TrapFrame_1->SegCs = 0x33;              // 新的CS寄存器
        TrapFrame_1->Rip = KeUserExceptionDispatcher;// 返回三环的落脚点,KeUserExceptionDispatcher调用三环的异常处理函数
        LOBYTE(result) = KiSetupForInstrumentationReturn((__int64)TrapFrame_1);
        _enable();
      }
    }
  }
  else                                          // 用户态第二次分发
  {
    CurrentProcess_2 = CurrentProcess;
    LOBYTE(v16) = 1;                            // SecondChance
    LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, v16);// 将SecondChance设置为True再次发送给调试器
    if ( !(_BYTE)result )                       // 如果还是没被处理,判断进程是不是一个Pico进程,如果是的话调用PspPicoProviderRoutines函数
    {
      if ( !*(_QWORD *)(CurrentProcess_2 + 0x6F8)// CurrentProcess.PicoContext
        || (LOBYTE(PreviousMode_2) = PreviousMode_1,
            LOBYTE(result) = PspPicoProviderRoutines_0(
                               ExceptionRecord_1,
                               ExceptionFrame_1,
                               TrapFrame_1,
                               2i64,
                               PreviousMode_2),
            !(_BYTE)result) )
      {
        LOBYTE(v27) = 1;
        LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 0, v27);// 发送给异常端口
        if ( !(_BYTE)result )
          LOBYTE(result) = ZwTerminateProcess(-1i64, (unsigned int)ExceptionRecord_1->ExceptionCode);
      }
    }
  }
  return result;
}

前面说过,当异常处理完成时,系统要根据TrapFrame结构返回三环,所以该函数首先要备份一下TrapFrame和ExceptionFrame结构体到Context结构体。

if ( PreviousMode )
  {
    if ( KeFeatureBits & 0x800000 )
      Contextflag = 0x10005F;
    Contextflag_1 = Contextflag;
  }
  RtlGetExtendedContextLength(Contextflag);     // 获取ExtendContext长度
  v10 = ContextLength + 15i64;
  if ( v10 <= ContextLength )
    v10 = 0xFFFFFFFFFFFFFF0i64;
  v11 = v10 & 0xFFFFFFFFFFFFFFF0ui64;
  v12 = alloca(v11);                            // 分配CONTEXT_EX内存
  v13 = alloca(v11);
  v43 = &Context;
  v35 = RtlInitializeExtendedContext((__int64)&Context, Contextflag, (int **)&ContextEx);
  KeContextFromKframes(TrapFrame_1, ExceptionFrame_1, &Context);// 根据ExceptionFrame和TrapFrame结构体构造Context结构体

如果是int3异常呢,操作系统默认int3指令为cc,占1字节,所以将RIP减一指向触发异常的指令地址。但是其实INT3并不是只有1字节,也有2字节的。

  if ( ExceptionRecord_1->ExceptionCode == 0x80000003 )
    --Context.rip;

然后判断一下该进程是不是Pico进程,经过查阅资料得知,Pico应该是windows下的linux子系统的进程。PspPicoProviderRoutines可能是负责解决pico进程异常的函数,这是一个函数指针,在windbg中看当前指针为空,可能是由于调试系统并没有安装linux子系统组件的缘故。

如果不是pico进程,则尝试调用KiPreprocessFault处理异常,如果异常是内部通用保护错误,无效操作码或者除零错误,则尝试在不实际引发异常的情况下解决问题。

if ( PreviousMode_1 && *(_QWORD *)(CurrentProcess + 0x6F8) )// CurrentProcess.PicoContext 可能用于windows下的linux子系统
  {
    LOBYTE(result) = PspPicoProviderRoutines_0(ExceptionRecord_1, ExceptionFrame_1, TrapFrame_1, 0i64, PreviousMode_1);// PspPicoProviderRoutines函数
    if ( (_BYTE)result )
      return result;                            // 如果异常解决了,就将Context转换成KFrames并返回
  }
  else if ( (unsigned __int8)KiPreprocessFault(ExceptionRecord_1) )// 如果异常是内部通用保护错误、无效操作码或整数除以0,则尝试在不实际引发异常的情况下解决问题。
  {
LABEL_39:
    LOBYTE(result) = KeContextToKframes((__int64)TrapFrame_1);
    return result;
  }

之后就开始进行正式的异常分发,如果先前模式为内核模式,则调用KiDebugRoutine尝试将异常发给调试器。KiDebugRoutine这是内核的全局变量函数指针,当系统在被调试的状态下指向KdpTrap函数,非调试下指向KdpStub函数。KdpStub函数非常简单,只是做了一些简单的处理就返回了False。KdpTrap函数会将异常发送给内核调试器,如果返回True,代表调试器处理了这个异常,如果返回False,则代表没有处理这个异常。

在第一次分发的时候,首先把异常发给调试器,如果调试器处理了异常则直接返回。如果调试器没有处理异常,则调用RtlDispatchException执行内核异常处理函数。RtlDispatchException的返回值和KiDebugRoutine一样,如果返回True代表异常处理函数处理了异常,如果返回False则代表异常没被处理。

如果异常没被处理,操作系统会将SecondChance设置为True,并且再一次调用KiDebugRoutine,一般调试器在收到SecondChance为True的异常时都会中断,哪怕这个异常不是用户设置的异常。

因为当第二次分发KiDebugRoutine依旧返回False的时候,系统会直接调用KeBugCheckEx引发BSOD!也就是蓝屏~

if ( !PreviousMode_1 )                        // 判断PreviousMode,正式开始异常分发。
  {
    if ( !FirstChance
      || (LOBYTE(SecondChance) = 0,
          LOBYTE(PreviousMode_2) = 0,
          !(unsigned __int8)KiDebugRoutine(     // 第一次分发先交给调试器
                              TrapFrame_1,
                              ExceptionFrame_1,
                              ExceptionRecord_1,
                              &Context,
                              PreviousMode_2,
                              SecondChance))
      && !(unsigned __int8)RtlDispatchException(ExceptionRecord_1, &Context) )// 如果调试器不出来开始调用异常处理函数处理异常
    {                                           // 二次分发执行块
      LOBYTE(SecondChance) = 1;
      LOBYTE(PreviousMode_2) = 0;
      if ( !(unsigned __int8)KiDebugRoutine(
                               TrapFrame_1,
                               ExceptionFrame_1,
                               ExceptionRecord_1,
                               &Context,
                               PreviousMode_2,
                               SecondChance) )
        KeBugCheckEx(                           // 二次分发还没被调试器处理则蓝屏
          0x1Eu,
          ExceptionRecord_1->ExceptionCode,
          (ULONG_PTR)ExceptionRecord_1->ExceptionAddress,
          ExceptionRecord_1->ExceptionInformation[0],
          ExceptionRecord_1->ExceptionInformation[1]);
    }
    goto LABEL_39;
  }

当先前模式不是内核模式的时候,就要开始进行应用层的异常分发

首先判断了一下CurrentProcess.Flags3.Minimal,这个标志为零才会运行代码块里的内容,不知道这个标志代表什么。

然后判断如果进程是Wow64进程,也就是x32进程的话,并且异常代码是0x80000002,并且Eflag寄存器AC被置位,则这个异常可能是一个对齐异常,将Eflag的AC位清空然后返回。

然后判断通过CS判断如果是一个x32异常,则将异常转换为Wow64的异常代码。

if ( !(*(_DWORD *)(CurrentProcess + 0x6B4) & 1) )// CurrentProcess.Flags3.Minimal
  {
    result = __readgsqword(0x188u);
    if ( *(_QWORD *)(*(_QWORD *)(result + 0xB8) + 0x428i64)// CurrentThread.Apcstate.Process.WoW64Process
      && ExceptionRecord_1->ExceptionCode == 0x80000002
      && TrapFrame_1->EFlags & 0x40000 )
    {
      _disable();
      TrapFrame_1->EFlags &= 0xFFFBFFFF;
      _enable();
      return result;
    }
    if ( (context.SegCs & 0xFFF8) == 0x20 )     // 如果在执行32位代码时发生异常,则将异常转换为wow64异常
    {
      if ( ExceptionRecord_1->ExceptionCode == 0x80000003 )
      {
        ExceptionRecord_1->ExceptionCode = 0x4000001F;
      }
      else if ( ExceptionRecord_1->ExceptionCode == 0x80000004 )
      {
        ExceptionRecord_1->ExceptionCode = 0x4000001E;
      }
      context.P4Home = v14 & 0xFFFFFFF0;
      context.SegCs = v14 & 0xFFFFFFF0;
    }
  }

然后将ExceptionRecord没有数据的内存清零,为了后面调用KdIsThisAKdTrap做准备。

调用KdIsThisAKdTrap判断这个异常是不是一个调试服务,当需要打印调试,征求用户输入,报告模块加载卸载时,系统会触发一个异常,编号为0x2D。通常把这个异常称之为调试服务(DebugService)异常。观察IDT表,可以看到这个异常的处理函数是KiDebugService,这个函数做了一些预处理工作后就跳转到了Int3中断处理函数,我们知道int3是断点异常的处理函数,因此,从KiDebugService跳转到int3后,要传递的调试信息会被当作断点异常的参数传给异常分发函数。ExceptionRecord结构中的ExceptionInfomation[0]标识了这个异常是一个真正的断点异常,还是一个调试服务异常,它可以是以下几个值之一:

BREAKPOINT_BREAK(0):真正的断点异常。
BREAKPOINT_PRINT(1):打印调试信息,ExceptionInformation[1]为要打印的字符串。
BREAKPOINT_PROMPT(2):提示并要求用户输入,ExceptionInformation[1]指向提示字符串,ExceptionInformation[2]用来指向存放用户输入的缓冲区。
BREAKPOINT_LOAD_SYMBOLS(3):加载符号文件,ExceptionInformation[1]指向模块文件名称,ExceptionInformation[2]是模块的基地址。
BREAKPOINT_UNLOAD_SYMBOLS(4):卸载符号文件:ExceptionInformation[1]指向模块文件名称,ExceptionInformation[2]是模块的基地址。
如果当前进程没有被调试,并且系统没有设置忽略用户态异常,或者这是一个调试服务则把异常发送给内核调试器,如果异常被内核调试器处理则直接返回。

 memset(
    (char *)ExceptionRecord_1 + 8 * (ExceptionRecord_1->NumberParameters + 4i64),
    0,
    -8 * (ExceptionRecord_1->NumberParameters + 4i64) + 0x98);// 将exceptionrecord参数后面的内存清零,为了KdIsThisAKdTrap做准备
  if ( FirstChance )                            // 用户态第一次分发
  {
    DebugService = KdIsThisAKdTrap(ExceptionRecord_1);
    if ( !*(_QWORD *)(*(_QWORD *)(__readgsqword(0x188) + 0xB8) + 0x420) && !KdIgnoreUmExceptions || DebugService )// 如果当前进程没有被调试,并且没有设置忽略用户态异常,或者这是一个调试服务则执行下面代码
    {
      LOBYTE(SecondChance) = 0;
      LOBYTE(PreviousMode_2) = PreviousMode_1;
      if ( (unsigned __int8)KiDebugRoutine(     // 如果异常被调试器处理则返回
                              TrapFrame_1,
                              ExceptionFrame_1,
                              ExceptionRecord_1,
                              &Context,
                              PreviousMode_2,
                              SecondChance) )
        goto LABEL_39;
    }

如果没被内核调试器处理则开始调用DbgkForwardException尝试将异常发送给三环调试器,如果调试器返回False则代表异常没有被处理。

当调试器没有处理异常的时候,还是判断当前进程是不是pico进程,如果是调用PspPicoProviderRoutines处理异常,或者DbgkForwardException返回False的之后向下执行。

if代码块中代码主要任务就是将异常信息拷贝到三环栈中,然后返回三环去执行异常处理函数。

首先要清空TrapFrame_1中Eflag寄存器的调试标志位,避免回到三环的时候循环出发异常

由于Context多了一个扩展结构,这个结构的定义不太清楚,所以不能分析的太详细,能力有限。

数据拷贝完成后设置三环的落脚点为KeUserExceptionDispatcher,也就是三环的异常分发函数。

值得一提的是KiSetupForInstrumentationReturn函数将_kprocess结构体中的InstrumentationCallback设置成了RIP的地址,然后将原本的RIP保存在了R10寄存器中,百度查询说这个字段指向一个回调函数,每次从内核层返回用户层的时候会调用。

LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, 0);
    if ( !(_BYTE)result )                       // 如果异常没被处理
    {
      if ( !*(_QWORD *)(CurrentProcess + 0x6F8) // CurrentProcess.PicoContext
        || (LOBYTE(PreviousMode_2) = PreviousMode_1,
            LOBYTE(result) = PspPicoProviderRoutines_0(// PspPicoProviderRoutines
                               ExceptionRecord_1,
                               ExceptionFrame_1,
                               TrapFrame_1,
                               1i64,
                               PreviousMode_2), // CurrentProcess.PicoContext如果为零或者PspPicoProviderRoutines返回True执行
            !(_BYTE)result) )
      {
        _disable();
        TrapFrame_1->EFlags &= 0xFFFFFEFF;      // 清空TF标志位,调试标志位
        _enable();
        v49 = 0xC0000005;
        user.rsp = context.P4Home;
        user.rsp_1 = context.P4Home;
        user.rsp_2 = context.P4Home;
        if ( (Contextflag & 0x100040) == 0x100040 )
        {
          user.rsp_1 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64;
          user.rsp_2 = (context.P4Home - *(unsigned int *)(ContextEx + 0x14)) & 0xFFFFFFFFFFFFFFC0ui64;
        }
        CurrentProcess = (user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64;
        MachineFrame = (MACHINE_FRAME *)((user.rsp_1 - 0x28) & 0xFFFFFFFFFFFFFFF0ui64);// 这里是开辟用户栈空间 -0x28是_MACHINE_FRAME大小
        context.P4Home = CurrentProcess - 0xA0; // MachineFrame - EXCEPTION_RECORD_LENGTH;EXCEPTION_RECORD_LENGTH=sizeof(EXCEPTION_RECORD) + STACK_ROUND) & ~STACK_ROUND
        context.Rsp = (_OWORD *)(CurrentProcess - 0xA0);
        pDesBuffer = CurrentProcess - 0xC0;
        user_context = (_BYTE *)(CurrentProcess - 0x590);// context
        user_context_1 = (_BYTE *)(CurrentProcess - 0x590);
        LODWORD(a3[0]) = 0xFFFFFB30;
        size = user.rsp - (CurrentProcess - 0x590);// 计算出一共栈顶提升了多少字节
        HIDWORD(a3[0]) = user.rsp - (CurrentProcess - 0x590);
        a3[1] = 0x4D0FFFFFB30i64;
        LODWORD(v52) = user.rsp_1 - (CurrentProcess - 0xC0);
        HIDWORD(v52) = user.rsp - user.rsp_1;
        if ( (unsigned __int64)(user.rsp - (CurrentProcess - 0x590) - 1) > 0xFFE )
        {
          ProbeForWrite(user_context, size, 0x10u);// 检测内存地址是否正常,可写,并且不越界
          v23 = (_OWORD *)context.P4Home;
          v22 = (_QWORD *)CurrentProcess;
        }
        else
        {
          if ( ((_BYTE)CurrentProcess + 0x70) & 0xF )
            ExRaiseDatatypeMisalignment();
          if ( user_context >= MmUserProbeAddress )
            user_context = MmUserProbeAddress;
          *user_context = *user_context;
          user_context[size - 1] = user_context[size - 1];
          v22 = &MachineFrame->Rip;
          v23 = context.Rsp;
          user_context = user_context_1;
        }
        v22[3] = user.rsp;
        *v22 = Context.rip;
        *v23 = *(_OWORD *)&ExceptionRecord_1->ExceptionCode;
        v23[1] = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;
        v23[2] = *(_OWORD *)ExceptionRecord_1->ExceptionInformation;
        v23[3] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2];
        v23[4] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4];
        v23[5] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6];
        v23[6] = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8];
        v24 = (signed __int64)(v23 + 8);
        *(_OWORD *)(v24 - 16) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10];
        *(_OWORD *)v24 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12];
        *(_QWORD *)(v24 + 16) = ExceptionRecord_1->ExceptionInformation[14];
        v25 = pDesBuffer;
        v35 = RtlpCopyExtendedContext(1, pDesBuffer, (__int64)a3, Contextflag, ContextEx, 0i64);
        *(_OWORD *)v25 = *(_OWORD *)a3;
        *(_QWORD *)(v25 + 16) = v52;
        _disable();
        TrapFrame_1->Rsp = (ULONG64)user_context;// TrapFrame中的RSP设置为新的栈顶
        TrapFrame_1->SegCs = 0x33;              // 新的CS寄存器
        TrapFrame_1->Rip = KeUserExceptionDispatcher;// 返回三环的落脚点,KeUserExceptionDispatcher调用三环的异常处理函数
        LOBYTE(result) = KiSetupForInstrumentationReturn((__int64)TrapFrame_1);
        _enable();
      }

用户层的第一次异常分发到这里就结束了。

下面就是用户层的第二次异常分发。

第二次用户层异常分发先是将SecondChance设置为True,然后调用DbgkForwardException尝试将异常发送给调试器,跟内核调试器一样,应用层调试器一般也都会处理SecondChance为True的异常。

如果DbgkForwardException还是返回False,那么系统会再次调用DbgkForwardException将这个异常发送给异常端口。

如果还是返回False的话,直接调用ZwTerminateProcess结束进程!

else                                          // 用户态第二次分发
  {
    CurrentProcess_2 = CurrentProcess;
    LOBYTE(v16) = 1;                            // SecondChance
    LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 1, v16);// 将SecondChance设置为True再次发送给调试器
    if ( !(_BYTE)result )                       // 如果还是没被处理,判断进程是不是一个Pico进程,如果是的话调用PspPicoProviderRoutines函数
    {
      if ( !*(_QWORD *)(CurrentProcess_2 + 0x6F8)// CurrentProcess.PicoContext
        || (LOBYTE(PreviousMode_2) = PreviousMode_1,
            LOBYTE(result) = PspPicoProviderRoutines_0(
                               ExceptionRecord_1,
                               ExceptionFrame_1,
                               TrapFrame_1,
                               2i64,
                               PreviousMode_2),
            !(_BYTE)result) )
      {
        LOBYTE(v27) = 1;
        LOBYTE(result) = DbgkForwardException(ExceptionRecord_1, 0, v27);// 发送给异常端口
        if ( !(_BYTE)result )
          LOBYTE(result) = ZwTerminateProcess(-1i64, (unsigned int)ExceptionRecord_1->ExceptionCode);
      }
    }
  }
  return result;
}

0x5:DbgkForwardException

在经历KiDispatchException的异常分发后,用户态异常终于来到了DbgkForwardException函数,这个函数主要的任务是将异常结构转换为DBGKM_MSG结构,然后根据不同的参数选择发送给异常端口还是调试端口,废话不多说,直接上代码:

bool __fastcall DbgkForwardException(EXCEPTION_RECORD *ExceptionRecord, char DebugPort, __int64 SecondChance)
{
  char SecondChance_1; // r15
  char DebugPort_1; // di
  EXCEPTION_RECORD *ExceptionRecord_1; // r12
  char UseLpc; // r14
  unsigned __int64 CurrentThrad; // rax
  ULONG_PTR CurrentProcess; // rsi
  void *DebugObject; // rbx
  __int128 v11; // xmm1
  ULONG_PTR v12; // rax
  __int128 v13; // xmm0
  __int128 v14; // xmm1
  __int128 v15; // xmm0
  __int128 v16; // xmm1
  __int128 v17; // xmm0
  __int128 v18; // xmm1
  int result; // esi
  signed int ReturnedStatus; // eax
  __int64 v21; // [rsp+20h] [rbp-E0h]
  DBGKM_MSG ApiMessage; // [rsp+30h] [rbp-D0h]
  SecondChance_1 = SecondChance;
  DebugPort_1 = DebugPort;
  ExceptionRecord_1 = ExceptionRecord;
  UseLpc = 1;
  if ( (_BYTE)SecondChance )
  {
    v21 = 1i64;
    PsSetProcessFaultInformation(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), &v21);// 如果是第二次分发,调用PsSetProcessFaultInformation设置进程故障信息
  }
  ApiMessage.ApiNumber = 0;
  ApiMessage.h.u1.Length = 0xD000A8;
  ApiMessage.h.u2.ZeroInit = 8;
  CurrentThrad = __readgsqword(0x188u);
  CurrentProcess = *(_QWORD *)(CurrentThrad + 0xB8);
  if ( DebugPort_1 )                            // 是否发送给调试端口
  {
    if ( *(_DWORD *)(__readgsqword(0x188u) + 0x6BC) & 4 )// 判断是否设置了CrossThreadFlags.HideFromDebugger,如果设置了HideFromDebugger标志位,则将调试对象清零
      DebugObject = 0i64;
    else
      DebugObject = *(void **)(CurrentProcess + 0x420);// 如果没有设置HideFromDebugger位则获取调试对象
    UseLpc = 0;
  }
  else
  {
    DebugObject = (void *)PsCaptureExceptionPort(*(_QWORD *)(CurrentThrad + 0xB8));// 获取异常端口对象
    ApiMessage.h.u2.ZeroInit = 7;
  }
  if ( !DebugObject && DebugPort_1 )            // 如果DebugObject为空则返回False
    return 0;                                   
  v11 = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;// 开始构建ApiMessage结构
  *(_OWORD *)&ApiMessage.u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&ExceptionRecord_1->ExceptionCode;
  v12 = ExceptionRecord_1->ExceptionInformation[14];
  v13 = *(_OWORD *)ExceptionRecord_1->ExceptionInformation;
  *((_OWORD *)&ApiMessage.u.UnloadDll + 1) = v11;
  v14 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 2) = v13;
  v15 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 3) = v14;
  v16 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 4) = v15;
  v17 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 5) = v16;
  v18 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 6) = v17;
  *((_OWORD *)&ApiMessage.u.UnloadDll + 7) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 8) = v18;
  ApiMessage.u.Exception.ExceptionRecord.ExceptionInformation[14] = v12;
  ApiMessage.u.Exception.FirstChance = SecondChance_1 == 0;
  if ( !UseLpc )
  {
    result = DbgkpSendApiMessage(CurrentProcess, DebugPort_1 != 0, (__int64)&ApiMessage);// 调用DbgkpSendApiMessage发送刚才构建的结构体,如果是发送给调试端口,则挂起进程
    goto LABEL_15;
  }
  if ( DebugObject )
  {                                             // 如果执行到这里,代表是发送给异常端口的
    LOBYTE(SecondChance) = DebugPort_1;
    result = DbgkpSendApiMessageLpc(&ApiMessage, DebugObject, SecondChance);
    ObfDereferenceObject(DebugObject);
LABEL_15:
    ReturnedStatus = ApiMessage.ReturnedStatus; // 获取ApiMessage中的ReturnStatus,判断异常是否处理。
    goto LABEL_16;
  }
  ReturnedStatus = 0x80010001;
  result = 0;
  ApiMessage.ReturnedStatus = 0x80010001;
LABEL_16:
  if ( result < 0 )                             // 返回值是NTSTATUS类型,如果小于0则代表失败,直接返回0代表异常未处理。
    return 0;
  if ( ReturnedStatus == 0x80010001 )           // 如果返回值不小于零,代表函数没有出错,这种情况下通过ApiMessage的ReturnStatus判断异常是否被处理,如果等于0x80010001代表异常没被处理。
  {
    if ( !DebugPort_1 )                         // 如果Debugport为0,代表这是要发送给异常端口的异常,对于这种异常,调用DbgkpSendErrorMessage发送错误消息。
    {
      ReturnedStatus = DbgkpSendErrorMessage(ExceptionRecord_1, 2i64, &ApiMessage);
      return ReturnedStatus >= 0;
    }
    return 0;
  }                                             
  return ReturnedStatus >= 0;          // 如果DbgkpSendApiMessage返回值正常,并且ApiMessage.ReturnStatus的值也不等于0x80010001,这代表异常被成功处理,直接返回True.
}

函数首先显示判断了是否是第二次分发,如果是第二次分发则设置进程故障信息,毕竟如果这次异常还是没被处理的话进程就直接被干掉了~

  if ( (_BYTE)SecondChance )
  {
    v21 = 1i64;
    PsSetProcessFaultInformation(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), &v21);// 如果是第二次分发,调用PsSetProcessFaultInformation设置进程故障信息
  }

接下来就开始简单的初始化DBGKM_MSG结构了,然后根据是是发送给异常端口还是调试端口,开始获取对应的对象。

如果是要发送给调试端口,则首先要检测一下CurrentThread.CrossThreadFlags.HideFromDebugger标志位是否被置位,如果这个标志被置位则直接将存储调试对象的变量置为零。这个标志位可以让这个线程的异常不走调试器,走正常的异常分发,这也是一个反调试的手段。

这个标志位可以调用ZwSetInformationThread函数来设置,这个函数是windows未公开的一个函数,在NTDLL模块导出,可以使用GetProcAddress来获取,这里不详细说明,有兴趣的可以去查资料。

如果HideFromDebugger没被置零,则通过进程结构体获取调试对象(CurrentProcess.DebugPort).

如果不是发送给调试端口的,则调用PsCaptureExceptionPort获取异常端口对象。

 ApiMessage.ApiNumber = 0;
  ApiMessage.h.u1.Length = 0xD000A8;
  ApiMessage.h.u2.ZeroInit = 8;
  CurrentThrad = __readgsqword(0x188u);
  CurrentProcess = *(_QWORD *)(CurrentThrad + 0xB8);
  if ( DebugPort_1 )                            // 是否发送给调试端口
  {
    if ( *(_DWORD *)(__readgsqword(0x188u) + 0x6BC) & 4 )// 判断是否设置了CrossThreadFlags.HideFromDebugger,如果设置了HideFromDebugger标志位,则将调试对象清零
      DebugObject = 0i64;
    else
      DebugObject = *(void **)(CurrentProcess + 0x420);// 如果没有设置HideFromDebugger位则获取调试对象
    UseLpc = 0;
  }
  else
  {
    DebugObject = (void *)PsCaptureExceptionPort(*(_QWORD *)(CurrentThrad + 0xB8));// 获取异常端口对象
    ApiMessage.h.u2.ZeroInit = 7;
  }</span>
然后开始判断调试对象是否获取成功,如果调试对象为零,并且参数是要发送给调试对象,则直接返回False.

然后开始根据ExceptionRecord开始构建DBGKM_MSG结构体。

<span style="font-family:Courier New,Courier,monospace">if ( !DebugObject && DebugPort_1 )            // 如果DebugObject为空则返回False
    return 0;                                  
  v11 = *(_OWORD *)&ExceptionRecord_1->ExceptionAddress;// 开始构建ApiMessage结构
  *(_OWORD *)&ApiMessage.u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&ExceptionRecord_1->ExceptionCode;
  v12 = ExceptionRecord_1->ExceptionInformation[14];
  v13 = *(_OWORD *)ExceptionRecord_1->ExceptionInformation;
  *((_OWORD *)&ApiMessage.u.UnloadDll + 1) = v11;
  v14 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[2];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 2) = v13;
  v15 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[4];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 3) = v14;
  v16 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[6];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 4) = v15;
  v17 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[8];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 5) = v16;
  v18 = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[12];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 6) = v17;
  *((_OWORD *)&ApiMessage.u.UnloadDll + 7) = *(_OWORD *)&ExceptionRecord_1->ExceptionInformation[10];
  *((_OWORD *)&ApiMessage.u.UnloadDll + 8) = v18;
  ApiMessage.u.Exception.ExceptionRecord.ExceptionInformation[14] = v12;
  ApiMessage.u.Exception.FirstChance = SecondChance_1 == 0;

然后就开始调用函数发送刚才构建的结构体了。

这里判断是否使用LPC,这个UseLPC变量是一个内部变量,初始化赋值为True,在获取调试对象时候赋值为False。

如果不使用LPC则调用DbgkpSendApiMessage函数发送结构体,函数中参数并没有调试对象,需要在函数内部获取。函数的第二个参数代表是否挂起进程。

如果不是发送给调试端口则判断DebugObject是否为零,这个变量中存储的不一定是调试对象,如果是发送给异常端口的话这个变量中存储的就是异常端口对象。

函数返回后首先判断函数返回值是否正常,如果函数返回值不正常则直接返回False.如果函数返回值正常,则判断ApiMessage.ReturnedStatus是否等于0x80010001,这个成员代表异常是否被调试器处理,如果等于0x80010001代表调试器未处理。

如果调试器 未处理异常,判断是否是发送给异常端口的,如果发送给异常端口则调用DbgkpSendErrorMessage发送错误消息,如果DbgkpSendErrorMessage返回值大于等于0,则返回True,否则返回False.

if ( !UseLpc )
  {
    result = DbgkpSendApiMessage(CurrentProcess, DebugPort_1 != 0, (__int64)&ApiMessage);// 调用DbgkpSendApiMessage发送刚才构建的结构体,如果是发送给调试端口,则挂起进程
    goto LABEL_15;
  }
  if ( DebugObject )
  {                                             // 如果执行到这里,代表是发送给异常端口的
    LOBYTE(SecondChance) = DebugPort_1;
    result = DbgkpSendApiMessageLpc(&ApiMessage, DebugObject, SecondChance);
    ObfDereferenceObject(DebugObject);
LABEL_15:
    ReturnedStatus = ApiMessage.ReturnedStatus; // 获取ApiMessage中的ReturnStatus,判断异常是否处理。
    goto LABEL_16;
  }
  ReturnedStatus = 0x80010001;
  result = 0;
  ApiMessage.ReturnedStatus = 0x80010001;
LABEL_16:
  if ( result < 0 )                             // 返回值是NTSTATUS类型,如果小于0则代表失败,直接返回0代表异常未处理。
    return 0;
  if ( ReturnedStatus == 0x80010001 )           // 如果返回值不小于零,代表函数没有出错,这种情况下通过ApiMessage的ReturnStatus判断异常是否被处理,如果等于0x80010001代表异常没被处理。
  {
    if ( !DebugPort_1 )                         // 如果Debugport为0,代表这是要发送给异常端口的异常,对于这种异常,调用DbgkpSendErrorMessage发送错误消息。
    {
      ReturnedStatus = DbgkpSendErrorMessage(ExceptionRecord_1, 2i64, &ApiMessage);
      return ReturnedStatus >= 0;
    }
    return 0;
  }                                             
  return ReturnedStatus >= 0;          // 如果DbgkpSendApiMessage返回值正常,并且ApiMessage.ReturnStatus的值也不等于0x80010001,这代表异常被成功处理,直接返回True.
}

0x6:DbgkpSendApiMessage

DbgkForwardException函数根据异常结构体构造了一个DBGKM_MSG结构体后,通过DbgkpSendApiMessage函数来发送调试事件。

函数首先判断PerfGlobalGroupMask是否开启了调试事件追踪,如果是就调用EtwTraceDebuggerEvent。

判断如果当前进程是指定进程,并且指定要挂起进程,则调用DbgkpSuspendProcess挂起进程,然后调用DbgkpQueueMessage将调试消息插入队列。

然后调用ZwFlushInstructionCache刷新指令缓存,这个函数的意思不太了解,百度说是刷新指令缓存。

然后恢复进程运行,判断DbgkpQueueMessage返回值和ApiMessage返回值,如果都正常就返回了,不正常在循环调用一次。

这个函数的工作主要就是挂起进程,主要的工作都是调用DbgkpQueueMessage来做的。

__int64 __fastcall DbgkpSendApiMessage(ULONG_PTR CurrentProcess, char SuspendProcess, DBGKM_MSG *ApiMessage)
{
  DBGKM_MSG *ApiMessage_1; // r15
  char SuspendProcess_1; // bp
  ULONG_PTR CurrentProcess_1; // rbx
  int v6; // er14
  unsigned int v7; // esi
  ApiMessage_1 = ApiMessage;
  SuspendProcess_1 = SuspendProcess;
  CurrentProcess_1 = CurrentProcess;
  if ( PerfGlobalGroupMask & 0x400000 )
    EtwTraceDebuggerEvent(*(_QWORD *)(__readgsqword(0x188u) + 0xB8), __readgsqword(0x188u), 1);
  do
  {
    v6 = 0;
    if ( CurrentProcess_1 == *(_QWORD *)(__readgsqword(0x188u) + 0xB8) && SuspendProcess_1 & 1 )// 如果参数指定的进程等于当前进程,并且设置挂起进程,则调用DbgkpSuspendProcess挂起进程。
      v6 = (unsigned __int8)DbgkpSuspendProcess(CurrentProcess_1);
    ApiMessage_1->ReturnedStatus = 0x103;
    v7 = DbgkpQueueMessage(
           CurrentProcess_1,
           __readgsqword(0x188u),
           ApiMessage_1,
           (SuspendProcess_1 & 2) != 0 ? 0x40 : 0,
           0i64);
    ZwFlushInstructionCache();                  // 刷新指令缓存
    if ( v6 )                                   // 根据是否冻结进程判定是否解冻进程
    {
      PsThawProcess(CurrentProcess_1, 0i64);
      KeLeaveCriticalRegion();
    }
  }
  while ( (v7 & 0x80000000) == 0 && ApiMessage_1->ReturnedStatus == 0x40010001 );// 如果返回值没出错,并且ApiMessage的返回状态为0x40010001,则创新调用DbgkpQueueMessage函数
  return v7;
}

0x7:DbgkpQueueMessage

这个函数的主要任务就是根据DBGKM_MSG构建一个调试事件,并且将这个事件挂入事件链表中,这也是用户态调试的最后一个函数。

__int64 __usercall DbgkpQueueMessage@<rax>(ULONG_PTR CurrentProcess@<rcx>, ULONG_PTR CurrentThrad@<rdx>, DBGKM_MSG *ApiMessage@<r8>, int Flags@<r9d>, PRKEVENT a5)
{
  _DEBUG_OBJECT *DebugObject; // rbx
  signed __int64 v6; // r12
  int Flags_1; // esi
  DBGKM_MSG *ApiMessage_1; // rbp
  ULONG_PTR CurrentThrad_1; // r15
  ULONG_PTR CurrentProcess_1; // r13
  _DEBUG_EVENT *DebugEvent; // rax
  _DEBUG_EVENT *DebugEvent_1; // r14
  DBGKM_APINUMBER ApiNumber; // ecx
  DBGKM_MSG *v15; // rsi
  DBGKM_MSG *v16; // rax
  DBGKM_MSG *v17; // rcx
  signed __int64 v18; // rdx
  __int128 v19; // xmm1
  signed int result; // ebx
  struct _FAST_MUTEX *debug_Mutex; // r12
  _LIST_ENTRY *v22; // rcx
  __int128 v23; // xmm1
  int NOWAIT; // [rsp+30h] [rbp-1C8h]
  _DEBUG_EVENT v25; // [rsp+40h] [rbp-1B8h]
  DebugObject = (_DEBUG_OBJECT *)a5;
  v6 = 2i64;
  Flags_1 = Flags;
  ApiMessage_1 = ApiMessage;
  NOWAIT = Flags & 2;                           // NOWAIT
  CurrentThrad_1 = CurrentThrad;
  CurrentProcess_1 = CurrentProcess;
  if ( Flags & 2 )                              // DEBUG_EVENT_NOWAIT,检查Flag是否设置了NOWAIT标志,如果没有设置,函数需要等待调试器回复,如果设置了这个标志,则不需要等待调试器
  {
    DebugEvent = (_DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)0x208, 0x168ui64, 0x45676244u);// 如果不需要等待调试器回复,则不能使用栈中的内存构建DEBUG_EVENT结构体,需要分配堆内存
    DebugEvent_1 = DebugEvent;
    if ( !DebugEvent )
      return 0xC000009Ai64;
    DebugEvent->Flags = Flags_1 | 4;
    ObfReferenceObjectWithTag(CurrentProcess_1, 1332175428i64);
    ObfReferenceObjectWithTag(CurrentThrad_1, 1332175428i64);
    DebugEvent_1->BackoutThread = (PETHREAD)__readgsqword(0x188u);
  }
  else
  {                                             //  等待调试器回复
    v25.Flags = Flags;
    DebugEvent_1 = &v25;
    ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
    ApiNumber = ApiMessage_1->ApiNumber;
    DebugObject = *(_DEBUG_OBJECT **)(CurrentProcess_1 + 0x420);
    if ( (unsigned int)(ApiNumber - 1) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是线程进程创建,并且线程设置了SkipCreationMsg,则将DebugObject设置为零
      DebugObject = 0i64;
    if ( ApiNumber == 5 && (unsigned __int8)Flags_1 & *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是模块加载,并且线程设置了SkipCreationMsg,则将DebugObject设置为零
      DebugObject = 0i64;
    if ( (unsigned int)(ApiNumber - 3) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) < 0 )// 判断这个事件如果是线程进程退出,并且线程设置了SkipTerminationMsg,则将DebugObject设置为零,这里F5有错误,实际上后面会&0x80
      DebugObject = 0i64;
    KeInitializeEvent(&v25.ContinueEvent, SynchronizationEvent, 0);// 初始化一个同步对象,为了等待调试器回复
  }
  v15 = &DebugEvent_1->ApiMsg;
  DebugEvent_1->Process = (PEPROCESS)CurrentProcess_1;
  v16 = &DebugEvent_1->ApiMsg;
  DebugEvent_1->Thread = (PETHREAD)CurrentThrad_1;
  v17 = ApiMessage_1;
  v18 = 2i64;
  do
  {
    *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength;
    *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v16->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v17->h.8 + 8);
    *(_OWORD *)&v16->h.ClientViewSize = *(_OWORD *)&v17->h.ClientViewSize;
    *(_OWORD *)&v16->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v17->u.Exception.ExceptionRecord.ExceptionCode;
    *((_OWORD *)&v16->u.UnloadDll + 1) = *((_OWORD *)&v17->u.UnloadDll + 1);
    *((_OWORD *)&v16->u.UnloadDll + 2) = *((_OWORD *)&v17->u.UnloadDll + 2);
    *((_OWORD *)&v16->u.UnloadDll + 3) = *((_OWORD *)&v17->u.UnloadDll + 3);
    v16 = (DBGKM_MSG *)((char *)v16 + 128);
    v19 = *((_OWORD *)&v17->u.UnloadDll + 4);
    v17 = (DBGKM_MSG *)((char *)v17 + 128);
    *((_OWORD *)&v16[-1].u.UnloadDll + 9) = v19;
    --v18;
  }
  while ( v18 );
  *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength;
  _mm_storeu_si128((__m128i *)&DebugEvent_1->ClientId, *(__m128i *)(CurrentThrad_1 + 0x628));
  if ( DebugObject )                            // 判断获取调试对象是否正常
  {
    debug_Mutex = &DebugObject->Mutex;
    ExAcquireFastMutex(&DebugObject->Mutex);    // 获取调试对象的互斥体
    if ( DebugObject->Flags & 1 )               // 判断调试器是否处于活跃状态,如果不是则返回c0000354
    {
      result = 0xC0000354;
    }
    else
    {
      v22 = DebugObject->EventList.Blink;       // 将调试事件挂入到调试对象的事件链表中
      DebugEvent_1->EventList.Flink = &DebugObject->EventList;
      DebugEvent_1->EventList.Blink = v22;
      if ( v22->Flink != &DebugObject->EventList )
        __fastfail(3u);
      v22->Flink = &DebugEvent_1->EventList;
      DebugObject->EventList.Blink = &DebugEvent_1->EventList;
      if ( !NOWAIT )
        KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 如果不是NOWAIT状态,触发EventsPresent这个事件,通知调试器来处理调试事件。
      result = 0;
    }
    KeReleaseGuardedMutex(debug_Mutex);         // 释放调试对象的互斥体
    v6 = 2i64;
  }
  else
  {                                             // 如果调试对象获取失败,则返回c00000353
    result = 0xC0000353;
  }
  if ( NOWAIT )
  {
    if ( result < 0 )                           // 如果返回值出错,则释放引用的进程线程对象,释放分配的DebugEvent内存
    {
      ObfDereferenceObjectWithTag(CurrentProcess_1);
      ObfDereferenceObjectWithTag(CurrentThrad_1);
      ExFreePoolWithTag(DebugEvent_1, 0);
    }
  }
  else
  {
    KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);// 释放DbgkpProcessDebugPortMutex互斥锁
    if ( result >= 0 )
    {
      KeWaitForSingleObject(&DebugEvent_1->ContinueEvent, 0, 0, 0, 0i64);// 等待调试事件的ContinueEvent,当事件被调试器处理完成的时候这个对象会被触发,当前线程也会被唤醒。
      result = DebugEvent_1->Status;
      do                                        // 通过调试事件的状态来设置ApiMessage,因为上一个函数需要通过ApiMessage的返回值决定下一步的行为。
      {
        *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength;
        *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&ApiMessage_1->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v15->h.8 + 8);
        *(_OWORD *)&ApiMessage_1->h.ClientViewSize = *(_OWORD *)&v15->h.ClientViewSize;
        *(_OWORD *)&ApiMessage_1->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v15->u.Exception.ExceptionRecord.ExceptionCode;
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 1) = *((_OWORD *)&v15->u.UnloadDll + 1);
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 2) = *((_OWORD *)&v15->u.UnloadDll + 2);
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 3) = *((_OWORD *)&v15->u.UnloadDll + 3);
        ApiMessage_1 = (DBGKM_MSG *)((char *)ApiMessage_1 + 128);
        v23 = *((_OWORD *)&v15->u.UnloadDll + 4);
        v15 = (DBGKM_MSG *)((char *)v15 + 128);
        *((_OWORD *)&ApiMessage_1[-1].u.UnloadDll + 9) = v23;
        --v6;
      }
      while ( v6 );
      *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength;
    }
  }
  return (unsigned int)result;
}

首先判断了参数中Flags是否指定了NOWAIT标志,如果指定了NOWAIT标志代表不需要等待调试器处理完直接返回。

如果指定了NOWAIT标志,就调用ExAllocatePoolWithQuotaTag分配DebugEvent的内存,因为如果不等待直接返回就不能用栈内存。

分配完内存后检查是否分配成功,如果未分配成功则直接返回。

if ( Flags & 2 )                              // DEBUG_EVENT_NOWAIT,检查Flag是否设置了NOWAIT标志,如果没有设置,函数需要等待调试器回复,如果设置了这个标志,则不需要等待调试器
  {
    DebugEvent = (_DEBUG_EVENT *)ExAllocatePoolWithQuotaTag((POOL_TYPE)0x208, 0x168ui64, 0x45676244u);// 如果不需要等待调试器回复,则不能使用栈中的内存构建DEBUG_EVENT结构体,需要分配堆内存
    DebugEvent_1 = DebugEvent;
    if ( !DebugEvent )
      return 0xC000009Ai64;
    DebugEvent->Flags = Flags_1 | 4;
    ObfReferenceObjectWithTag(CurrentProcess_1, 1332175428i64);
    ObfReferenceObjectWithTag(CurrentThrad_1, 1332175428i64);
    DebugEvent_1->BackoutThread = (PETHREAD)__readgsqword(0x188u);
  }

如果没有指定NOWAIT标志,则首先获取DbgkpProcessDebugPortMutex,这是一个全局互斥锁,来保证将调试事件挂入链表的时候不会发生多线程问题。

然后通过进程结构体获取调试对象,并且判断这个事件是否被设置了跳过,如果是的话则将调试对象设置为零。

经历这些判断后,调用KeInitializeEvent初始化一个事件,用于等待调试器回复用。

 else
  {                                             //  等待调试器回复
    v25.Flags = Flags;
    DebugEvent_1 = &v25;
    ExAcquireFastMutex(&DbgkpProcessDebugPortMutex);
    ApiNumber = ApiMessage_1->ApiNumber;
    DebugObject = *(_DEBUG_OBJECT **)(CurrentProcess_1 + 0x420);
    if ( (unsigned int)(ApiNumber - 1) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是线程进程创建,并且线程设置了SkipCreationMsg,则将DebugObject设置为零
      DebugObject = 0i64;
    if ( ApiNumber == 5 && (unsigned __int8)Flags_1 & *(_BYTE *)(CurrentThrad_1 + 0x6BC) & 0x40 )// 判断这个事件如果是模块加载,并且线程设置了SkipCreationMsg,则将DebugObject设置为零
      DebugObject = 0i64;
    if ( (unsigned int)(ApiNumber - 3) <= 1 && *(_BYTE *)(CurrentThrad_1 + 0x6BC) < 0 )// 判断这个事件如果是线程进程退出,并且线程设置了SkipTerminationMsg,则将DebugObject设置为零,这里F5有错误,实际上后面会&0x80
      DebugObject = 0i64;
    KeInitializeEvent(&v25.ContinueEvent, SynchronizationEvent, 0);// 初始化一个同步对象,为了等待调试器回复
  }

然后就开始通过ApiMessage来构造DebugEvent结构体。

构建完结构体之后,检查调试对象是否不为零,如果为零直接返回错误,如果调试对象正常,则检查调试对象是否是活跃的。

然后获取调试对象的互斥锁,并且将调试事件挂入到调试对象的事件列表中。(其实这里有一点疑惑,如果只是为了在挂入链表时保证多线程安全的话,获取一个调试对象的互斥锁就可以了,为什么还要获取DbgkpProcessDebugPortMutex)

然后把调试事件挂入到事件链表中,挂入完成后,如果没指定NOWAIT标志,则调用KeSetEvent设置DebugObject->EventsPresent事件,用来通知调试器处理调试事件,然后释放调试对象的互斥锁。

  v15 = &DebugEvent_1->ApiMsg;
  DebugEvent_1->Process = (PEPROCESS)CurrentProcess_1;
  v16 = &DebugEvent_1->ApiMsg;
  DebugEvent_1->Thread = (PETHREAD)CurrentThrad_1;
  v17 = ApiMessage_1;
  v18 = 2i64;
  do
  {
    *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength;
    *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v16->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v17->h.8 + 8);
    *(_OWORD *)&v16->h.ClientViewSize = *(_OWORD *)&v17->h.ClientViewSize;
    *(_OWORD *)&v16->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v17->u.Exception.ExceptionRecord.ExceptionCode;
    *((_OWORD *)&v16->u.UnloadDll + 1) = *((_OWORD *)&v17->u.UnloadDll + 1);
    *((_OWORD *)&v16->u.UnloadDll + 2) = *((_OWORD *)&v17->u.UnloadDll + 2);
    *((_OWORD *)&v16->u.UnloadDll + 3) = *((_OWORD *)&v17->u.UnloadDll + 3);
    v16 = (DBGKM_MSG *)((char *)v16 + 128);
    v19 = *((_OWORD *)&v17->u.UnloadDll + 4);
    v17 = (DBGKM_MSG *)((char *)v17 + 128);
    *((_OWORD *)&v16[-1].u.UnloadDll + 9) = v19;
    --v18;
  }
  while ( v18 );
  *(_OWORD *)&v16->h.u1.s1.DataLength = *(_OWORD *)&v17->h.u1.s1.DataLength;
  _mm_storeu_si128((__m128i *)&DebugEvent_1->ClientId, *(__m128i *)(CurrentThrad_1 + 0x628));
  if ( DebugObject )                            // 判断获取调试对象是否正常
  {
    debug_Mutex = &DebugObject->Mutex;
    ExAcquireFastMutex(&DebugObject->Mutex);    // 获取调试对象的互斥体
    if ( DebugObject->Flags & 1 )               // 判断调试器是否处于活跃状态,如果不是则返回c0000354
    {
      result = 0xC0000354;
    }
    else
    {
      v22 = DebugObject->EventList.Blink;       // 将调试事件挂入到调试对象的事件链表中
      DebugEvent_1->EventList.Flink = &DebugObject->EventList;
      DebugEvent_1->EventList.Blink = v22;
      if ( v22->Flink != &DebugObject->EventList )
        __fastfail(3u);
      v22->Flink = &DebugEvent_1->EventList;
      DebugObject->EventList.Blink = &DebugEvent_1->EventList;
      if ( !NOWAIT )
        KeSetEvent(&DebugObject->EventsPresent, 0, 0);// 如果不是NOWAIT状态,触发EventsPresent这个事件,通知调试器来处理调试事件。
      result = 0;
    }
    KeReleaseGuardedMutex(debug_Mutex);         // 释放调试对象的互斥体
    v6 = 2i64;
  }
  else
  {                                             // 如果调试对象获取失败,则返回c00000353
    result = 0xC0000353;
  }

继续判断,如果指定了NOWAIT标志,并且返回值小于零,也就是代表出现错误,则释放申请的内存,并且将引用的对象解引用。

如果没指定NOWAIT标志,则释放DbgkpProcessDebugPortMutex全局互斥锁,然后调用KeWaitForSingleObject等待DebugEvent_1->ContinueEvent,当事件被调试器处理完成后会触发这个事件,当前线程会被唤醒继续执行。

然后通过调试事件的信息来填充ApiMessage,用于通知上一层函数事件处理的结果。

  if ( NOWAIT )
  {
    if ( result < 0 )                           // 如果返回值出错,则释放引用的进程线程对象,释放分配的DebugEvent内存
    {
      ObfDereferenceObjectWithTag(CurrentProcess_1);
      ObfDereferenceObjectWithTag(CurrentThrad_1);
      ExFreePoolWithTag(DebugEvent_1, 0);
    }
  }
  else
  {
    KeReleaseGuardedMutex(&DbgkpProcessDebugPortMutex);// 释放DbgkpProcessDebugPortMutex互斥锁
    if ( result >= 0 )
    {
      KeWaitForSingleObject(&DebugEvent_1->ContinueEvent, 0, 0, 0, 0i64);// 等待调试事件的ContinueEvent,当事件被调试器处理完成的时候这个对象会被触发,当前线程也会被唤醒。
      result = DebugEvent_1->Status;
      do                                        // 通过调试事件的状态来设置ApiMessage,因为上一个函数需要通过ApiMessage的返回值决定下一步的行为。
      {
        *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength;
        *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&ApiMessage_1->h.8 + 8) = *(union _PORT_MESSAGE::$C6BC508B531A81D74C33985719C23F49 *)((char *)&v15->h.8 + 8);
        *(_OWORD *)&ApiMessage_1->h.ClientViewSize = *(_OWORD *)&v15->h.ClientViewSize;
        *(_OWORD *)&ApiMessage_1->u.Exception.ExceptionRecord.ExceptionCode = *(_OWORD *)&v15->u.Exception.ExceptionRecord.ExceptionCode;
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 1) = *((_OWORD *)&v15->u.UnloadDll + 1);
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 2) = *((_OWORD *)&v15->u.UnloadDll + 2);
        *((_OWORD *)&ApiMessage_1->u.UnloadDll + 3) = *((_OWORD *)&v15->u.UnloadDll + 3);
        ApiMessage_1 = (DBGKM_MSG *)((char *)ApiMessage_1 + 128);
        v23 = *((_OWORD *)&v15->u.UnloadDll + 4);
        v15 = (DBGKM_MSG *)((char *)v15 + 128);
        *((_OWORD *)&ApiMessage_1[-1].u.UnloadDll + 9) = v23;
        --v6;
      }
      while ( v6 );
      *(_OWORD *)&ApiMessage_1->h.u1.s1.DataLength = *(_OWORD *)&v15->h.u1.s1.DataLength;
    }
  }
  return (unsigned int)result;

以上,就是用户态调试流程的全部内容,能力有限,有些地方分析的不是很细致~