oxygen1a1 发表于 2022-12-20 21:33

Win10_x64 21h2调试体系分析(一)

本帖最后由 oxygen1a1 于 2022-12-20 21:35 编辑



记的笔记,发出来参考下,抛砖引玉,有错误多纠正。还望各位大佬别嘲笑。
平台如下:

版本      Windows 10 专业版
版本号      21H2
安装日期      ‎2021-‎08-‎23
操作系统内部版本      19044.2364
体验      Windows Feature Experience Pack 120.2212.4190.0





# 0x0参考

《WRK源码》


# 0x1 Windows调试体系

在Windows中,调试器是基于事件处理的,不是基于状态机的。

因此在内核中,是在进程与被调试进程之间建立通道进行通信的,即==DebugPort:调试对象==

被调试进程中产生事件时,会把事件放在DebugPort的一个事件链表中。而调试器接受事件通知,去DebugPort拿调试事件。

常见的调试事件如

- 创建进线程
- 进线程结束
- ==异常==
- 模块加卸载
- 打印日志:OutputStringA

最核心的便是异常,其他的调试事件一般是用记录的。

## 0x1-1 调试对象的建立

Windows调试必须先建立管道,才能在调试进程和被调试进程传递信息。而管道就是调试对象。

调试器拥有调试对象句柄从而对被调试进程进行操作。被调试进程EPROCESS.DebugPort存值以便于往里面写入DeBugEvent。

### 0x1-1-1 DebugActiveProcess



```cpp
BOOL __stdcall DebugActiveProcess(DWORD dwProcessId);
```

这个函数是调试通道建立的开始,他的主要功能就是

- 创建调试对象(DEBUG_OBJECT)
- 根据传入的Pid打开句柄(==权限问题==),调用__imp_DbgUiDebugActiveProcess,把DebugPort挂上去。

DbgUiConnectToDbg即创建调试对象,判断是否创建成功。

```assembly
mov   , rbx
push    rdi             ; 保留非易失寄存器
sub   rsp, 20h
mov   ebx, ecx
call    cs:__imp_DbgUiConnectToDbg ; 先创建一个调试对象
nop   dword ptr
test    eax, eax
jns   short DebugPortCreateSuccess ; 因此想要调试,首先得打开进程
```

然后根据Pid打开进程获取句柄,调用`__imp_DbgUiDebugActiveProcess()`将被调试进程DEBUG_PORT端口和创建的调试对象句柄联系起来。

```assembly
DebugPortCreateSuccess: ; 因此想要调试,首先得打开进程
mov   ecx, ebx
call    ProcessIdToHandle ; 打开进程,获取句柄
mov   rbx, rax
test    rax, rax
jz      short OpenFailed
mov   rcx, rax      ; 被调试进程句柄
call    cs:__imp_DbgUiDebugActiveProcess ; 初始化调试对象信息
nop   dword ptr
mov   edi, eax
mov   rcx, rbx
test    eax, eax
jns   short InitDebugPortSuccess ; 关闭句柄返回
OpenFaild:
;清理资源,关闭句柄。
```

### 0x1-1-2 DbgUiConnectToDbg

前文提到,这个用于创建调试对象,创建过程是ntdll!DbgUiConnectToDbg->nt!NtCreateDebugObject

在这个函数中,进行了一些简单判断,判断是否已经在调试别的程序中。

```assembly
mov   rax, gs:_TEB.NtTib.Self
xor   ecx, ecx
cmp   , rcx ; 判断是否已经有调试
jnz   short HasDebugge
```

他判断是否有被调试进程是通过TEB.DbgSsReserved+8的位置,事实上,这个地方存的就是句柄。

如果没有,则调用NtCreateDebugObject进入内核进程创建对象。

```assembly
mov   , rcx
lea   r8,    ; 传地址 其实是OBJECT_ATTRIBUTES
mov   , ecx
xorps   xmm0, xmm0
mov   , rcx
mov   r9d, 1          ; 传参
movdquxmmword ptr , xmm0
mov   dword ptr , 30h
mov   edx, 1F000Fh    ; 传参
mov   rcx, gs:_TEB.NtTib.Self
add   rcx, _TEB.DbgSsReserved+8
call    NtCreateDebugObject


```

