win内核调试原理揭秘3

Posted by Qmeimei10086 on July 12, 2026

我们来看WaitForDebugEvent和ContinueDebugEvent

WaitForDebugEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
BOOL __stdcall WaitForDebugEvent(LPDEBUG_EVENT lpDebugEvent, DWORD dwMilliseconds)
{
  return WaitForDebugEventWorker(lpDebugEvent);
}


// r3的DEBUG_EVENT
// 
// typedef struct _DEBUG_EVENT {
//     DWORD dwDebugEventCode;  // 调试事件类型
//     DWORD dwProcessId;       // 发生事件的进程ID
//     DWORD dwThreadId;        // 发生事件的线程ID
//     union {
//         EXCEPTION_DEBUG_INFO Exception;
//         CREATE_THREAD_DEBUG_INFO CreateThread;
//         CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
//         EXIT_THREAD_DEBUG_INFO ExitThread;
//         EXIT_PROCESS_DEBUG_INFO ExitProcess;
//         LOAD_DLL_DEBUG_INFO LoadDll;
//         UNLOAD_DLL_DEBUG_INFO UnloadDll;
//         OUTPUT_DEBUG_STRING_INFO DebugString;
//         RIP_INFO RipInfo;
//     } u;
// } DEBUG_EVENT, *LPDEBUG_EVENT;
// 
__int64 __fastcall WaitForDebugEventWorker(LPDEBUG_EVENT DebugEvent, DWORD WaitTime, char ZeroBit)
{
  union _LARGE_INTEGER *time; // rsi
  NTSTATUS status; // eax
  DWORD dwDebugEventCode; // eax
  _BYTE *i; // rcx
  HANDLE hThread; // r8
  _BYTE WaitTime_1[16]; // [rsp+20h] [rbp-E8h] BYREF
  struct _DBGUI_WAIT_STATE_CHANGE DbgUiWaitStateCange; // [rsp+30h] [rbp-D8h] BYREF

  time = (union _LARGE_INTEGER *)BaseFormatTimeOut(WaitTime_1);
  do
  {
    do
      status = DbgUiWaitStateChange(&DbgUiWaitStateCange, time);
    while ( status == 0x101 );                  // 00000101    STATUS_ALERTED
  }
  while ( status == 0xC0 );                     // 000000C0    STATUS_USER_APC
  if ( status < 0 )
    goto Failed;
  if ( status == 0x102 )                        // 00000102    STATUS_TIMEOUT
  {
    RtlSetLastWin32Error(0x79u);
    return 0;
  }
  status = ZeroBit
         ? DbgUiConvertStateChangeStructureEx(&DbgUiWaitStateCange, DebugEvent)
         : DbgUiConvertStateChangeStructure(&DbgUiWaitStateCange, DebugEvent);
  if ( status < 0 )
  {
Failed:
    BaseSetLastNTError((unsigned int)status);
    return 0;
  }
  dwDebugEventCode = DebugEvent->dwDebugEventCode;
  if ( DebugEvent->dwDebugEventCode != 1 )
  {
    if ( dwDebugEventCode == 2 )
    {
      hThread = DebugEvent->u.CreateThread.hThread;
    }
    else
    {
      if ( dwDebugEventCode != 3 )
      {
        if ( dwDebugEventCode == 4 )
        {
          MarkThreadHandle(DebugEvent->dwThreadId);
        }
        else if ( dwDebugEventCode == 5 )
        {
          MarkThreadHandle(DebugEvent->dwThreadId);
          for ( i = NtCurrentTeb()->DbgSsReserved[0]; i; i = *(_BYTE **)i )
          {
            if ( *((_DWORD *)i + 6) == DebugEvent->dwProcessId && !*((_DWORD *)i + 7) )
            {
              i[32] = 1;
              return 1;
            }
          }
        }
        else if ( dwDebugEventCode != 6 && dwDebugEventCode != 7 && (dwDebugEventCode <= 7 || dwDebugEventCode > 9) )
        {
          return 0;
        }
        return 1;
      }
      SaveProcessHandle(DebugEvent->dwProcessId, DebugEvent->u.Exception.ExceptionRecord.ExceptionRecord);
      hThread = DebugEvent->u.Exception.ExceptionRecord.ExceptionAddress;
    }
    SaveThreadHandle(DebugEvent->dwProcessId, DebugEvent->dwThreadId, hThread);
  }
  return 1;
}

