首页 > 其他分享 >Win10_x64 21h2调试体系分析

Win10_x64 21h2调试体系分析

时间:2024-04-24 16:13:20浏览次数:24  
标签:21h2 DebugObject 代码 x64 mov rax Win10 DebugEvent 调试

参考 https://www.52pojie.cn/thread-1728894-1-1.html

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

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

image.png (22.14 KB, 下载次数: 0)

下载附件

2022-12-20 21:32 上传

 






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     [rsp+arg_0], rbx
push    rdi             ; 保留非易失寄存器
sub     rsp, 20h
mov     ebx, ecx
call    cs:__imp_DbgUiConnectToDbg ; 先创建一个调试对象
nop     dword ptr [rax+rax+00h]
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 [rax+rax+00h]
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     [rax+(_TEB.DbgSsReserved+8)], rcx ; 判断是否已经有调试
jnz     short HasDebugge

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

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

 复制代码 隐藏代码
mov     [rsp+28h], rcx
lea     r8, [rsp+20h]   ; 传地址 其实是OBJECT_ATTRIBUTES
mov     [rsp+38h], ecx
xorps   xmm0, xmm0
mov     [rsp+30h], rcx
mov     r9d, 1          ; 传参
movdqu  xmmword ptr [rsp+40h], xmm0
mov     dword ptr [rsp+20h], 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 [rsp+48h], 0
lea     rax, [rsp+88h+pObject]
mov     [rsp+40h], rax  ; pObject
and     dword ptr [rsp+38h], 0
and     dword ptr [rsp+30h], 0
mov     dword ptr [rsp+28h], 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, [rsp+88h+pObject] ; 调试对象进行赋值
mov     [rbx+_DEBUG_PORT.Mutex.Count], 1 ; 参考WRK的DEBUG_PORT对象
and     [rbx+_DEBUG_PORT.Mutex.Owner], 0 ; 初始化互斥体,用于插入链表时候的同步
and     [rbx+_DEBUG_PORT.Mutex.Contention], 0
lea     rcx, [rbx+_DEBUG_PORT.Mutex.Event] ; Event
xor     r8d, r8d        ; State
lea     edx, [r8+1]     ; Type
call    KeInitializeEvent
lea     rax, [rbx+_DEBUG_PORT.EventList]
mov     [rax+8], rax
mov     [rax], 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 [rbx+_DEBUG_PORT.Flags], 2  
jmp     short loc_140883589
Equal:
and     dword ptr [rbx+_DEBUG_PORT.Flags], 0

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

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

 复制代码 隐藏代码
mov     rax, gs:188h
mov     rcx, [rax+_ETHREAD.Tcb._union_90.ApcState.Process]
mov     rax, [rcx+_EPROCESS.WoW64Process] ; 这是调试器的线程
test    rax, rax
jz      short x64Bit
or      dword ptr [rbx+_DEBUG_PORT.Flags], 4 ; 即flags & 4 就是wow64
x64Bit:
xxxxx

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

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

 复制代码 隐藏代码
mov     r8d, r14d       ; r14就是R3传过来的DesriedAccess
xor     edx, edx
mov     rcx, [rsp+88h+pObject]
call    ObInsertObjectEx ; InsertObject的作用就是把对象查到句柄表里面
mov     ecx, eax
test    eax, eax
js      short Ret
mov     rax, [rsp+88h+Handle] 
mov     [rdi], 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[1]);
  if ( status >= 0 )
  {
    status = DbgUiIssueRemoteBreakin(hProcess);
    if ( status < 0 )
      ZwRemoveProcessDebug(hProcess, NtCurrentTeb()->DbgSsReserved[1]);
  }
  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[0x7FFE02D4] & 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[1]);

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

0x1-2-1 nt!NtDebugActiveProcess

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

声明如下:

 复制代码 隐藏代码
NTSTATUS
NtDebugActiveProcess (
    IN HANDLE ProcessHandle,
    IN HANDLE DebugObjectHandle
    );

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

 复制代码 隐藏代码