而NtCreateDebugObject函数声明是

```cpp
NTSTATUS
NtCreateDebugObject (
    OUT PHANDLE DebugObjectHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    IN ULONG Flags
    );
```

由参数推出,第一个参数,DebugObjectHandle就是`_TEB.DbgSsReserved+8`位置,也就是调试对象的句柄。

值得一提的是,DesiredAccess是对于调试对象句柄的权限。

### 0x1-1-3 nt!NtCreateDebugObject

这个函数只有两个作用

- 创建调试对象
- 根据参数DesiredAccess作为调试对象权限存入调试进程的句柄表中

首先检查一些参数,这是R3->R0的常规操作。

然后创建调试对象,使用==ObCreateObjectEx==,所有内核对象都是通过它创建的,包括ETHREAD,EPROCESS等

```assembly
CreateDebugObject:      ; 调试对象类型
mov   rdx, cs:DbgkDebugObjectType
and   qword ptr , 0
lea   rax,
mov   , rax; pObject
and   dword ptr , 0
and   dword ptr , 0
mov   dword ptr , 68h ; ObjectSize
mov   r9b, r10b
mov   cl, r10b      ; AccessMode
call    ObCreateObjectEx ; 创建调试对象
test    eax, eax
js      Ret
```

调试对象的结构如下:

```cpp
typedef struct _DEBUG_OBJECT {
    //
    // Event thats set when the EventList is populated.
    //
    KEVENT EventsPresent;
    //
    // Mutex to protect the structure
    //
    FAST_MUTEX Mutex;
    //
    // Queue of events waiting for debugger intervention
    //
    LIST_ENTRY EventList;
    //
    // Flags for the object
    //
    ULONG Flags;
} DEBUG_OBJECT, *PDEBUG_OBJECT;
```

**其中EventsPresent**的意义是方便让调试器的调试循环捕捉,一旦在链表中有了要处理的调试事件,就会用KeSetEvent设置事件信号(==后面会有体现==)。

**而Mutex**的意义便是多线程操作链表时候的同步作用。

**EventList是链表头**,链接DEBUG_EVENT所有事件

**flags**则表明DebugObject属性,如下值

```cpp
#define DEBUG_OBJECT_DELETE_PENDING (0x1) // Debug object is delete pending.
#define DEBUG_OBJECT_KILL_ON_CLOSE(0x2) // Kill all debugged processes on close
```

若为1,说明DebugObject无效

创建对象成功后,进行简单初始化,如链表清空操作

```assembly
mov   rbx, ; 调试对象进行赋值
mov   , 1 ; 参考WRK的DEBUG_PORT对象
and   , 0 ; 初始化互斥体,用于插入链表时候的同步
and   , 0
lea   rcx, ; Event
xor   r8d, r8d      ; State
lea   edx,    ; Type
call    KeInitializeEvent
lea   rax,
mov   , rax
mov   , rax      ; 情况链表 自己指向自己
xor   r8d, r8d      ; State
xor   edx, edx      ; Type
mov   rcx, rbx      ; Event
call    KeInitializeEvent
test    sil, 1          ; R3 flags
jz      short Equal
mov   dword ptr , 2
jmp   short loc_140883589
Equal:
and   dword ptr , 0
```

**其中sil即R3->R0传入的flags**,不难发现,如果传入1,则代表调试关闭时关闭所有调试进程(==出现场景为调试子进程==),如果传入0,则不会关闭所有被调试进程。

在创建完对象之后,进行wow64进程的判断,如果调试进程是32位的,那么flags | 4

```assembly
mov   rax, gs:188h
mov   rcx,
mov   rax, ; 这是调试器的线程
test    rax, rax
jz      short x64Bit
or      dword ptr , 4 ; 即flags & 4 就是wow64
x64Bit:
xxxxx
```

