Win10_x64 21h2调试体系分析(一)
***记的笔记,发出来参考下,抛砖引玉,有错误多纠正。还望各位大佬别嘲笑????,个人认为做得还算详细,加个精华应该不过分,视情况更新下一篇*。平台如下:版本 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**
```
?BOOL __stdcall DebugActiveProcess(DWORD dwProcessId);
```
**这个函数是调试通道建立的开始,他的主要功能就是**
* **创建调试对象(DEBUG_OBJECT)**
* **根据传入的Pid打开句柄(**=**权限问题**=**),调用__imp_DbgUiDebugActiveProcess,把DebugPort挂上去。**
**DbgUiConnectToDbg即创建调试对象,判断是否创建成功。**
```
?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端口和创建的调试对象句柄联系起来。**
```
?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**
**在这个函数中,进行了一些简单判断,判断是否已经在调试别的程序中。**
```
?mov ? ? rax, gs:_TEB.NtTib.Self
?xor ? ? ecx, ecx
?cmp ? ? , rcx ; 判断是否已经有调试
?jnz ? ? short HasDebugge
```
**他判断是否有被调试进程是通过TEB.DbgSsReserved+8的位置,事实上,这个地方存的就是句柄。**
**如果没有,则调用NtCreateDebugObject进入内核进程创建对象。**
```
?mov ? ? , rcx
?lea ? ? r8, ? ; 传地址 其实是OBJECT_ATTRIBUTES
?mov ? ? , ecx
?xorps ? xmm0, xmm0
?mov ? ? , rcx
?mov ? ? r9d, 1 ? ? ? ? ?; 传参
?movdqu ?xmmword ptr , xmm0
?mov ? ? dword ptr , 30h
?mov ? ? edx, 1F000Fh ? ?; 传参
?mov ? ? rcx, gs:_TEB.NtTib.Self
?add ? ? rcx, _TEB.DbgSsReserved+8
?call ? ?NtCreateDebugObject
?
?
```
**而NtCreateDebugObject函数声明是**
```
?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等**
```
?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
```
**调试对象的结构如下:**
```
?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属性,如下值**
```
?#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无效**
**创建对象成功后,进行简单初始化,如链表清空操作**
```
?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**
```
?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这个位置。**
```
?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中,创建完调试对象之后,则开始进行与被调试对象挂入操作。**
**调用如下函数进行挂入:**
```
?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产生异常,被调试器捕获。**
**这就是为什么用调试器附加进程,总是会断在一个系统断点。**
```
?__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();`
```
?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`**执行失败,则会执行**
```
? ?if ( status < 0 )
? ? ? ?ZwRemoveProcessDebug(hProcess, NtCurrentTeb()->DbgSsReserved);
```
**而**`DbgUiIssueRemoteBreakin`**执行失败只有一个原因,即创建远程线程失败。因此要调试还需要具有远程创建线程的句柄权限。**
### **0x1-2-1 nt!NtDebugActiveProcess**
**它是被调试进程和调试对象建立起来联系核心函数。**
**声明如下:**
```
?NTSTATUS
?NtDebugActiveProcess (
? ? ?IN HANDLE ProcessHandle,
? ? ?IN HANDLE DebugObjectHandle
? ?);
```
**函数首先根据句柄找到进程**
```
?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**
```
?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 无法调试
```
**检查无误之后,获取调试对象**
```
?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 锁,防止进程结束**
```
?lea ? ? rbp,
?mov ? ? rcx, rbp
?call ? ?ExAcquireRundownProtection_0 ; 获取被调试对象的RunDown锁
?mov ? ? rsi, ; 这个可以反调试 但是不要用
?test ? ?al, al
?jz ? ? ?short RunDownProtectErr
```
**之后进入核心代码,发送**=**假消息模拟**=
> **调试假消息的意义在于附加时进程已经创建,无法还原线程进程创建时场景,因此采取模拟发送假消息方式进行折中,还原进程刚创建时的调试信息不会遗漏。**
```
?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
```
**核心函数便是**
```
?NTSTATUS
?DbgkpPostFakeProcessCreateMessages (
? ? ?IN PEPROCESS Process,
? ? ?IN PDEBUG_OBJECT DebugObject,
? ? ?IN PETHREAD *pLastThread
? ?);
```
```
?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**
```
?__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()`**中**
```
?result = DbgkpPostFakeThreadMessages(DebugProcess, DebugObject, 0i64, &pFirstThread, &pLastThread);// 发送线程假消息
```
**在此函数中,主要进行了如下操作**
* **判断是否是不能调试的进线程,入system的线程,直接跳过不发送假消息**
* **遍历被调试进程所有线程,获取线程结构体,初始化**=**_DBGKM_APIMSG**=**结构体**
**此结构是后面用于初始化DEBUG_EVENT的结构体,DEBUG_EVENT是用于挂在DEBUG_OBJECT.EventList链表中的。**
**如下代码,根据ApiNumber=2,初始化ApiMsg结构体,**
**_DBGKM_APIMSG结构如下**
```
?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则是如下枚举**
```
?typedef enum _DBGKM_APINUMBER {
? ?DbgKmExceptionApi,
? ?DbgKmCreateThreadApi,
? ?DbgKmCreateProcessApi,
? ?DbgKmExitThreadApi,
? ?DbgKmExitProcessApi,
? ?DbgKmLoadDllApi,
? ?DbgKmUnloadDllApi,
? ?DbgKmMaxApiNumber
?} DBGKM_APINUMBER;
```
****DbgkpPostFakeThreadMessages该函数操作如下****
```
?*(_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**
**函数声明如下**
```
?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**
```
?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。**
```
?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`**,**=**即阻塞被调试进程的线程。**=
```
?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**
* **再次遍历线程,双重保险,防止被调试进程又新建线程导致无法发送消息。**
****函数声明如下****
```
?NTSTATUS
?DbgkpSetProcessDebugObject (
? ? ?IN PEPROCESS Process,
? ? ?IN PDEBUG_OBJECT DebugObject,
? ? ?IN NTSTATUS MsgStatus,
? ? ?IN PETHREAD LastThread
? ?);
```
---
=**以下函数代码来自WRK,非IDA逆出**=
**首先判断传入MsgStatus,这个值是**`DbgkpPostFakeProcessCreateMessages`**函数的返回值,标志这个函数是不是执行成功。**
```
? if (!NT_SUCCESS (MsgStatus)) { //这个是前面插入DebugObject List时候是否成功
? ? ? ? ?LastThread = NULL;
? ? ? ? ?Status = MsgStatus;
? ?} else {
? ? ? ? ?Status = STATUS_SUCCESS;
? ?}
```
**设置被调试进程的DebugPort**
```
?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;//设置调试对象
```
**判断是否被调试进程新建线程,双重保险防止遗漏**
```
? ? ? ? ?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);`
```
?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**
```
? 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调试体系的创建调试对象,调试对象的链接以及假消息发送告一段落。剩下的编译异常分发,调试器处理了,这些基础知识也是自建调试通道的基础。**
...网站MarkDown语法支持也太垃圾了吧 好多问号呵 好文,但好多问号呵,楼主能处理一下吗? 李卓吾 发表于 2022-12-21 07:32
好文,但好多问号呵,楼主能处理一下吗?
论坛问题,真垃圾,技术论坛连适配Markdown都做不到...你想看完整的去某爱吧,我在那里也发了。以后不在这发了,让易语言大手子继续横行论坛吧 谢谢分享 oxygen1a1 发表于 2022-12-21 08:39
论坛问题,真垃圾,技术论坛连适配Markdown都做不到...你想看完整的去某爱吧,我在那里也发了。以后不在这发 ...
谢谢,我已经看到了! 看不懂。。哈哈 支持一波!
页:
[1]
2