mov     r8, cs:PsProcessType
and     qword ptr [r11+18h], 0
mov     bpl, byte ptr [rax+_ETHREAD.Tcb._union_171.UserAffinity.Reserved] ; PreviousMode
lea     rax, [r11+18h]  ; pObject rsp+0x80
and     qword ptr [r11-28h], 0
mov     r9b, bpl
mov     [r11-40h], rax
mov     dword ptr [rsp+68h+Object], 4F676244h
call    ObReferenceObjectByHandleWithTag ; 获取进程对象

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

  • 是否调试自己
  • 是否是系统进程
  • 是否是wow64
 复制代码 隐藏代码
mov     rax, gs:188h
mov     rdi, [rsp+68h+pDebugProcess]
mov     rsi, [rax+_ETHREAD.Tcb._union_90.ApcState.Process]
cmp     rdi, rsi        ; 判断是不是在调试自己
jz      DebugProcessErr
cmp     rdi, cs:PsInitialSystemProcess ; 判断一下是不是这个进程 就是system进程
jz      DebugProcessErr
mov     rax, [rdi+_EPROCESS.WoW64Process]
test    rax, rax
jz      x64Bit 
;进行调试进程被调试进程检查
; 如果被调试64 调试32 无法调试

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

 复制代码 隐藏代码
GetDebugObject:         ; ObjectType
mov     r8, cs:DbgkDebugObjectType
lea     rax, [rsp+68h+pDebugObject]
and     [rsp+68h+var_40], 0
mov     r9b, bpl        ; AccessMode
and     [rsp+68h+pDebugObject], 0
mov     edx, 2          ; DesiredAccess
mov     rcx, r14        ; Handle
mov     [rsp+68h+Object], rax ; Object
call    ObReferenceObjectByHandle

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

 复制代码 隐藏代码
lea     rbp, [rdi+_EPROCESS.RundownProtect]
mov     rcx, rbp
call    ExAcquireRundownProtection_0 ; 获取被调试对象的RunDown锁
mov     rsi, [rsp+68h+pDebugObject] ; 这个可以反调试 但是不要用
test    al, al
jz      short RunDownProtectErr

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

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

 复制代码 隐藏代码
lea     r8, [rsp+68h+var_28]
mov     rdx, rsi        ; DebugObject
mov     rcx, rdi        ; DebugProcess
call    DbgkpPostFakeProcessCreateMessages ; 创建进程创建的假消息 其实就是进程已经创建过了 还得接受一下消息
                        ; 模拟一下正常调试信息
                        ; 注意是创建,不会发送!
mov     r9, [rsp+68h+var_28]
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[11] = DbgkpSectionToFileHandle(Section);// 就是返回文件句柄的
else
  *(_QWORD *)&ApiMsg.u[11] = 0i64;