我们回忆一下r3也有一个debugevent,两个是不一样的,你总不能和r0一样一堆r0的地址吧
代码不难,关键点是

1
2
3
status = DbgUiWaitStateChange(&DbgUiWaitStateCange, time);
...
DbgUiConvertStateChangeStructureEx(&DbgUiWaitStateCange, DebugEvent)

死等,然后拿到了个DbgUiWaitStateCange,然后转换为r3的DebugEvent,最后返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
__int64 __fastcall DbgUiWaitStateChange(__int64 a1, __int64 a2)
{
  __int64 v2; // r8

  v2 = a2;
  LOBYTE(a2) = 1;
  return ZwWaitForDebugEvent(NtCurrentTeb()->DbgSsReserved[1], a2, v2, a1);
}

// from ntoskrnl
NTSTATUS NtWaitForDebugEvent(
        HANDLE DebugObjectHandle,
        BOOLEAN Alertable,
        PLARGE_INTEGER Timeout,
        PDBGUI_WAIT_STATE_CHANGE WaitStateChange)
{
  char is_got_event; // r14
  KPROCESSOR_MODE IsPreviousMode; // r15
  __int64 v9; // rcx
  NTSTATUS status; // eax
  BOOLEAN v11; // r9
  PDEBUG_OBJECT DebugObject; // rdi
  struct _DEBUG_EVENT *p_EventListHead; // rdx
  PDEBUG_EVENT entry; // rax
  PDEBUG_EVENT debug_event; // rbx
  ULONG Flags; // r8d
  PDEBUG_EVENT Flink; // rcx
  int status1; // ebx
  bool v19; // sf
  unsigned __int64 *v20; // rsi
  LONGLONG QuadPart; // [rsp+38h] [rbp-150h] BYREF
  PLARGE_INTEGER Timeouta; // [rsp+40h] [rbp-148h]
  PVOID Object; // [rsp+48h] [rbp-140h] BYREF
  __int64 v25; // [rsp+50h] [rbp-138h]
  PVOID Process; // [rsp+58h] [rbp-130h]
  PVOID Thread; // [rsp+60h] [rbp-128h]
  _OWORD v28[12]; // [rsp+80h] [rbp-108h] BYREF

  Timeouta = Timeout;
  is_got_event = 0;
  QuadPart = 0;
  v25 = 0;
  IsPreviousMode = KeGetCurrentThread()->PreviousMode;
  memset(v28, 0, 0xB8u);
  if ( Timeouta )
  {
    QuadPart = Timeouta->QuadPart;
    Timeouta = (PLARGE_INTEGER)&QuadPart;
    v25 = MEMORY[0xFFFFF78000000014];
  }
  if ( IsPreviousMode )
  {
    v9 = (__int64)WaitStateChange;
    if ( (unsigned __int64)WaitStateChange >= 0x7FFFFFFF0000LL )
      v9 = 0x7FFFFFFF0000LL;                    // 判断越界
    *(_BYTE *)v9 = *(_BYTE *)v9;
    *(_BYTE *)(v9 + 183) = *(_BYTE *)(v9 + 183);
  }
  Object = 0;
  status = ObReferenceObjectByHandle(DebugObjectHandle, 1u, DbgkDebugObjectType, IsPreviousMode, &Object, 0);
  if ( status >= 0 )
  {
    Process = 0;
    Thread = 0;
    v11 = Alertable;
    DebugObject = (PDEBUG_OBJECT)Object;
    while ( 1 )
    {
      status1 = KeWaitForSingleObject(DebugObject, Executive, IsPreviousMode, v11, Timeouta);
      if ( status1 < 0 || status1 == 0xC0 || (unsigned int)(status1 - 0x101) <= 1 )
        break;
      ExAcquireFastMutex(&DebugObject->Mutex);
      if ( (DebugObject->Flags & 1) != 0 )      // #define DEBUG_OBJECT_DELETE_PENDING (0x1) // Debug object is delete pending.
                                                // #define DEBUG_OBJECT_KILL_ON_CLOSE  (0x2) // Kill all debugged processes on close
      {
        status1 = 0xC0000354;                   //     STATUS_DEBUGGER_INACTIVE
      }
      else
      {
        p_EventListHead = (struct _DEBUG_EVENT *)&DebugObject->EventList;
        for ( entry = (PDEBUG_EVENT)DebugObject->EventList.Flink; ; entry = (PDEBUG_EVENT)entry->EventList.Flink )// 遍历debug_event
        {
          if ( entry == p_EventListHead )
          {
            KeResetEvent(&DebugObject->EventsPresent);
            goto LABEL_24;
          }
          debug_event = entry;
          Flags = entry->Flags;
          if ( (Flags & 5) == 0 )               // #define DEBUG_EVENT_READ            (0x01)  // Event had been seen by win32 app
                                                // #define DEBUG_EVENT_NOWAIT          (0x02)  // No waiter one this. Just free the pool
                                                // #define DEBUG_EVENT_INACTIVE        (0x04)  // The message is in inactive. It may be activated or deleted later
                                                // #define DEBUG_EVENT_RELEASE         (0x08)  // Release rundown protection on this thread
                                                // #define DEBUG_EVENT_PROTECT_FAILED  (0x10)  // Rundown protection failed to be acquired on this thread
                                                // #define DEBUG_EVENT_SUSPEND         (0x20)  // Resume thread on continue
                                                // 
                                                // 5 = 1 + 4
          {
            is_got_event = 1;
            Flink = (PDEBUG_EVENT)p_EventListHead->EventList.Flink;
            if ( (PDEBUG_EVENT)p_EventListHead->EventList.Flink != entry )
            {
              while ( entry->ClientId.UniqueProcess != Flink->ClientId.UniqueProcess )
              {
                Flink = (PDEBUG_EVENT)Flink->EventList.Flink;
                if ( Flink == entry )
                  goto LABEL_19;
              }
              entry->Flags = Flags | 4;
              entry->BackoutThread = 0;
              is_got_event = 0;
            }
LABEL_19:
            if ( is_got_event )
              break;
          }
        }
        Process = entry->Process;
        Thread = entry->Thread;
        ObfReferenceObjectWithTag(Thread, 0x4F676244u);
        ObfReferenceObjectWithTag(Process, 0x4F676244u);
        DbgkpConvertKernelToUserStateChange(v28, debug_event);
        debug_event->Flags |= 1u;               // 标记已读
LABEL_24:
        status1 = 0;
      }
      KeReleaseGuardedMutex(&DebugObject->Mutex);
      if ( status1 < 0 )
        break;
      if ( is_got_event )
      {
        DbgkpOpenHandles(v28, Process, Thread);
        ObfDereferenceObjectWithTag(Thread, 0x4F676244u);
        ObfDereferenceObjectWithTag(Process, 0x4F676244u);
        break;
      }
      is_got_event = 0;
      if ( QuadPart < 0 )
      {
        v19 = MEMORY[0xFFFFF78000000014] - v25 + QuadPart < 0;
        QuadPart += MEMORY[0xFFFFF78000000014] - v25;
        v25 = MEMORY[0xFFFFF78000000014];
        DebugObject = (PDEBUG_OBJECT)Object;
        if ( !v19 )
        {
          status1 = 258;
          break;
        }
      }
      v11 = Alertable;
    }
    HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
    *(_OWORD *)&WaitStateChange->NewState = v28[0];
    *(_OWORD *)&WaitStateChange->AppClientId.UniqueThread = v28[1];
    *(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 1) = v28[2];
    *(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 3) = v28[3];
    *(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 5) = v28[4];
    *(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 7) = v28[5];
    *(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 9) = v28[6];
    v20 = &WaitStateChange->StateInfo.Exception.ExceptionRecord.ExceptionInformation[9];
    *((_OWORD *)v20 - 1) = v28[7];
    *(_OWORD *)v20 = v28[8];
    *((_OWORD *)v20 + 1) = v28[9];
    *((_OWORD *)v20 + 2) = v28[10];
    v20[6] = *(_QWORD *)&v28[11];
    return status1;
  }
  return status;
}