然后把调试对象插入到调试进程的句柄表中,其中句柄的权限就是R3传入的DesriedAccess;

顺带也可以发现,产生的句柄确实放在了_TEB.DbgSsReserved+8这个位置。

```assembly
mov   r8d, r14d       ; r14就是R3传过来的DesriedAccess
xor   edx, edx
mov   rcx,
call    ObInsertObjectEx ; InsertObject的作用就是把对象查到句柄表里面
mov   ecx, eax
test    eax, eax
js      short Ret
mov   rax,
mov   , rax      ; 这是TEB的那个位置,用于保存句柄
Ret:
;进行释放资源的操作
```

自此调试对象创建完毕。

## 0x1-2调试对象挂入被调试进程

在DebugActiveProcess中,创建完调试对象之后,则开始进行与被调试对象挂入操作。

调用如下函数进行挂入:

```cpp
NTSTATUS __fastcall DbgUiDebugActiveProcess(__int64 ProcessHandle)
{
__int64 hProcess; // rdi
signed int status; // ebx

hProcess = ProcessHandle;
status = NtDebugActiveProcess(ProcessHandle, NtCurrentTeb()->DbgSsReserved);
if ( status >= 0 )
{
    status = DbgUiIssueRemoteBreakin(hProcess);
    if ( status < 0 )
      ZwRemoveProcessDebug(hProcess, NtCurrentTeb()->DbgSsReserved);
}
return (unsigned int)status;
}
```

函数主要功能即调用NtDebugActiveProcess,传入被调试进程句柄和调试对象句柄,在内核进行挂载。

在挂入成功之后,调用`DbgUiIssueRemoteBreakin()`。

这个函数的作用是创建一个远程线程,让远程线程指向int3产生异常,被调试器捕获。

这就是为什么用调试器附加进程,总是会断在一个系统断点。

```cpp
__int64 __fastcall DbgUiIssueRemoteBreakin(__int64 hProcess)
{
status = RtlpCreateUserThreadEx(
             hProcess,
             0i64,
             2,
             0,
             0i64,
             0x4000i64,
             v3,
             (__int64)DbgUiRemoteBreakin,       // 新建线程的地址
             0i64,
             &v5,
             (__m128i *)&v4);
if ( (status & 0x80000000) == 0 )
    NtClose(v5);
return status;
}
```

`DbgUiRemoteBreakin`是新建线程的地址,在进行简单判断之后就会调用`DbgBreakPoint();`

```cpp
void __noreturn DbgUiRemoteBreakin()
{
if ( (NtCurrentPeb()->BeingDebugged || MEMORY & 2) && !(NtCurrentTeb()->_union_108.SameTebFlags & 0x20) )
{
    if ( UseWOW64 )
    {
      if ( g_LdrpWow64PrepareForDebuggerAttach )
      g_LdrpWow64PrepareForDebuggerAttach();
    }
    DbgBreakPoint();//执行Int3
}
RtlExitUserThread(0i64);
}
```

值得一提的是,在`DbgUiDebugActiveProcess`中,如果`DbgUiIssueRemoteBreakin`执行失败,则会执行

```cpp
if ( status < 0 )
      ZwRemoveProcessDebug(hProcess, NtCurrentTeb()->DbgSsReserved);
```

而`DbgUiIssueRemoteBreakin`执行失败只有一个原因,即创建远程线程失败。因此要调试还需要具有远程创建线程的句柄权限。

### 0x1-2-1 nt!NtDebugActiveProcess

它是被调试进程和调试对象建立起来联系核心函数。

声明如下:

```cpp
NTSTATUS
NtDebugActiveProcess (
    IN HANDLE ProcessHandle,
    IN HANDLE DebugObjectHandle
    );
```

函数首先根据句柄找到进程

```assembly
mov   r8, cs:PsProcessType
and   qword ptr , 0
mov   bpl, byte ptr ; PreviousMode
lea   rax, ; pObject rsp+0x80
and   qword ptr , 0
mov   r9b, bpl
mov   , rax
mov   dword ptr , 4F676244h
call    ObReferenceObjectByHandleWithTag ; 获取进程对象
```