*(_QWORD *)&ApiMsg.u[19] = Process_2->SectionBaseAddress;// BaseOfImage
KeStackAttachProcess(Process_2, (_KAPC_STATE *)&Apc);
ntHead = (IMAGE_NT_HEADERS64 *)RtlImageNtHeader(Process_2->SectionBaseAddress);
if ( ntHead )
{
    *(_QWORD *)&ApiMsg.u[43] = 0i64;
    *(_DWORD *)&ApiMsg.u[27] = ntHead->FileHeader.PointerToSymbolTable;// 符号表
    *(_DWORD *)&ApiMsg.u[31] = 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[16] = *(_OWORD *)&ApiMsg_2->h[16];
    *(_OWORD *)&DebugEvent_1_1->h[32] = *(_OWORD *)&ApiMsg_2->h[32];
    *(_OWORD *)&DebugEvent_1_1->u[3] = *(_OWORD *)&ApiMsg_2->u[3];
    *(_OWORD *)&DebugEvent_1_1->u[19] = *(_OWORD *)&ApiMsg_2->u[19];
    *(_OWORD *)&DebugEvent_1_1->u[35] = *(_OWORD *)&ApiMsg_2->u[35];
    *(_OWORD *)&DebugEvent_1_1->u[51] = *(_OWORD *)&ApiMsg_2->u[51];
    DebugEvent_1_1 = (_DBGKM_APIMSG *)((char *)DebugEvent_1_1 + 128);
    v19 = *(_OWORD *)&ApiMsg_2->u[67];
    ApiMsg_2 = (_DBGKM_APIMSG *)((char *)ApiMsg_2 + 128);
    *(_OWORD *)&DebugEvent_1_1[-1].u[211] = 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调试体系的创建调试对象,调试对象的链接以及假消息发送告一段落。剩下的编译异常分发,调试器处理了,这些基础知识也是自建调试通道的基础。

 

标签:21h2,DebugObject,代码,x64,mov,rax,Win10,DebugEvent,调试
From: https://www.cnblogs.com/kuangke/p/18155682

相关文章

  • Windows 10 x64 异常分发
    参考https://www.52pojie.cn/thread-1663524-1-1.htmlWindows10  x64异常分发系统版本: 系统版本.png(28.46KB,下载次数:0)下载附件2022-7-2000:49上传 这个版本吾爱破解论坛有人分享了,也可以下载我提供的ntdll.dll和ntoskrnl.exe(包括pdb符号文件)......
  • win10系统腾讯会议连接蓝牙耳机(小米buds3)没有声音
    台式机放在工位地下,用有线耳机实在太麻烦。台式机如何连接蓝牙耳机了。只需要在拼多多上面买个蓝牙适配器。5-10块钱哪种就行(蓝5.1就够了,有钱就买最好的)添加后,听音乐啥的都没有问题。但是今天腾讯会议开会,居然灭有声音。连接蓝牙耳机后,腾讯会议还是外放,可能是由于声音模式未切......
  • MySQL-8.0.33-winx64 解压版安装 [Windows]
    1、下载安装包mysql-8.0.33-winx64.ziphttps://dev.mysql.com/downloads/file/?id=5182202、安装解压mysql-8.0.33-winx64.zip(至:C:\app\mysql-8.0.33-winx64);创建my.ini文件;默认解压目录无my.ini文件,需自己创建;进入目录C:\app\mysql-8.0.33-winx64,创建my.ini,文件内容......
  • 重装Win10系统
    参考自教程重装Win10-百度重装Win10-知乎BIOS设置U盘启动-百度知道安装步骤制作Win10的安装盘一个内存大于等于8G的U盘-Win10操作系统会下载到这个盘中,在Microsoft官网下载时,Microsoft会先将U盘格式化,因此需要一块空的U盘官网下载Win10Win10-官网下载此时会将......
  • 解决WIN11/WIN10找不到secpol.msc的问题
    原文链接:https://blog.csdn.net/m0_65048141/article/details/134948534一、下载https://www.mediafire.com/file/geaxzi5rc86oqv9/gpedit-enabler.zip/file二、安装解压压缩包,右键管理员打开,等待运行结束三、打开WIN+R搜索secpol.msc,对需要修改的进行修改即可 ......
  • Windows 上小狼毫输入法的上手教程,右下角出现禁用问题(win10解决)
    小玲以前在使用Windows系统时,一直都是用系统自带的输入法——微软拼音的,而且小玲没有那种安装第三方输入法的习惯。但是有一天,小玲在网上看到好多人推荐的Rime输入法,抱着试一下的想法,小玲试用了一下这款输入法。没想到这款输入法的可定制性真的高。小玲从此就离不开这款输入法......
  • win10完美去除小箭头
    win10完美去除小箭头1.去掉小箭头regadd"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIcons"/v29/d"%systemroot%\system32\imageres.dll,197"/treg_sz/ftaskkill/f/imexplorer.exeattrib-s-r-h"......
  • win10 vscode 插件使用
     翻译搜索复制......
  • win10共享打印机服务机以及客户机设置方法
    服务机确保网络状态是专用设置-网络-状态-属性-网络选择为专用 在打印机所在的计算机上设置共享。首先,确保打印机已经正确连接到计算机并已安装适当的驱动程序,进入“控制面板”,找到“设备和打印机”,右键点击想要共享的打印机,选择“打印机属性”,在“打印机属性”窗口中,切换......
  • Win10删除"此电脑"下视频,图片等文件夹
    WIN+R打开运行,输入regedit打开注册表。找到(直接复制)HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace不同的值对应不同的文件夹{24ad3ad4-a569-4530-98e1-ab02f9417aa8}--图片文件夹{0DB7E03F-FC29-4DC6-9020-FF41B59E51......