吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 1905|回复: 13

[原创逆向图文] Win10_x64 21h2调试体系分析(一)

[复制链接]
oxygen1a1 发表于 2022-12-20 21:39 | 显示全部楼层 |阅读模式

记的笔记,发出来参考下,抛砖引玉,有错误多纠正。还望各位大佬别嘲笑????,个人认为做得还算详细,加个精华应该不过分,视情况更新下一篇。 平台如下:版本 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 ? ? [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调试体系的创建调试对象,调试对象的链接以及假消息发送告一段落。剩下的编译异常分发,调试器处理了,这些基础知识也是自建调试通道的基础。

评分

参与人数 14HB +11 THX +7 收起 理由
消逝的过去 + 1
再来壹瓶 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
祥雨 + 1
创客者V2.0 + 1
bing_mao + 1
霍华德 + 1 + 1
禽大师 + 1
WolfKing + 2 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
虚心学习 + 1
行行行行行行 + 1
shaokui123 + 1
zxjzzh + 2 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
李卓吾 + 1
sjtkxy + 1 + 1

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| oxygen1a1 发表于 2022-12-20 21:40 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
lies 发表于 2022-12-21 01:15 | 显示全部楼层

好多问号呵
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
sjtkxy 发表于 2022-12-21 05:40 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
李卓吾 发表于 2022-12-21 07:32 | 显示全部楼层

好文,但好多问号呵,楼主能处理一下吗?
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| oxygen1a1 发表于 2022-12-21 08:39 | 显示全部楼层

李卓吾 发表于 2022-12-21 07:32
好文,但好多问号呵,楼主能处理一下吗?

论坛问题,真垃圾,技术论坛连适配Markdown都做不到...你想看完整的去某爱吧,我在那里也发了。以后不在这发了,让易语言大手子继续横行论坛吧
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
李名的名字 发表于 2022-12-21 09:30 | 显示全部楼层

谢谢分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
李卓吾 发表于 2022-12-21 09:37 | 显示全部楼层

oxygen1a1 发表于 2022-12-21 08:39
论坛问题,真垃圾,技术论坛连适配Markdown都做不到...你想看完整的去某爱吧,我在那里也发了。以后不在这发 ...

谢谢,我已经看到了!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
老飞飞 发表于 2022-12-21 13:50 | 显示全部楼层

看不懂。。哈哈
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Cerolluo 发表于 2022-12-26 08:47 | 显示全部楼层

支持一波!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层
9层
10层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表