然后根据进程进行一些判断,基本就是

- 是否调试自己
- 是否是系统进程
- 是否是wow64

```assembly
mov   rax, gs:188h
mov   rdi,
mov   rsi,
cmp   rdi, rsi      ; 判断是不是在调试自己
jz      DebugProcessErr
cmp   rdi, cs:PsInitialSystemProcess ; 判断一下是不是这个进程 就是system进程
jz      DebugProcessErr
mov   rax,
test    rax, rax
jz      x64Bit
;进行调试进程被调试进程检查
; 如果被调试64 调试32 无法调试
```

检查无误之后,获取调试对象

```assembly
GetDebugObject:         ; ObjectType
mov   r8, cs:DbgkDebugObjectType
lea   rax,
and   , 0
mov   r9b, bpl      ; AccessMode
and   , 0
mov   edx, 2          ; DesiredAccess
mov   rcx, r14      ; Handle
mov   , rax ; Object
call    ObReferenceObjectByHandle
```

此外,获取RunDown 锁,防止进程结束

```assembly
lea   rbp,
mov   rcx, rbp
call    ExAcquireRundownProtection_0 ; 获取被调试对象的RunDown锁
mov   rsi, ; 这个可以反调试 但是不要用
test    al, al
jz      short RunDownProtectErr
```

之后进入核心代码,发送==假消息模拟==

> 调试假消息的意义在于附加时进程已经创建,无法还原线程进程创建时场景,因此采取模拟发送假消息方式进行折中,还原进程刚创建时的调试信息不会遗漏。

```assembly
lea   r8,
mov   rdx, rsi      ; DebugObject
mov   rcx, rdi      ; DebugProcess
call    DbgkpPostFakeProcessCreateMessages ; 创建进程创建的假消息 其实就是进程已经创建过了 还得接受一下消息
                        ; 模拟一下正常调试信息
                        ; 注意是创建,不会发送!
mov   r9,
mov   r8d, eax
mov   rdx, rsi      ; DebugPort
mov   rcx, rdi      ; DebugProcess
call    DbgkpSetProcessDebugObject ; 把DebugPort写入被调试进程
                        ; 参考WRK
                        ; 并发送消息上一个函数模拟的假消息
mov   rcx, rbp
mov   ebx, eax
call    ExReleaseRundownProtection_0 ; 释放锁
jmp   short release
```

核心函数便是

```cpp
NTSTATUS
DbgkpPostFakeProcessCreateMessages (
    IN PEPROCESS Process,
    IN PDEBUG_OBJECT DebugObject,
    IN PETHREAD *pLastThread
    );
```

```cpp
NTSTATUS
DbgkpSetProcessDebugObject (
    IN PEPROCESS Process,
    IN PDEBUG_OBJECT DebugObject,
    IN NTSTATUS MsgStatus,
    IN PETHREAD LastThread
    );
```

## 0x1-3发送假消息

在调试器附加之后,会发送假消息模拟进程创建。这时候DebugPort还没有挂入到被调试进程。无法直接将消息写入Process.DebugPort,==Windows采取采取传入DebugPort变量,消息写入变量中==,而非Process.DebugPort中。

然后在`DbgkpSetProcessDebugObject`中进行设置DebugPort挂入被调试进程。

### 0x1-3-1 DbgkpPostFakeProcessCreateMessages