大概流程是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//一个大循环死等DebugObject的信号量被置位
status1 = KeWaitForSingleObject(DebugObject, Executive, IsPreviousMode, v11, Timeouta);
// 特别注意,KeWaitForSingleObject如果status == true就会reset信号位
//拿一个就跑
p_EventListHead = (struct _DEBUG_EVENT *)&DebugObject->EventList;
   ...
is_got_event = 1;
   ...
if ( is_got_event )
    break;
//填写WaitStateChange,然后返回
*(_OWORD *)&WaitStateChange->NewState = v28[0];
*(_OWORD *)&WaitStateChange->AppClientId.UniqueThread = v28[1];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 1) = v28[2];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 3) = v28[3];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 5) = v28[4];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 7) = v28[5];
*(_OWORD *)(&WaitStateChange->StateInfo.UnloadDll + 9) = v28[6];
v20 = &WaitStateChange->StateInfo.Exception.ExceptionRecord.ExceptionInformation[9];
*((_OWORD *)v20 - 1) = v28[7];
*(_OWORD *)v20 = v28[8];
*((_OWORD *)v20 + 1) = v28[9];
*((_OWORD *)v20 + 2) = v28[10];
v20[6] = *(_QWORD *)&v28[11];
return status1;

然后转换一下,r3调试器就高高兴兴的跑去处理调试事件了

ContinueDebugEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// from kernelbase
BOOL __stdcall ContinueDebugEvent(DWORD dwProcessId, DWORD dwThreadId, DWORD dwContinueStatus)
{
  NTSTATUS v5; // eax
  struct _CLIENT_ID v7; // [rsp+20h] [rbp-18h] BYREF

  v7.UniqueProcess = (HANDLE)(int)dwProcessId;
  v7.UniqueThread = (HANDLE)(int)dwThreadId;
  v5 = DbgUiContinue(&v7, dwContinueStatus);
  if ( v5 >= 0 )
  {
    RemoveHandles(dwThreadId, dwProcessId);
    return 1;
  }
  else
  {
    BaseSetLastNTError((unsigned int)v5);
    return 0;
  }
}
// from ntdll
__int64 __fastcall DbgUiContinue(__int64 a1, unsigned int a2)
{
  return NtDebugContinue(NtCurrentTeb()->DbgSsReserved[1], a1, a2);
}
// from ntoskrnl
NTSTATUS NtDebugContinue(HANDLE DebugObjectHandle, PCLIENT_ID ClientId, NTSTATUS ContinueStatus)
{
  KPROCESSOR_MODE PreviousMode; // r9
  NTSTATUS status; // eax
  NTSTATUS status1; // edi
  char find; // r15
  PDEBUG_EVENT v8; // rsi
  PDEBUG_OBJECT DebugObject; // r14
  PDEBUG_EVENT DebugEvent; // rcx
  PDEBUG_EVENT Flink; // rdx
  PDEBUG_EVENT Blink; // rax
  CLIENT_ID ClientId_copy; // [rsp+40h] [rbp-28h]
  PVOID Object; // [rsp+88h] [rbp+20h] BYREF

  PreviousMode = KeGetCurrentThread()->PreviousMode;
  ClientId_copy = *ClientId;
  if ( ContinueStatus != 0x80010001
    && (ContinueStatus <= 0x10000
     || ContinueStatus > 0x10002
     && ContinueStatus != 0x40010001
     && (ContinueStatus <= 0x40010002 || ContinueStatus > 0x40010004)) )
  {
    return 0xC000000D;                          //     STATUS_INVALID_PARAMETER
  }
  Object = 0;
  status = ObReferenceObjectByHandle(DebugObjectHandle, 1u, DbgkDebugObjectType, PreviousMode, &Object, 0);
  status1 = status;
  if ( status >= 0 )
  {
    find = 0;
    v8 = 0;
    DebugObject = (PDEBUG_OBJECT)Object;
    ExAcquireFastMutex((PFAST_MUTEX)((char *)Object + 24));
    DebugEvent = (PDEBUG_EVENT)DebugObject->EventList.Flink;
    if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
      goto empty_link;
    while ( 1 )
    {
      if ( DebugEvent->ClientId.UniqueProcess == ClientId_copy.UniqueProcess )
      {
        if ( find )
        {
          DebugEvent->Flags &= ~4u;
          KeSetEvent(&DebugObject->EventsPresent, 0, 0);
empty_link:
          KeReleaseGuardedMutex(&DebugObject->Mutex);
          HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
          if ( !find )
            return -1073741811;                 // STATUS_INVALID_PARAMETER
          if ( (PerfGlobalGroupMask & 0x400000) != 0 )
            EtwTraceDebuggerEvent(v8->Process, v8->Thread, 2);
          v8->ApiMsg.ReturnedStatus = ContinueStatus;
          v8->Status = 0;
          DbgkpWakeTarget((char *)v8);          // 放行被调试进程
          return status1;
        }
        if ( DebugEvent->ClientId.UniqueThread == ClientId_copy.UniqueThread && (DebugEvent->Flags & 1) != 0 )// 必须读过
                                                // #define DEBUG_EVENT_READ            (0x01)  // Event had been seen by win32 app
                                                // #define DEBUG_EVENT_NOWAIT          (0x02)  // No waiter one this. Just free the pool
                                                // #define DEBUG_EVENT_INACTIVE        (0x04)  // The message is in inactive. It may be activated or deleted later
                                                // #define DEBUG_EVENT_RELEASE         (0x08)  // Release rundown protection on this thread
                                                // #define DEBUG_EVENT_PROTECT_FAILED  (0x10)  // Rundown protection failed to be acquired on this thread
                                                // #define DEBUG_EVENT_SUSPEND         (0x20)  // Resume thread on continue
        {
          Flink = (PDEBUG_EVENT)DebugEvent->EventList.Flink;
          Blink = (PDEBUG_EVENT)DebugEvent->EventList.Blink;
          if ( (PDEBUG_EVENT)DebugEvent->EventList.Flink->Blink != DebugEvent
            || (PDEBUG_EVENT)Blink->EventList.Flink != DebugEvent )
          {
            __fastfail(3u);
          }
          Blink->EventList.Flink = &Flink->EventList;// 双向循环链表特有的去掉节点方法
          Flink->EventList.Blink = &Blink->EventList;
          v8 = DebugEvent;
          find = 1;
        }
      }
      DebugEvent = (PDEBUG_EVENT)DebugEvent->EventList.Flink;
      if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
        goto empty_link;
    }
  }
  return status;
}

