Windows内核分析——内核调试机制的实现(NtCreateDebugObject、DbgkpPostFakeProcessCreateMessages、DbgkpPostFakeThreadMessages分析)
本文主要分析内核中与调试相关的几个内核函数。
首先是NtCreateDebugObject函数,用于创建一个内核调试对象,分析程序可知,其实只是一层对ObCreateObject的封装,并初始化一些结构成员而已。
我后面会写一些与window对象管理方面的笔记,会分析到对象的创建过程。
1 | 来自WRK1.2 |
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 | <br>NTSTATUS NtCreateDebugObject ( OUT PHANDLE DebugObjectHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN ULONG Flags ) { NTSTATUS Status; HANDLE Handle; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; //检测这个宏所在位置的IRQL级别,相当于一个断言,确定当前IRQL允许分页 PAGED_CODE(); //获取当前操作模式是内核还是用户 PreviousMode = KeGetPreviousMode(); try { if (PreviousMode != KernelMode) { //验证是否可读写(验证参数正确性) ProbeForWriteHandle (DebugObjectHandle); } *DebugObjectHandle = NULL; } except (ExSystemExceptionFilter ()) { return GetExceptionCode (); } if (Flags & ~DEBUG_KILL_ON_CLOSE) { return STATUS_INVALID_PARAMETER; } // // 创建调试对象 // Status = ObCreateObject (PreviousMode, DbgkDebugObjectType, ObjectAttributes, PreviousMode, NULL, sizeof (DEBUG_OBJECT), 0, 0, &DebugObject); if (!NT_SUCCESS (Status)) { return Status; } ExInitializeFastMutex (&DebugObject->Mutex); //初始化调试内核对象中的调试事件链表 InitializeListHead (&DebugObject->EventList); KeInitializeEvent (&DebugObject->EventsPresent, NotificationEvent, FALSE); if (Flags & DEBUG_KILL_ON_CLOSE) { DebugObject->Flags = DEBUG_OBJECT_KILL_ON_CLOSE; } else { DebugObject->Flags = 0; } // 调试对象插入当前进程的句柄表 Status = ObInsertObject (DebugObject, NULL, DesiredAccess, 0, NULL, &Handle /*返回一个句柄*/ ); if (!NT_SUCCESS (Status)) { return Status; } //用异常处理进行安全复制 try { *DebugObjectHandle = Handle; } except(ExSystemExceptionFilter ()) { Status = GetExceptionCode (); } return Status; } |
29号写的windows调试机制学习笔记中分析了DebugActiveProcess
()函数,这个函数会调用到ZwDebugActiveProcess(),而这个函数最后又会执行到NtDebugActiveProcess()
而这个函数里关键的调用就是DbgkpPostFakeProcessCreateMessages()和DbgkpSetProcessDebugObject()
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 | NTSTATUS NtDebugActiveProcess ( IN HANDLE ProcessHandle, IN HANDLE DebugObjectHandle ) { NTSTATUS Status; KPROCESSOR_MODE PreviousMode; PDEBUG_OBJECT DebugObject; //返回调试对象的值 PEPROCESS Process; PETHREAD LastThread; //检测这个宏所在位置的IRQL级别,相当于一个断言,确定当前IRQL允许分页 PAGED_CODE (); PreviousMode = KeGetPreviousMode(); //用句柄使用进程内核对象 Status = ObReferenceObjectByHandle (ProcessHandle, PROCESS_SET_PORT, PsProcessType, PreviousMode, &Process, NULL); if (!NT_SUCCESS (Status)) { return Status; } // Don't let us debug ourselves or the system process. //验证参数合法性 if (Process == PsGetCurrentProcess () /*是否要调试自己*/ || Process == PsInitialSystemProcess /*不能调试system进程,pid为4*/ ) { ObDereferenceObject (Process); //消除计数 return STATUS_ACCESS_DENIED; } //用句柄使用调试内核对象 Status = ObReferenceObjectByHandle (DebugObjectHandle, DEBUG_PROCESS_ASSIGN, DbgkDebugObjectType, PreviousMode, &DebugObject, NULL); if (NT_SUCCESS (Status)) { // We will be touching process address space. Block process rundown. //加锁,防止进程在操作过程中退出 if (ExAcquireRundownProtection (&Process->RundownProtect)) { //这是个关键的函数,下面会针对分析 //作用是发送伪造的调试信息 //根据字面意思是发送伪造的进程创建调试信息 Status = DbgkpPostFakeProcessCreateMessages (Process, DebugObject, &LastThread); // Set the debug port. If this fails it will remove any faked messages. //上面的是WRK自带的注释 Status = DbgkpSetProcessDebugObject (Process, DebugObject, Status, LastThread); //释放锁 ExReleaseRundownProtection (&Process->RundownProtect); } else { Status = STATUS_PROCESS_IS_TERMINATING; } ObDereferenceObject (DebugObject); } ObDereferenceObject (Process); return Status; } |
注意DbgkpPostFakeProcessCreateMessages()是用于向调试器发生伪造的调试信息的。为什么要进行伪造调试信息呢?因为是为了获取完整的调试信息。
想象这样一个场景,你附加一个正在运行的进程。如果没有伪造调试信息那么调试器是无法正常工作的,因为你附加时进程已经运行起来了,之前的调试信息你是收不到的,
调试器也就无法正常工作了。伪造调试信息就是为了完整的把调试信息重新发送一遍给调试器而诞生的。
我们来看DbgkpPostFakeProcessCreateMessages()这个函数,我们可以看到这个其实也只是一层封装而已。有作用的是DbgkpPostFakeThreadMessages和DbgkpPostFakeModuleMessages这两个函数。
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 | NTSTATUS DbgkpPostFakeProcessCreateMessages ( IN PEPROCESS Process, IN PDEBUG_OBJECT DebugObject, IN PETHREAD *pLastThread ) { NTSTATUS Status; KAPC_STATE ApcState; PETHREAD Thread; PETHREAD LastThread; PAGED_CODE (); //附加到目标进程上 KeStackAttachProcess(&Process->Pcb, &ApcState); //发送假的线程创建信息(主要功能在这里实现) Status = DbgkpPostFakeThreadMessages (Process, DebugObject, NULL, &Thread, &LastThread); if (NT_SUCCESS (Status)) { //发送模块消息 Status = DbgkpPostFakeModuleMessages (Process, Thread, DebugObject); if (!NT_SUCCESS (Status)) { ObDereferenceObject (LastThread); LastThread = NULL; } ObDereferenceObject (Thread); } else { LastThread = NULL; } //解除附加 KeUnstackDetachProcess(&ApcState); *pLastThread = LastThread; return Status; } |
DbgkpPostFakeThreadMessages函数分析如下
1.发送进程创建伪造调试信息
2.依次发送每个线程的创建伪造调试信息
3.把每个信息放入调试对象的链表中的调试事件中
这里的伪造调试信息使用的是
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;
typedef struct _DBGKM_CREATE_PROCESS {
ULONG SubSystemKey;
HANDLE FileHandle;
PVOID BaseOfImage;
ULONG DebugInfoFileOffset;
ULONG DebugInfoSize;
DBGKM_CREATE_THREAD InitialThread;
} DBGKM_CREATE_PROCESS, *PDBGKM_CREATE_PROCESS;
注意这个结构最后被写入到调试事件中
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 170 171 172 173 174 175 176 177 178 | NTSTATUS DbgkpPostFakeThreadMessages ( IN PEPROCESS Process, IN PDEBUG_OBJECT DebugObject, IN PETHREAD StartThread, OUT PETHREAD *pFirstThread, OUT PETHREAD *pLastThread ) { NTSTATUS Status; PETHREAD Thread, FirstThread, LastThread; DBGKM_APIMSG ApiMsg; BOOLEAN First = TRUE; BOOLEAN IsFirstThread; PIMAGE_NT_HEADERS NtHeaders; ULONG Flags; NTSTATUS Status1; //验证IRQL PAGED_CODE (); LastThread = FirstThread = NULL; Status = STATUS_UNSUCCESSFUL; //注意,上面传过来的就是NULL!!! if (StartThread != NULL) { //StartThread!=NULL说明当前线程有ID即当前线程不是初始线程 First = FALSE; //不是第一个 FirstThread = StartThread; ObReferenceObject (FirstThread); } else { //==0说明当前线程是初始线程。也说明是在创建进程。 StartThread = PsGetNextProcessThread (Process, NULL); //这里获得的就是初始线程 First = TRUE; //是第一个 } for (Thread = StartThread; Thread != NULL; //遍历进程的每一个线程 Thread = PsGetNextProcessThread (Process, Thread)) { //设置调试事件不等待 Flags = DEBUG_EVENT_NOWAIT; if (LastThread != NULL) { ObDereferenceObject (LastThread); } //用来记录最后一个线程 LastThread = Thread; ObReferenceObject (LastThread); //锁住线程,防止线程终止 if (ExAcquireRundownProtection (&Thread->RundownProtect)) { Flags |= DEBUG_EVENT_RELEASE; //判断获得的线程是否是系统的线程 if (!IS_SYSTEM_THREAD (Thread)) { //暂停线程 Status1 = PsSuspendThread (Thread, NULL); if (NT_SUCCESS (Status1)) { //暂停成功,加一个暂停标记 Flags |= DEBUG_EVENT_SUSPEND; } } } else { //获取锁失败,加上标记 Flags |= DEBUG_EVENT_PROTECT_FAILED; } //构造一个ApiMsg结构(DBGKM_APIMSG类型) RtlZeroMemory (&ApiMsg, sizeof (ApiMsg)); //如果申请成功,并且这个线程是第一个线程 //说明是进程创建 //这里会发生进程创建伪造消息 if (First && (Flags&DEBUG_EVENT_PROTECT_FAILED) == 0 && !IS_SYSTEM_THREAD (Thread) && Thread->GrantedAccess != 0) { IsFirstThread = TRUE; //说明是第一线程创建兼进程创建 } else { IsFirstThread = FALSE; } if (IsFirstThread) { //这里设置了进程创建伪造消息的结构 ApiMsg.ApiNumber = DbgKmCreateProcessApi; if (Process->SectionObject != NULL) // { //把进程主模块的文件句柄保存在伪造消息的结构中 ApiMsg.u.CreateProcessInfo.FileHandle = DbgkpSectionToFileHandle (Process->SectionObject); } else { ApiMsg.u.CreateProcessInfo.FileHandle = NULL; } //把进程主模块基址保存在伪造信息的结构中 ApiMsg.u.CreateProcessInfo.BaseOfImage = Process->SectionBaseAddress; //用异常处理增强稳定性 try { //获得PE结构的NT头部 NtHeaders = RtlImageNtHeader(Process->SectionBaseAddress); if (NtHeaders) { ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; // Filling this in breaks MSDEV! //解析NT头部中的调试信息,放入伪造信息的结构中 ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = NtHeaders->FileHeader.PointerToSymbolTable; ApiMsg.u.CreateProcessInfo.DebugInfoSize = NtHeaders->FileHeader.NumberOfSymbols; } } except (EXCEPTION_EXECUTE_HANDLER) { ApiMsg.u.CreateProcessInfo.InitialThread.StartAddress = NULL; ApiMsg.u.CreateProcessInfo.DebugInfoFileOffset = 0; ApiMsg.u.CreateProcessInfo.DebugInfoSize = 0; } } else { //不是第一个,说明是线程创建,设置一个线程创建伪造信息结构 ApiMsg.ApiNumber = DbgKmCreateThreadApi; ApiMsg.u.CreateThread.StartAddress = Thread->StartAddress; } //把上面构造的消息包插入到队列中 Status = DbgkpQueueMessage (Process, Thread, &ApiMsg, Flags, DebugObject); //错误处理 if (!NT_SUCCESS (Status)) { if (Flags&DEBUG_EVENT_SUSPEND) { PsResumeThread (Thread, NULL); } if (Flags&DEBUG_EVENT_RELEASE) { ExReleaseRundownProtection (&Thread->RundownProtect); } if (ApiMsg.ApiNumber == DbgKmCreateProcessApi && ApiMsg.u.CreateProcessInfo.FileHandle != NULL) { ObCloseHandle (ApiMsg.u.CreateProcessInfo.FileHandle, KernelMode); } PsQuitNextProcessThread (Thread); break ; } else if (IsFirstThread) { First = FALSE; //已经处理完第一次了 ObReferenceObject (Thread); FirstThread = Thread; } } if (!NT_SUCCESS (Status)) { if (FirstThread) { ObDereferenceObject (FirstThread); } if (LastThread != NULL) { ObDereferenceObject (LastThread); } } else { if (FirstThread) { *pFirstThread = FirstThread; *pLastThread = LastThread; } else { Status = STATUS_UNSUCCESSFUL; } } 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 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 | NTSTATUS DbgkpQueueMessage ( IN PEPROCESS Process, IN PETHREAD Thread, IN OUT PDBGKM_APIMSG ApiMsg, IN ULONG Flags, IN PDEBUG_OBJECT TargetDebugObject ) { PDEBUG_EVENT DebugEvent; //ApiMsg最后会封装入这个结构内 DEBUG_EVENT StaticDebugEvent; PDEBUG_OBJECT DebugObject; NTSTATUS Status; PAGED_CODE (); //判断是同步事件还是异步事件…… if (Flags&DEBUG_EVENT_NOWAIT) { //异步事件这样处理 //给调试事件分配空间 DebugEvent = ExAllocatePoolWithQuotaTag (NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, sizeof (*DebugEvent), 'EgbD' ); if (DebugEvent == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } //设置DEBUG_EVENT结构的信息 DebugEvent->Flags = Flags|DEBUG_EVENT_INACTIVE; ObReferenceObject (Process); ObReferenceObject (Thread); DebugEvent->BackoutThread = PsGetCurrentThread (); DebugObject = TargetDebugObject; } else { //同步事件这样处理 //同步处理时没有为结构开辟pool内存 DebugEvent = &StaticDebugEvent; //直接给定一个局部变量,因为在函数内就处理完成了,局部变量就可以满足条件 DebugEvent->Flags = Flags; //获取同步锁 ExAcquireFastMutex (&DbgkpProcessDebugPortMutex); DebugObject = Process->DebugPort; // // See if this create message has already been sent. // if (ApiMsg->ApiNumber == DbgKmCreateThreadApi || ApiMsg->ApiNumber == DbgKmCreateProcessApi) { if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_CREATION_MSG) { DebugObject = NULL; } } // // See if this exit message is for a thread that never had a create // if (ApiMsg->ApiNumber == DbgKmExitThreadApi || ApiMsg->ApiNumber == DbgKmExitProcessApi) { if (Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_SKIP_TERMINATION_MSG) { DebugObject = NULL; } } } KeInitializeEvent (&DebugEvent->ContinueEvent, SynchronizationEvent, FALSE); //填充DebugEvent DebugEvent->Process = Process; DebugEvent->Thread = Thread; DebugEvent->ApiMsg = *ApiMsg; DebugEvent->ClientId = Thread->Cid; if (DebugObject == NULL) { Status = STATUS_PORT_NOT_SET; } else { // // We must not use a debug port thats got no handles left. // //获取一个调试对象的锁 ExAcquireFastMutex (&DebugObject->Mutex); // // If the object is delete pending then don't use this object. // if ((DebugObject->Flags&DEBUG_OBJECT_DELETE_PENDING) == 0) { //把调试事件插入调试对象中的链表里 InsertTailList (&DebugObject->EventList, &DebugEvent->EventList); // // Set the event to say there is an unread event in the object // if ((Flags&DEBUG_EVENT_NOWAIT) == 0) { //如果是同步的,通知调试器去处理了 KeSetEvent (&DebugObject->EventsPresent, 0, FALSE); } Status = STATUS_SUCCESS; } else { Status = STATUS_DEBUGGER_INACTIVE; } ExReleaseFastMutex (&DebugObject->Mutex); } if ((Flags&DEBUG_EVENT_NOWAIT) == 0) { ExReleaseFastMutex (&DbgkpProcessDebugPortMutex); if (NT_SUCCESS (Status)) { //等待调试器返回信息 KeWaitForSingleObject (&DebugEvent->ContinueEvent, Executive, KernelMode, FALSE, NULL); Status = DebugEvent->Status; *ApiMsg = DebugEvent->ApiMsg; } } else { if (!NT_SUCCESS (Status)) { ObDereferenceObject (Process); ObDereferenceObject (Thread); ExFreePool (DebugEvent); } } return Status; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· .NET 8.0 + Linux 香橙派,实现高效的 IoT 数据采集与控制解决方案
· .NET中 泛型 + 依赖注入 的实现与应用