```cpp
__int64 __fastcall DbgkpPostFakeProcessCreateMessages(_EPROCESS *DebugProcess, _DEBUG_PORT *DebugObject, __int64 *a3)
{
v3 = a3;
v4 = 0i64;
pFirstThread = 0i64;
v10 = 0i64;
pLastThread = 0i64;
DebugObject_1 = DebugObject;
v11 = 0i64;
DebugProcess_1 = DebugProcess;
v12 = 0i64;
result = DbgkpPostFakeThreadMessages(DebugProcess, DebugObject, 0i64, &pFirstThread, &pLastThread);// 发送线程假消息
if ( (signed int)result >= 0 )
{
    KiStackAttachProcess((ULONG_PTR)DebugProcess_1, 0, (__int64)&v10);
    DbgkpPostModuleMessages(DebugProcess_1, pFirstThread, DebugObject_1);// 发送模块消息
    KiUnstackDetachProcess(&v10, 0i64);
    ObfDereferenceObjectWithTag((ULONG_PTR)pFirstThread);
    result = 0i64;
    v4 = pLastThread;
}
*v3 = (__int64)v4;
return result;
}
```

首先是发送假线程消息,在`DbgkpPostFakeProcessCreateMessages()`中

```cpp
result = DbgkpPostFakeThreadMessages(DebugProcess, DebugObject, 0i64, &pFirstThread, &pLastThread);// 发送线程假消息
```

在此函数中,主要进行了如下操作

- 判断是否是不能调试的进线程,入system的线程,直接跳过不发送假消息
- 遍历被调试进程所有线程,获取线程结构体,初始化==_DBGKM_APIMSG==结构体

此结构是后面用于初始化DEBUG_EVENT的结构体,DEBUG_EVENT是用于挂在DEBUG_OBJECT.EventList链表中的。

如下代码,根据ApiNumber=2,初始化ApiMsg结构体,

_DBGKM_APIMSG结构如下

```cpp
typedef struct _DBGKM_APIMSG {
    PORT_MESSAGE h;
    DBGKM_APINUMBER ApiNumber; //枚举
    NTSTATUS ReturnedStatus;
    union {
      DBGKM_EXCEPTION Exception;
      DBGKM_CREATE_THREAD CreateThread;
      DBGKM_CREATE_PROCESS CreateProcessInfo;
      DBGKM_EXIT_THREAD ExitThread;
      DBGKM_EXIT_PROCESS ExitProcess;
      DBGKM_LOAD_DLL LoadDll;
      DBGKM_UNLOAD_DLL UnloadDll;
    } u;
} DBGKM_APIMSG, *PDBGKM_APIMSG;
```

其中h用于串口联网调试。

ApiNumber则是如下枚举

```c
typedef enum _DBGKM_APINUMBER {
DbgKmExceptionApi,
DbgKmCreateThreadApi,
DbgKmCreateProcessApi,
DbgKmExitThreadApi,
DbgKmExitProcessApi,
DbgKmLoadDllApi,
DbgKmUnloadDllApi,
DbgKmMaxApiNumber
} DBGKM_APINUMBER;
```

**DbgkpPostFakeThreadMessages该函数操作如下**

```cpp
*(_DWORD *)&ApiMsg.ApiNumber = 2;   
Section = (_SECTION *)Process_2->SectionObject;
if ( Section )
*(_QWORD *)&ApiMsg.u = DbgkpSectionToFileHandle(Section);// 就是返回文件句柄的
else
*(_QWORD *)&ApiMsg.u = 0i64;
*(_QWORD *)&ApiMsg.u = Process_2->SectionBaseAddress;// BaseOfImage
KeStackAttachProcess(Process_2, (_KAPC_STATE *)&Apc);
ntHead = (IMAGE_NT_HEADERS64 *)RtlImageNtHeader(Process_2->SectionBaseAddress);
if ( ntHead )
{
        *(_QWORD *)&ApiMsg.u = 0i64;
        *(_DWORD *)&ApiMsg.u = ntHead->FileHeader.PointerToSymbolTable;// 符号表
        *(_DWORD *)&ApiMsg.u = ntHead->FileHeader.NumberOfSymbols;
}
         
KeUnstackDetachProcess(&Apc);
status = DbgkpQueueMessage(Process_2, StartThread_1, &ApiMsg, flags, DebugObject_1);// 将信息插入DebugObject
                                                // 这个函数就是插入DebugObject并设置DebugObject的等待位为等待
                                                // 现在我们在发假消息的时候调用,他的作用仅仅是初始化DebugPort,填DebugEvent
```