他会遍历链表摘除读过的事件,如果还有就恢复被前面KeWaitForSingleObject清空的信号量,给通过DebugEvent的信号置位通行,DbgkpWakeTarget放行线程然后返回

1
2
3
4
5
6
7
8
9
10
11
12
13
          DebugEvent->Flags &= ~4u;
          KeSetEvent(&DebugObject->EventsPresent, 0, 0);
empty_link:
          KeReleaseGuardedMutex(&DebugObject->Mutex);
          HalPutDmaAdapter((PADAPTER_OBJECT)DebugObject);
          if ( !find )
            return -1073741811;                 // STATUS_INVALID_PARAMETER
          if ( (PerfGlobalGroupMask & 0x400000) != 0 )
            EtwTraceDebuggerEvent(v8->Process, v8->Thread, 2);
          v8->ApiMsg.ReturnedStatus = ContinueStatus;
          v8->Status = 0;
          DbgkpWakeTarget((char *)v8);          // 放行被调试进程
          return status1;

如果判断调试事件链表是否为空,

1
2
3
    DebugEvent = (PDEBUG_EVENT)DebugObject->EventList.Flink;
    if ( DebugEvent == (PDEBUG_EVENT)&DebugObject->EventList )
      goto empty_link;

对于双向循环链表,前一个等于后一个等于自己就意味着空 如果为空就不置位debugobject的信号,直接放行

1
DbgkpWakeTarget((char *)v8);          // 放行被调试进程

至此,整体框架构建完成
ok先更到这,前面的知识以后再来探索吧(