在初始化完ApiMsg之后,调用`DbgkpQueueMessage()`进行插入,此函数不仅是假消息插入核心,也是正常调试消息插入核心函数。

这就是在DbgkpPostFakeProcessCreateMessage中发送假线程过程,发送假dll也是在此函数中进行,原理类似。

### 0x1-3-2 DbgkpQueueMessage

函数声明如下

```cpp
NTSTATUS
DbgkpQueueMessage (
    IN PEPROCESS Process,
    IN PETHREAD Thread,
    IN OUT PDBGKM_APIMSG ApiMsg,
    IN ULONG Flags,
    IN PDEBUG_OBJECT TargetDebugObject
    );
```

函数作用主要是

- 对于发送假消息(==本质是假消息调用此函数传入flags为NoWait,不用替换DebugObject==),直接操作TargetDebugObject。
- 对于需要等待的消息,取得Process.DebugPort,根据ApiMsg初始化DebugEvent,挂入DebugPort.EventList链表,KeWaitXXX(DebugEvent.ContinueEvent)等待

值得注意的是,此时的等待是被调试进程等待DebugEvent。而非调试进程等待,原因是如果是非NoWait消息,此时被调试进程一定有DebugPort,才可能产生这种消息,而且代表`DbgkpQueueMessage`调用者是被调试进程自己而不是调试进程在模拟假消息时候的调用,因此==此时的等待是被调试进程等待DebugEvent==

`KeWaitForSingleObject(&DebugEvent_1->ContinueEvent.Header, 0, 0, 0, 0i64);// 进行等待`

复制ApiMsg到DebugEvent

```cpp
CopyDApiMsgToDbkEvent:
v15 = &DebugEvent_1->ApiMsg;
DebugEvent_1->Process = Process_1;
DebugEvent_1_1 = &DebugEvent_1->ApiMsg;
DebugEvent_1->Thread = Thread_1;
ApiMsg_2 = ApiMsg_1;
v18 = 2i64;
do                                          // 把ApiMsg复制过去
{
    *(_OWORD *)DebugEvent_1_1->h = *(_OWORD *)ApiMsg_2->h;
    *(_OWORD *)&DebugEvent_1_1->h = *(_OWORD *)&ApiMsg_2->h;
    *(_OWORD *)&DebugEvent_1_1->h = *(_OWORD *)&ApiMsg_2->h;
    *(_OWORD *)&DebugEvent_1_1->u = *(_OWORD *)&ApiMsg_2->u;
    *(_OWORD *)&DebugEvent_1_1->u = *(_OWORD *)&ApiMsg_2->u;
    *(_OWORD *)&DebugEvent_1_1->u = *(_OWORD *)&ApiMsg_2->u;
    *(_OWORD *)&DebugEvent_1_1->u = *(_OWORD *)&ApiMsg_2->u;
    DebugEvent_1_1 = (_DBGKM_APIMSG *)((char *)DebugEvent_1_1 + 128);
    v19 = *(_OWORD *)&ApiMsg_2->u;
    ApiMsg_2 = (_DBGKM_APIMSG *)((char *)ApiMsg_2 + 128);
    *(_OWORD *)&DebugEvent_1_1[-1].u = v19;
    --v18;
}
while ( v18 );
*(_OWORD *)DebugEvent_1_1->h = *(_OWORD *)ApiMsg_2->h;
_mm_storeu_si128((__m128i *)&DebugEvent_1->Cid, (__m128i)Thread_1->Cid);
```

操作DebugObject,插入双向链表

值得一提,如果是被调试进程主动调用此函数(需要等待),DebugObject==Process.DebugPort,否则,代表无需等待,DebugObject=TargetObject。

```cpp
Tail = DebugObject_1->EventList.Blink;    // 这个算法是插到链表尾部
      if ( Tail->Flink != &DebugObject_1->EventList )
      __fastfail(3u);
      DebugEvent_1->EventList.Flink = &DebugObject_1->EventList;
      DebugEvent_1->EventList.Blink = Tail;
      Tail->Flink = &DebugEvent_1->EventList;
      DebugObject_1->EventList.Blink = &DebugEvent_1->EventList;
      if ( !bNoWait )
      KeSetEvent(&DebugObject_1->EventPresent, 0, 0);// 需要等待,设置一下DebugObject的位,当调试循环能改进行
//而我们发送假消息bNoWait是true,也就是不会KeSetEvent
      status = 0;
```

KeSetEvent作用是让调试进程的KeWait能改等待到,说明有消息需要处理,==即阻塞调试进程的线程。==

最后判断一下是否是需要等待的DebugEvent,需要等待,`KeWaitForSingleObject`,==即阻塞被调试进程的线程。==

```cpp
KeReleaseGuardedMutex((ULONG_PTR)&DbgkpProcessDebugPortMutex);
    if ( status >= 0 )
    {
      KeWaitForSingleObject(&DebugEvent_1->ContinueEvent.Header, 0, 0, 0, 0i64);// 进行等待
      status = DebugEvent_1->Status;            // 注意,是被调试进程在这等待!
                                                // 等待的是DebugEvent的Event
                                                // 而DebugObject的Event则标志着有事件要进行处理
    }
```

### 0x1-3-3 DbgkpSetProcessDebugObject

**函数作用为**

- 设置被调试进程的DebugPort
- 遍历EventList,执行之前在DebugPort初始化的消息(==发送假消息只是初始化了这个结构体,并没有设置KeSetEvent,参见上文==)
- 清理无效的DebugEvent
- 再次遍历线程,双重保险,防止被调试进程又新建线程导致无法发送消息。

**函数声明如下**

```c
NTSTATUS
DbgkpSetProcessDebugObject (
    IN PEPROCESS Process,
    IN PDEBUG_OBJECT DebugObject,
    IN NTSTATUS MsgStatus,
    IN PETHREAD LastThread
    );
```

---

==以下函数代码来自WRK,非IDA逆出==

首先判断传入MsgStatus,这个值是`DbgkpPostFakeProcessCreateMessages`函数的返回值,标志这个函数是不是执行成功。

```c
if (!NT_SUCCESS (MsgStatus)) { //这个是前面插入DebugObject List时候是否成功
      LastThread = NULL;
      Status = MsgStatus;
    } else {
      Status = STATUS_SUCCESS;
    }
```

设置被调试进程的DebugPort

```c
if (Process->DebugPort != NULL) {
                Status = STATUS_PORT_ALREADY_SET;
                break;
            }
            //
            // Assign the debug port to the process to pick up any new threads
            //
            Process->DebugPort = DebugObject;//设置调试对象
```

判断是否被调试进程新建线程,双重保险防止遗漏

```c
      while (1) {
            //
            // Acquire the debug port mutex so we know that any new threads will
            // have to wait to behind us.
            //
            GlobalHeld = TRUE;

            ExAcquireFastMutex (&DbgkpProcessDebugPortMutex);//获取

            //
            // If the port has been set then exit now.
            //
            if (Process->DebugPort != NULL) {
                Status = STATUS_PORT_ALREADY_SET;
                break;
            }
            //
            // Assign the debug port to the process to pick up any new threads
            //
            Process->DebugPort = DebugObject;//设置

            //
            // Reference the last thread so we can deref outside the lock
            //
            ObReferenceObject (LastThread);

            //
            // Search forward for new threads
            //
            Thread = PsGetNextProcessThread (Process, LastThread);//判断一下是否有新的线程,有的话再发假线程消息
            if (Thread != NULL) {

                //
                // Remove the debug port from the process as we are
                // about to drop the lock
                //
                Process->DebugPort = NULL;

                ExReleaseFastMutex (&DbgkpProcessDebugPortMutex);

                GlobalHeld = FALSE;

                ObDereferenceObject (LastThread);

                //
                // Queue any new thread messages and repeat.
                //

                Status = DbgkpPostFakeThreadMessages (Process,
                                                      DebugObject,
                                                      Thread,
                                                      &FirstThread,
                                                      &LastThread);
                if (!NT_SUCCESS (Status)) {
                  LastThread = NULL;
                  break;
                }
                ObDereferenceObject (FirstThread);
            } else {
                break;
            }
      }

```

遍历DebugObject->EventList链表,如果有值则` KeSetEvent (&DebugObject->EventsPresent, 0, FALSE);`

```c
for (Entry = DebugObject->EventList.Flink;//遍历DebugObject链表
         Entry != &DebugObject->EventList;
         ) {

      DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList);
      Entry = Entry->Flink;

      if ((DebugEvent->Flags&DEBUG_EVENT_INACTIVE) != 0 && DebugEvent->BackoutThread == ThisThread) {
            Thread = DebugEvent->Thread;

            //
            // If the thread has not been inserted by CreateThread yet then don't
            // create a handle. We skip system threads here also
            //
            if (NT_SUCCESS (Status) && Thread->GrantedAccess != 0 && !IS_SYSTEM_THREAD (Thread)) {
                //
                // If we could not acquire rundown protection on this
                // thread then we need to suppress its exit message.
                //
                if ((DebugEvent->Flags&DEBUG_EVENT_PROTECT_FAILED) != 0) {
                  PS_SET_BITS (&Thread->CrossThreadFlags,
                                 PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG);
                  RemoveEntryList (&DebugEvent->EventList);
                  InsertTailList (&TempList, &DebugEvent->EventList);
                } else {
                  if (First) {//只有第一次进入才设置
                         DebugEvent->Flags &= ~DEBUG_EVENT_INACTIVE;
                        KeSetEvent (&DebugObject->EventsPresent, 0, FALSE);//设置DebugObject->Event,调试器的KeWait可以等待成功。
                        First = FALSE;
                  }
```

最后释放资源,清理无效DebugEvent

```c
if (GlobalHeld) {
      ExReleaseFastMutex (&DbgkpProcessDebugPortMutex);//可以用于反调试 占用这个全局变量导致所有调试器无法调试,链接DebugPort
    }

    if (LastThread != NULL) {
      ObDereferenceObject (LastThread);
    }

    while (!IsListEmpty (&TempList)) {//清空无效DebugEvent
      Entry = RemoveHeadList (&TempList);
      DebugEvent = CONTAINING_RECORD (Entry, DEBUG_EVENT, EventList);
      DbgkpWakeTarget (DebugEvent);
    }
        return status;
```

# 0x2结语

自此,Windows调试体系的创建调试对象,调试对象的链接以及假消息发送告一段落。剩下的编译异常分发,调试器处理了,这些基础知识也是自建调试通道的基础。

ws920222 发表于 2022-12-20 21:47

沙发,看不懂

man5604820 发表于 2022-12-22 14:37

我在找这个系统不过是家庭版的    不会找,所以找不到。。。楼主有的话。能否提供一下地址。。。谢谢
我的系统:
版本        Windows 10 家庭中文版
版本号        21H2
安装日期        ‎2021/‎10/‎22
操作系统内部版本        19044.2364
体验        Windows Feature Experience Pack 120.2212.4190.0

bolangu 发表于 2022-12-20 23:11

好深奥,看不懂

aonima 发表于 2022-12-21 00:37

收藏了,有空慢慢研究

lizhuowu 发表于 2022-12-21 07:44

这样好文章不得不支持!

lzd759125184 发表于 2022-12-21 07:51

深奥慢慢研究

BI3NWQ 发表于 2022-12-21 08:22

学习了!

aa2923821a 发表于 2022-12-21 08:48

感谢分享!!!

GTR022 发表于 2022-12-21 08:50

好深奥,看不懂

15235109295 发表于 2022-12-21 08:55

好深奥,看不懂
页: [1] 2 3 4 5 6
查看完整版本: Win10_x64 21h2调试体系分析(一)