-
-
[原创]Windows内核学习笔记之调试(上)
-
2022-1-2 21:14 10471
-
一.调试API的使用
1.创建调试关系
既然是调试,那么必然存在调试进程和被调试进程。调试进程和被调试进程的关系可以通过函数CreateProcess来定义,该函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 | BOOL WINAPI CreateProcess( __in_opt LPCTSTR lpApplicationName, __inout_opt LPTSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCTSTR lpCurrentDirectory, __in LPSTARTUPINFO lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation ); |
其中第六个参数dwCreationFlags的取值有两个重要的常量,分别为DEBUG_PROCESS和DEBUG_ONLY_THIS_PROCESS。DEBUG_PROCESS的作用是被创建的进程处于调试状态。如果一同指定DEBUG_ONLY_THIS_PROCESS的话,那么就只能调试被创建的进程,而不能调试被调试进程创建出来的进程。
另一种创建调试关系的方法是DebugActiveProcess函数,该函数定义如下:
1 2 3 | BOOL WINAPI DebugActiveProcess( __in DWORD dwProcessId ); |
这个函数的功能是将调试进程附加到被调试进程上。该函数的参数只有一个,该参数指定了被调试进程的进程PID。从函数名和参数可以看出,该函数是和一个已经被创建的进程来建立调试关系的。
想要让已建立关系的两个进程断开调试关系,可以使用如下的API:
1 2 3 | BOOL WINAPI DebugActiveProcessStop( __in DWORD dwProcessId ); |
该函数只有一个参数,就是被调试进程的ID号。使用该函数可以在不影响调试器进程和被调试进程的正常运行的情况下,将两者的关系接触。但是有一个前提,被调试进程需要处于运行状态,而不是中断状态。如果被调试进程处于中断状态时和调试进程解除调试关系,由于被调试进程无法运行而导致退出。
2.判断进程是否处于调试状态
以下函数用来判断进程是否处于调试状态:
1 | BOOL WINAPI IsDebuggerPresent(void); |
如果处于调试状态,该函数返回非0值,否则返回0。
3.断点异常函数
可以通过如下函数来在当前进程中产生一个"int 3"断点异常,如果当前进程未处于调试状态,那么这个异常就会被系统接管,通常情况下会导致进程终止。
1 | void WINAPI DebugBreak(void); |
以下函数可以在指定进程中产生一个断点异常:
1 2 3 | BOOL WINAPI DebugBreakProcess( __in HANDLE Process ); |
参数是要产生断点异常的进程句柄。
以下函数将使调试进程强制退出,将控制权转移至调试器。于ExitProcess不同的是,该函数在退出前会调试一个int 3断点:
1 2 3 | void WINAPI FatalExit( __in int ExitCode ); |
参数是进程退出码。
4.调试循环
调试器不断地对被调试目标进程进行捕获异常调试信息,有点类似于Win32应用程序地消息循环,但是又有所不同。调试器在捕获到调试信息后进行相应的处于,然后恢复线程,使之继续运行。
用来等待捕获被调试进程调试事件的函数如下:
1 2 3 4 | BOOL WINAPI WaitForDebugEvent( __out LPDEBUG_EVENT lpDebugEvent, __in DWORD dwMilliseconds ); |
参数 | 含义 |
---|---|
lpDebugEvent | 用于保存调试事件 |
dwMilliseconds | 用于指定超时的时间,如果是INFINITE则表示无线等待 |
调试器捕获到调试事件后,会对被调试的目标进程中产生调试事件的线程进行挂起。调试器对被调试目标进程进行相应的处理后,可以使用以下函数对先前被挂起的线程进行恢复:
1 2 3 4 5 | BOOL WINAPI ContinueDebugEvent( __in DWORD dwProcessId, __in DWORD dwThreadId, __in DWORD dwContinueStatus ); |
参数 | 含义 |
---|---|
dwProcessId | 表示被调试进程的进程标识符 |
dwThreadId | 表示准备恢复挂机线程的线程标识符 |
dwContinueStatus | 该参数指定了该线程以何种方式继续,其取值为DEBUG_EXCEPTION_NOT_HANDLED和DBG_CONTINUE。对于这两个值来说,通常情况下并没有什么差别。但是当遇到调试事件中的调试码为EXCEPTION_DEBUG_EVENT时,这两个常量会有不同的动作。如果使用前者,则调试器进程会忽略该异常,Windows会使用被调试进程的异常处理函数对异常进行处理;如果使用后者,那么需要调试器进程对异常进行处理 ,然后继续运行 |
另外还有一个比较常用的函数如下:
1 2 3 | void WINAPI OutputDebugString( __in_opt LPCTSTR lpOutputString ); |
该函数可以将一个字符串传递给调试器显示,参数指向的就是要显示的以"00"作为结尾的字符串的指针。
二.调试对象
当调试器与调试子系统建立连接时,调试子系统会为其创建一个DEBUG_OBJECT调试对象,并将其保存在调试器当前线程的线程环境块(TEB)偏移0x0F24的DbgSsReserved[1]字段中。该字段中保存的调试对象是这个调试器线程区别于其他普通线程的重要标志。由于TEB是用户层的数据结构,所以此时DbgSsReserved[1]中保存的其实是调试对象的句柄,而不是调试对象的地址。
调试对象通常由调试器进程创建,为了起到联系被调试进程和调试进程的作用,还需要将其设置到被调试进程EPROCESS结构的DebugPort字段中。DebugPort是内核进程对象结构的一个有关调试的重要字段。如果一个进程不在调试状态,那么该字段为NULL,否则,该字段是一个指针,指向的就是DEBUG_OBJECT调试对象。
调试对象的结构如下:
1 2 3 4 5 6 7 | typedef struct _DEBUG_OBJECT { KEVENT EventsPresent; FAST_MUTEX Mutex; LIST_ENTRY StateEventListEntry; ULONG Flags; }DEBUG_OBJECT, * PDEBUG_OBJECT; |
偏移 | 名称 | 作用 |
---|---|---|
0x00 | EventsPresent | 用于指示调试事件发生的事件对象,用来同步调试器进程和被调试进程,调试子系统服务器通过设置此事件来通知调试器读取消息队列中的调试信息 |
0x10 | Mutex | 用于同步的互斥对象,用来锁定对这个数据结构的访问,以防止对各线程同时读写造成数据错误 |
0x30 | StateEventListEntry | 保存调试事件的链表,被称为调试消息队列 |
0x38 | Flags | 包含多个标志位,比如,位1代表结束调试会话时是否终止被调试进程 |
接下来通过查看DebugActiveProcess函数的实现来验证上述内容。该函数的调用非常简单,只需要将被调试进程的PID传递给函数,系统便会为调用这个API的进程与传入的参数PID指定的进程建立起调试关系。
函数一开始,会调用DbgUiConnectToDbg使调用进程与调试子系统建立连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .text: 7C85B0FB ; BOOL __stdcall DebugActiveProcess(DWORD dwProcessId) .text: 7C85B0FB public _DebugActiveProcess@ 4 .text: 7C85B0FB _DebugActiveProcess@ 4 proc near ; DATA XREF: .text:off_7C802654↑o .text: 7C85B0FB .text: 7C85B0FB dwProcessId = dword ptr 8 .text: 7C85B0FB .text: 7C85B0FB mov edi, edi .text: 7C85B0FD push ebp .text: 7C85B0FE mov ebp, esp .text: 7C85B100 call _DbgUiConnectToDbg@ 0 ; DbgUiConnectToDbg() .text: 7C85B105 test eax, eax .text: 7C85B107 jge short loc_7C85B113 ; 调用成功则跳转 .text: 7C85B109 push eax ; Status .text: 7C85B10A call _BaseSetLastNTError@ 4 ; BaseSetLastNTError(x) .text: 7C85B10F xor eax, eax .text: 7C85B111 jmp short loc_7C85B145 |
DbgUiConnectToDbg是ntdll.dll中的函数,该函数首先就判断TEB结构的DbgSsReserved[1]中是否已保存了调试对象句柄,如果保存了就会退出函数
1 2 3 4 5 6 7 8 9 10 11 12 13 | .text: 7C96FEF1 public DbgUiConnectToDbg .text: 7C96FEF1 DbgUiConnectToDbg proc near ; DATA XREF: .text:off_7C923428↑o .text: 7C96FEF1 .text: 7C96FEF1 var_ObjAttributes = _OBJECT_ATTRIBUTES ptr - 18h .text: 7C96FEF1 .text: 7C96FEF1 mov edi, edi .text: 7C96FEF3 push ebp .text: 7C96FEF4 mov ebp, esp .text: 7C96FEF6 sub esp, 18h .text: 7C96FEF9 xor ecx, ecx .text: 7C96FEFB mov eax, large fs: 18h ; 获取TEB地址 .text: 7C96FF01 cmp [eax + 0F24h ], ecx ; 判断DbgSsReserved[ 1 ]中是否保存了内容 .text: 7C96FF07 jnz short loc_7C96FF3D ; 如果保存了则退出函数 |
如果没保存就会通过调用内核函数ZwCreateDebugObject来创建调试对象,此时第一个参数指向的就是TEB的DbgSsRevered[1],当函数成功创建调试对象以后,就会将句柄赋值到参数所指的地址中,然后退出函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .text: 7C96FF09 mov [ebp + var_ObjAttributes.Length], 18h .text: 7C96FF10 mov [ebp + var_ObjAttributes.RootDirectory], ecx .text: 7C96FF13 mov [ebp + var_ObjAttributes.Attributes], ecx .text: 7C96FF16 mov [ebp + var_ObjAttributes.ObjectName], ecx .text: 7C96FF19 mov [ebp + var_ObjAttributes.SecurityDescriptor], ecx .text: 7C96FF1C mov [ebp + var_ObjAttributes.SecurityQualityOfService], ecx .text: 7C96FF1F mov eax, large fs: 18h ; 将TEB地址赋给eax .text: 7C96FF25 push 1 .text: 7C96FF27 lea ecx, [ebp + var_ObjAttributes] .text: 7C96FF2A push ecx .text: 7C96FF2B push 1F000Fh .text: 7C96FF30 add eax, 0F24h ; eax指向DbgSsReserved[ 1 ]的地址 .text: 7C96FF35 push eax .text: 7C96FF36 call ZwCreateDebugObject ; 创建DebugObject对象 .text: 7C96FF3B mov ecx, eax .text: 7C96FF3D .text: 7C96FF3D loc_7C96FF3D: ; CODE XREF: DbgUiConnectToDbg + 16 ↑j .text: 7C96FF3D mov eax, ecx .text: 7C96FF3F leave .text: 7C96FF40 retn .text: 7C96FF40 DbgUiConnectToDbg endp |
在内核函数ZwCreateObject中,首先会传入第一个参数是否可写以及Flags字段进行验证
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 | PAGE: 0058B2B6 ; NTSTATUS __stdcall NtCreateDebugObject(PHANDLE DebugHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, ULONG Flags) PAGE: 0058B2B6 _NtCreateDebugObject@ 16 proc near ; DATA XREF: .text: 0040D8A4 ↑o PAGE: 0058B2B6 PAGE: 0058B2B6 var_2C = dword ptr - 2Ch PAGE: 0058B2B6 var_28 = dword ptr - 28h PAGE: 0058B2B6 Object = dword ptr - 24h PAGE: 0058B2B6 PreviousMode = byte ptr - 20h PAGE: 0058B2B6 Handle = dword ptr - 1Ch PAGE: 0058B2B6 ms_exc = CPPEH_RECORD ptr - 18h PAGE: 0058B2B6 DebugHandle = dword ptr 8 PAGE: 0058B2B6 DesiredAccess = dword ptr 0Ch PAGE: 0058B2B6 ObjectAttributes = dword ptr 10h PAGE: 0058B2B6 Flags = dword ptr 14h PAGE: 0058B2B6 push 1Ch PAGE: 0058B2B8 push offset stru_4585F0 PAGE: 0058B2BD call __SEH_prolog PAGE: 0058B2C2 mov eax, large fs: 124h PAGE: 0058B2C8 mov al, [eax + _KTHREAD.PreviousMode] PAGE: 0058B2CE mov [ebp + PreviousMode], al PAGE: 0058B2D1 xor ebx, ebx PAGE: 0058B2D6 mov edi, [ebp + DebugHandle] PAGE: 0058B2D9 cmp al, bl PAGE: 0058B2DB jz short loc_58B2EC PAGE: 0058B2DD mov eax, _MmUserProbeAddress PAGE: 0058B2E2 cmp edi, eax PAGE: 0058B2E4 jb short loc_58B2E8 PAGE: 0058B2E6 mov [eax], ebx PAGE: 0058B2E8 PAGE: 0058B2E8 loc_58B2E8: ; CODE XREF: NtCreateDebugObject(x,x,x,x) + 2E ↑j PAGE: 0058B2E8 mov eax, [edi] PAGE: 0058B2EA mov [edi], eax PAGE: 0058B2EC PAGE: 0058B2EC loc_58B2EC: ; CODE XREF: NtCreateDebugObject(x,x,x,x) + 25 ↑j PAGE: 0058B2EC mov [edi], ebx PAGE: 0058B2F2 test [ebp + Flags], 0FFFFFFFEh PAGE: 0058B2F9 jz short loc_58B305 PAGE: 0058B2FB mov eax, STATUS_INVALID_PARAMETER PAGE: 0058B300 jmp loc_58B3D8 |
如果验证通过,调用ObCreateObject来创建一个DbgkDebugObjectType的调试对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | PAGE: 0058B305 loc_58B305: ; CODE XREF: NtCreateDebugObject(x,x,x,x) + 43 ↑j PAGE: 0058B305 lea eax, [ebp + Object ] PAGE: 0058B308 push eax ; Object PAGE: 0058B309 push ebx ; NonPagedPoolCharge PAGE: 0058B30A push ebx ; PagePoolCharge PAGE: 0058B30B push 3Ch ; ObjectBodySize PAGE: 0058B30D push ebx ; ParentContext PAGE: 0058B30E push dword ptr [ebp + PreviousMode] ; OwnershipMode PAGE: 0058B311 push [ebp + ObjectAttributes] ; ObjectAttributes PAGE: 0058B314 push _DbgkDebugObjectType ; ObjectType PAGE: 0058B31A push dword ptr [ebp + PreviousMode] ; PreviousMode PAGE: 0058B31D call _ObCreateObject@ 36 ; ObCreateObject(x,x,x,x,x,x,x,x,x) PAGE: 0058B322 cmp eax, ebx PAGE: 0058B324 jl loc_58B3D8 |
如果成功创建调试对象,就会为创建的调试对象赋值
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 | PAGE: 0058B32A mov eax, [ebp + Object ] ; 将调试对象地址赋给eax PAGE: 0058B32D xor esi, esi PAGE: 0058B32F inc esi PAGE: 0058B330 mov [eax + _DEBUG_OBJECT.Mutex.Count], esi PAGE: 0058B333 mov [eax + _DEBUG_OBJECT.Mutex.Owner], ebx PAGE: 0058B336 mov [eax + _DEBUG_OBJECT.Mutex.Contention], ebx PAGE: 0058B339 mov [eax + _DEBUG_OBJECT.Mutex.Event.Header. Type ], 1 PAGE: 0058B33D mov [eax + _DEBUG_OBJECT.Mutex.Event.Header.Size], 4 PAGE: 0058B341 mov [eax + _DEBUG_OBJECT.Mutex.Event.Header.SignalState], ebx PAGE: 0058B344 lea ecx, [eax + _DEBUG_OBJECT.Mutex.Event.Header.WaitListHead] PAGE: 0058B347 mov [ecx + LIST_ENTRY.Blink], ecx PAGE: 0058B34A mov [ecx + LIST_ENTRY.Flink], ecx PAGE: 0058B34C lea ecx, [eax + _DEBUG_OBJECT.EventList] PAGE: 0058B34F mov [ecx + LIST_ENTRY.Blink], ecx PAGE: 0058B352 mov [ecx + LIST_ENTRY.Flink], ecx PAGE: 0058B354 mov [eax + _DEBUG_OBJECT.EventsPresent.Header. Type ], bl PAGE: 0058B356 mov [eax + _DEBUG_OBJECT.EventsPresent.Header.Size], 4 PAGE: 0058B35A mov [eax + _DEBUG_OBJECT.EventsPresent.Header.SignalState], ebx PAGE: 0058B35D lea ecx, [eax + _DEBUG_OBJECT.EventsPresent.Header.WaitListHead] PAGE: 0058B360 mov [ecx + LIST_ENTRY.Blink], ecx PAGE: 0058B363 mov [ecx + LIST_ENTRY.Flink], ecx PAGE: 0058B365 movzx ecx, byte ptr [ebp + Flags] PAGE: 0058B369 and ecx, esi PAGE: 0058B36B shl ecx, 1 PAGE: 0058B36D mov [eax + _DEBUG_OBJECT.Flags], ecx |
赋值完成以后,调用ObInsertObject将调试对象插入到句柄表中
1 2 3 4 5 6 7 8 9 10 | PAGE: 0058B370 lea ecx, [ebp + Handle] PAGE: 0058B373 push ecx ; Handle PAGE: 0058B374 push ebx ; NewObject PAGE: 0058B375 push ebx ; ObjectPointerBias PAGE: 0058B376 push [ebp + DesiredAccess] ; DesiredAccess PAGE: 0058B379 push ebx ; PACCESS_STATE PAGE: 0058B37A push eax ; Object PAGE: 0058B37B call _ObInsertObject@ 24 ; ObInsertObject(x,x,x,x,x,x) PAGE: 0058B380 cmp eax, ebx PAGE: 0058B382 jl short loc_58B3D8 |
最后将得到的句柄赋值到第一个参数指向的用户层地址,也就是TEB的DbgSsReserved[1],然后退出函数
1 2 3 | PAGE: 0058B387 mov ecx, [ebp + Handle] PAGE: 0058B38A mov [edi], ecx ; 将得到的句柄赋值到DebugHandle指定用户层地址 PAGE: 0058B38C jmp short loc_58B3AE |
也就是说,DbgUiConnectToDbg实质上就是创建了一个调试对象,然后将其赋值到TEB结构的DbgSsReserved[1]中。如果该函数成功调用,接下来就会调用ProcessIdToHandle函数来将被调试进程的PID转为句柄,并将其赋给esi。ProcessIdToHandle函数会通过调用内核函数NtOpenProcess来获得进程句柄,在进行这一步的时候就需要调试进程与被调试进程有同样或更高的权限,否则这一步就会失败。
1 2 3 4 5 6 7 | .text: 7C85B113 loc_7C85B113: ; CODE XREF: DebugActiveProcess(x) + C↑j .text: 7C85B113 push esi .text: 7C85B114 push [ebp + dwProcessId] ; ProcessHandle .text: 7C85B117 call _ProcessIdToHandle@ 4 ; ProcessIdToHandle(x) .text: 7C85B11C mov esi, eax ; 将句柄赋给esi .text: 7C85B11E test esi, esi .text: 7C85B120 jz short loc_7C85B144 |
成功获得调试对象句柄以后,就会调用DbgUiDebugActiveProcess函数,此时第一个参数esi指向的是被调试进程的句柄,而edi指向的是调试进程的句柄
1 2 3 | .text: 7C85B122 push edi .text: 7C85B123 push esi ; Process .text: 7C85B124 call _DbgUiDebugActiveProcess@ 4 |
DbgUiDebugActiveProcess是ntdll.dll中的一个函数,该函数会调用ZwDebugActiveProcess
内核函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .text: 7C970082 public DbgUiDebugActiveProcess .text: 7C970082 DbgUiDebugActiveProcess proc near ; DATA XREF: .text:off_7C923428↑o .text: 7C970082 .text: 7C970082 arg_ProcessHandle = dword ptr 8 .text: 7C970082 .text: 7C970082 mov edi, edi .text: 7C970084 push ebp .text: 7C970085 mov ebp, esp .text: 7C970087 push esi .text: 7C970088 mov eax, large fs: 18h ; 获取TEB地址 .text: 7C97008E push dword ptr [eax + 0F24h ] ; 将调试对象句柄入栈 .text: 7C970094 push [ebp + arg_ProcessHandle] .text: 7C970097 call ZwDebugActiveProcess .text: 7C97009C mov esi, eax .text: 7C97009E test esi, esi .text: 7C9700A0 jl short loc_7C9700B8 |
在内核函数中,首先会通过ObReferenceObjectByHandle来获取被调试进程的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | PAGE: 0058C431 ; NTSTATUS __stdcall NtDebugActiveProcess(HANDLE Process, HANDLE DebugObject) PAGE: 0058C431 _NtDebugActiveProcess@ 8 proc near ; DATA XREF: .text: 0040D904 ↑o PAGE: 0058C431 PAGE: 0058C431 AccessMode = byte ptr - 4 PAGE: 0058C431 Process = dword ptr 8 PAGE: 0058C431 DebugObject = dword ptr 0Ch PAGE: 0058C431 PAGE: 0058C431 mov edi, edi PAGE: 0058C433 push ebp PAGE: 0058C434 mov ebp, esp PAGE: 0058C436 push ecx PAGE: 0058C437 mov eax, large fs: 124h ; 获取线程对象地址 PAGE: 0058C43D mov al, [eax + _KTHREAD.PreviousMode] PAGE: 0058C443 push 0 ; HandleInformation PAGE: 0058C445 mov [ebp + AccessMode], al PAGE: 0058C448 lea eax, [ebp + Process] ; 保存被调试进程对象地址 PAGE: 0058C44B push eax ; Object PAGE: 0058C44C push dword ptr [ebp + AccessMode] ; AccessMode PAGE: 0058C44F push _PsProcessType ; ObjectType PAGE: 0058C455 push PROCESS_SUSPEND_RESUME ; DesiredAccess PAGE: 0058C45A push [ebp + Process] ; Handle PAGE: 0058C45D call _ObReferenceObjectByHandle@ 24 ; ObReferenceObjectByHandle(x,x,x,x,x,x) PAGE: 0058C462 test eax, eax PAGE: 0058C464 jl locret_58C4F8 |
判断被调试对象是否是当前进程或系统进程,如果是的话,函数就会返回错误
1 2 3 4 5 6 7 8 | PAGE: 0058C46A push ebx PAGE: 0058C46B push esi PAGE: 0058C46C mov eax, large fs: 124h PAGE: 0058C472 mov esi, [ebp + Process] ; 将被调试进程对象地址赋给esi PAGE: 0058C475 cmp esi, [eax + _KTHREAD.ApcState.Process] ; 判断是否为当前进程 PAGE: 0058C478 jz short loc_58C4E8 PAGE: 0058C47A cmp esi, _PsInitialSystemProcess ; 判断是否为系统进程 PAGE: 0058C480 jz short loc_58C4E8 |
再次调用ObReferenceObjectByHandle来获取调试对象的对象地址
1 2 3 4 5 6 7 8 9 10 11 | PAGE: 0058C482 push 0 ; HandleInformation PAGE: 0058C484 lea eax, [ebp + Process] ; 保存调试对象的地址 PAGE: 0058C487 push eax ; Object PAGE: 0058C488 push dword ptr [ebp + AccessMode] ; AccessMode PAGE: 0058C48B push _DbgkDebugObjectType ; ObjectType PAGE: 0058C491 push 2 ; DesiredAccess PAGE: 0058C493 push [ebp + DebugObject] ; Handle PAGE: 0058C496 call _ObReferenceObjectByHandle@ 24 ; ObReferenceObjectByHandle(x,x,x,x,x,x) PAGE: 0058C49B mov ebx, eax PAGE: 0058C49D test ebx, ebx PAGE: 0058C49F jl short loc_58C4ED |
获取调试对象以后,会调用DbgkpSetProcessDebugObject函数,此时esi保存的是被调试进程的对象地址,而[ebp+Process]保存的是调试对象的地址
1 2 3 4 5 | PAGE: 0058C4C0 push [ebp + DebugObject] ; DebugObjectHandle PAGE: 0058C4C3 push eax ; int PAGE: 0058C4C4 push [ebp + Process] ; DebugObject PAGE: 0058C4C7 push esi ; Process PAGE: 0058C4C8 call _DbgkpSetProcessDebugObject@ 16 |
DbgkpSetProcessDebugObject最重要的代码就是判断调试进程的DebugPort是否保存了调试对象,如果没保存,就将解析得到调试对象地址赋值到DebugPort中
1 2 3 4 5 6 7 8 | PAGE: 0058C237 mov edi, [ebp + Process] ; 将被调试进行句柄赋给edi PAGE: 0058C251 cmp [edi + _EPROCESS.DebugPort], ebx ; 判断DebugPort是否保存了调试对象 PAGE: 0058C257 jnz short loc_58C2CC PAGE: 0058C259 PAGE: 0058C259 loc_58C259: ; CODE XREF: DbgkpSetProcessDebugObject(x,x,x,x) + CF↓j PAGE: 0058C259 mov eax, [ebp + DebugObject] ; 将调试对象地址赋给eax PAGE: 0058C25C mov ecx, [ebp + DebugObjectHandle] PAGE: 0058C25F mov [edi + _EPROCESS.DebugPort], eax ; 将调试对象地址赋给DebugPort |
当ntdll.dll中的DbgUiDebugActiveProcess函数调用ZwDebugProcess函数返回以后,就会调用函数DbgUiIssueRemoteBreakin在被调试进程中创建中断线程,使其可以中断到调试器中,随后退出函数
1 2 3 4 5 6 7 8 9 10 | .text: 7C97009C mov esi, eax .text: 7C97009E test esi, esi .text: 7C9700A0 jl short loc_7C9700B8 .text: 7C9700A2 push [ebp + arg_ProcessHandle] .text: 7C9700A5 call DbgUiIssueRemoteBreakin .text: 7C9700AA mov esi, eax .text: 7C9700AC test esi, esi .text: 7C9700AE jge short loc_7C9700B8 .text: 7C9700B0 push [ebp + arg_ProcessHandle] .text: 7C9700B3 call DbgUiStopDebugging |
而DebugActiveProcess函数返回以后,就会调用NtClose关闭被调试进程句柄,再退出函数
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 | .text: 7C85B129 push esi ; Handle .text: 7C85B12A mov edi, eax .text: 7C85B12C call ds:__imp__NtClose@ 4 ; NtClose(x) .text: 7C85B132 test edi, edi .text: 7C85B134 jge short loc_7C85B140 .text: 7C85B136 push edi ; Status .text: 7C85B137 call _BaseSetLastNTError@ 4 ; BaseSetLastNTError(x) .text: 7C85B13C xor eax, eax .text: 7C85B13E jmp short loc_7C85B143 .text: 7C85B140 ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .text: 7C85B140 .text: 7C85B140 loc_7C85B140: ; CODE XREF: DebugActiveProcess(x) + 39 ↑j .text: 7C85B140 xor eax, eax .text: 7C85B142 inc eax .text: 7C85B143 .text: 7C85B143 loc_7C85B143: ; CODE XREF: DebugActiveProcess(x) + 43 ↑j .text: 7C85B143 pop edi .text: 7C85B144 .text: 7C85B144 loc_7C85B144: ; CODE XREF: DebugActiveProcess(x) + 25 ↑j .text: 7C85B144 pop esi .text: 7C85B145 .text: 7C85B145 loc_7C85B145: ; CODE XREF: DebugActiveProcess(x) + 16 ↑j .text: 7C85B145 pop ebp .text: 7C85B146 retn 4 .text: 7C85B146 _DebugActiveProcess@ 4 endp |
在调试进程与被调试进程建立连接的过程中会创建一个调试对象,被调试进程对象的DebugPort字段保存了调试对象的地址,调试进程的调试线程的线程环境块(TEB)的偏移0xF24DbgSsReserved[1]中保存了调试对象的句柄
三.调试消息
1.采集调试消息
为了让调试进程得知被调试进程的状态,内核会将被调试进程所有的调试消息都收集起来发送给调试子系统。调试消息用DBGKM_APIMSG结构描述,该结构定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 | typedef enum _DBGKM_APINUMBER { DbgKmExceptionApi = 0 , / / 异常 DbgKmCreateThreadApi = 1 , / / 创建线程 DbgKmCreateProcessApi = 2 , / / 创建进程 DbgKmExitThreadApi = 3 , / / 线程退出 DbgKmExitProcessApi = 4 , / / 进程退出 DbgKmLoadDllApi = 5 , / / 映射DLL DbgKmUnloadDllApi = 6 , / / 卸载DLL DbgKmErrorReportApi = 7 , / / 内部错误,已不再使用 DbgKmMaxApiNumber = 8 , / / 这组常量的最大值 } DBGKM_APINUMBER; |
不同的消息会在内核执行不同函数的时候被采集起来,具体情况如下:
消息类型 | 采集方式 |
---|---|
进程和线程创建消息 | 创建进程的时候,都需要创建主线程。而创建用户模式线程的时候,会执行PspUserThreadStartup例程,在该函数中会通过调用DbgkCreateThread来采集创建消息 |
进程和线程退出消息 | 进程的退出其实就是将其所有线程都退出,而线程退出的最终函数是PspExitThread。该函数会判断退出的线程是否是最后一个线程,如果不是则会调用DbgkExitThread来采集线程退出消除;如果是最后一个线程,就会通过调用DbgkExitProcess来采集进程退出消息 |
DLL映射消息 | Windows内核使用NtMapViewOfSection来将一个模块对象映射到指定的进程空间中时,NtMapViewOfSection会调用DbgkMapViewOfSection来采集模块映射的消息 |
DLL卸载消息 | NtUnMapViewOfSection则会在卸载模块的时候,调用DbgkUnMapViewOfSection来采集模块卸载消息 |
异常消息 | 当出现异常的时候,KiDispatchException会完成对异常的分发,而该函数会判断是否具有调试器,如果有调试器,则会通过调用DbgkForwardException函数来采集异常消息 |
这些不同的Dbgk采集例程会根据当前进程的DebugPort字段来判断是否处于被调试状态。如果不是,便会忽略这次调用;如果处于调试状态,便会产生一个DBGKM_APIMSG结构,该结构的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef struct _DBGKM_APIMSG { PORT_MESSAGE h; / / LPC端口消息结构,Windows XP之前使用 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; / / 映射DLL DBGKM_UNLOAD_DLL UnLoadDll; / / 卸载DLL } u; } DBGKM_APIMSG, * PDBGKM_APIMG; |
其中联合体u根据消息类型的不同而不同,用来描述消息参数和详细信息。
接下来以DbgkForwardException函数为例,体会上述过程。该函数原型如下:
1 2 3 | int DbgkForwardException(PEXCEPTION_RECORD pExceptionRecord, BOOLEAN DebugException, BOOLEAN FirstChance); |
参数 | 含义 |
---|---|
pExceptionRecord | 保存执行异常结构指针 |
DebugException | 布尔值,用来指定是调试端口还是异常端口,TRUE表示是调试端口,FALSE表示异常端口 |
FirstChance | 布尔值,说明是否是第一次调用,FALSE代表是第一次调用,TRUE代表不是第一次调用 |
函数首先会对一些字段进行填充,然后判断是否是要发送给调试端口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | PAGE: 004AD63A ; int __stdcall DbgkForwardException(PEXCEPTION_RECORD pExceptionRecord, BOOLEAN DebugException, BOOLEAN FirstChance) PAGE: 004AD63A _DbgkForwardException@ 12 proc near ; CODE XREF: KiDispatchException(x,x,x,x,x) + 1C5F5 ↑p PAGE: 004AD63A ; KiDispatchException(x,x,x,x,x) + 267A6 ↑p ... PAGE: 004AD63A PAGE: 004AD63A Dbgkm_ApiMsg = _DBGKM_APIMSG ptr - 78h PAGE: 004AD63A pExceptionRecord = dword ptr 8 PAGE: 004AD63A DebugException = byte ptr 0Ch PAGE: 004AD63A FirstChance = byte ptr 10h PAGE: 004AD63A PAGE: 004AD63A ; FUNCTION CHUNK AT PAGE: 00544B0E SIZE 00000079 BYTES PAGE: 004AD63A PAGE: 004AD63A mov edi, edi PAGE: 004AD63C push ebp PAGE: 004AD63D mov ebp, esp PAGE: 004AD63F sub esp, 78h PAGE: 004AD642 and [ebp + Dbgkm_ApiMsg.ApiNumber], 0 PAGE: 004AD646 push ebx PAGE: 004AD647 mov dword ptr [ebp + Dbgkm_ApiMsg.h.u1], 78005Ch PAGE: 004AD64E mov dword ptr [ebp + Dbgkm_ApiMsg.h.u2], 8 PAGE: 004AD655 mov eax, large fs: 124h PAGE: 004AD65B mov ebx, dword ptr [ebp + DebugException] ; 将是否是调试端口的结果赋给ebx PAGE: 004AD65E test bl, bl ; 判断是否为调试端口 PAGE: 004AD660 mov ecx, [eax + _KTHREAD.ApcState.Process] PAGE: 004AD663 jz loc_544B15 |
如果是调试端口,接下来就会判断DebugPort字段是否存在,也就是是否存在调试器
1 2 3 4 5 6 7 8 9 10 11 | PAGE: 004AD669 mov eax, large fs: 124h PAGE: 004AD66F test byte ptr [eax + _ETHREAD.___u24.CrossThreadFlags], 4 PAGE: 004AD676 jnz loc_544B0E PAGE: 004AD67C mov eax, [ecx + _EPROCESS.DebugPort] ; 将DebugPort赋给eax PAGE: 004AD682 PAGE: 004AD682 loc_4AD682: ; CODE XREF: DbgkForwardException(x,x,x) + 974D6 ↓j PAGE: 004AD682 xor dl, dl PAGE: 004AD684 PAGE: 004AD684 loc_4AD684: ; CODE XREF: DbgkForwardException(x,x,x) + 974EA ↓j PAGE: 004AD684 test eax, eax ; 判断是否存在DebugPort PAGE: 004AD686 jnz loc_544B29 |
如果存在调试调试器,就会继续填充字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | PAGE: 00544B29 loc_544B29: ; CODE XREF: DbgkForwardException(x,x,x) + 4C ↑j PAGE: 00544B29 push esi PAGE: 00544B2A mov esi, [ebp + pExceptionRecord] PAGE: 00544B2D push edi PAGE: 00544B2E push 14h PAGE: 00544B30 pop ecx PAGE: 00544B31 lea edi, [ebp + Dbgkm_ApiMsg.Exception] PAGE: 00544B34 rep movsd PAGE: 00544B36 xor ecx, ecx PAGE: 00544B38 cmp [ebp + FirstChance], cl PAGE: 00544B3B pop edi PAGE: 00544B3C setz cl PAGE: 00544B3F test dl, dl PAGE: 00544B41 pop esi PAGE: 00544B42 push ebx ; char PAGE: 00544B43 mov [ebp + Dbgkm_ApiMsg.Exception.FirstChance], ecx PAGE: 00544B46 jz short loc_544B54 |
调用DbgkpSendApiMessage将结构体发送给调试子系统
1 2 3 4 | PAGE: 00544B54 loc_544B54: ; CODE XREF: DbgkForwardException(x,x,x) + 9750C ↑j PAGE: 00544B54 lea eax, [ebp + Dbgkm_ApiMsg] PAGE: 00544B57 push eax PAGE: 00544B58 call _DbgkpSendApiMessage@ 8 |
对函数的返回结果以及ReturnedStatus进行判断
1 2 3 4 5 6 7 8 9 | PAGE: 00544B5D loc_544B5D: ; CODE XREF: DbgkForwardException(x,x,x) + 97518 ↑j PAGE: 00544B5D test eax, eax PAGE: 00544B5F jl loc_4AD68C PAGE: 00544B65 test bl, bl PAGE: 00544B67 jz short loc_544B80 PAGE: 00544B69 cmp [ebp + Dbgkm_ApiMsg.ReturedStatus], DBG_EXCEPTION_NOT_HANDLED PAGE: 00544B70 jz loc_4AD68C PAGE: 00544B76 cmp [ebp + Dbgkm_ApiMsg.ReturedStatus], 0 PAGE: 00544B7A jl loc_4AD68C |
如果验证通过,则将返回值赋为1,然后退出函数
1 2 3 4 5 6 7 8 9 | PAGE: 00544B80 loc_544B80: ; CODE XREF: DbgkForwardException(x,x,x) + 9752D ↑j PAGE: 00544B80 mov al, 1 PAGE: 00544B82 jmp loc_4AD68E 。。。 PAGE: 004AD68E loc_4AD68E: ; CODE XREF: DbgkForwardException(x,x,x) + 97548 ↓j PAGE: 004AD68E pop ebx PAGE: 004AD68F leave PAGE: 004AD690 retn 0Ch PAGE: 004AD690 _DbgkForwardException@ 12 endp |
所有的调试消息的采集例程的代码主要的执行内容都是以下这几个步骤:
判断是否存在调试器,也就是DebugPort是否为空
填充DBGKM_APIMSG结构
将DBGKM_APIMSG结构的作为参数,调用DbgkpSendApiMessage来讲调试消息发送给调试子系统
最大的区别就是DBGKM_APIMSG结构的联合体u中保存的数据的不同
2.发送调试消息
上面提到通过DbgkpSendApiMessage来将调试消息发送给调试子系统,该函数定义如下:
1 | int DbgkpSendApiMessage(PDBGKM_APIMSG ApiMsg, BOOLEAN SuspendProcess); |
参数 | 含义 |
---|---|
ApiMsg | 采集的调试消息的详细信息 |
SuspendProcess | 布尔值,指定是否要将被调试进程中除了调用线程以外的线程全部冻结,TRUE为要,FALSE为不要 |
函数首先就会判断参数SuspendProcess,如果为TRUE就会调用DbgkpSuspendProcess,并将返回值赋给参数SuspendProcess
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | PAGE: 0058C6E4 ; int __stdcall DbgkpSendApiMessage( int , BOOLEAN SuspendProcess) PAGE: 0058C6E4 _DbgkpSendApiMessage@ 8 proc near ; CODE XREF: DbgkCreateThread(x) + 4232F ↑p PAGE: 0058C6E4 ; DbgkCreateThread(x) + 42449 ↑p ... PAGE: 0058C6E4 PAGE: 0058C6E4 ApiMsg = dword ptr 8 PAGE: 0058C6E4 SuspendProcess = byte ptr 0Ch PAGE: 0058C6E4 PAGE: 0058C6E4 mov edi, edi PAGE: 0058C6E6 push ebp PAGE: 0058C6E7 mov ebp, esp PAGE: 0058C6E9 push ebx PAGE: 0058C6EA xor ebx, ebx ; ebx清 0 PAGE: 0058C6EC cmp [ebp + SuspendProcess], bl PAGE: 0058C6EF push esi PAGE: 0058C6F0 jz short loc_58C6FA PAGE: 0058C6F2 call _DbgkpSuspendProcess@ 0 ; DbgkpSuspendProcess() PAGE: 0058C6F7 mov [ebp + SuspendProcess], al |
而DbgkpSuspendProcess则是通过KeFreezeAllThreads来冻结线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | PAGE: 0058C7DC ; _DWORD __stdcall DbgkpSuspendProcess() PAGE: 0058C7DC _DbgkpSuspendProcess@ 0 proc near ; CODE XREF: DbgkpSendApiMessage(x,x) + E↑p PAGE: 0058C7DC ; DbgkpSendApiMessageLpc(x,x,x) + 11 ↑p ... PAGE: 0058C7DC mov eax, large fs: 124h PAGE: 0058C7E2 mov eax, [eax + _KTHREAD.ApcState.Process] PAGE: 0058C7E5 test byte ptr [eax + _EPROCESS.___u70.Flags], 8 PAGE: 0058C7EC jnz short loc_58C7F6 PAGE: 0058C7EE call _KeFreezeAllThreads@ 0 ; KeFreezeAllThreads() PAGE: 0058C7F3 mov al, 1 PAGE: 0058C7F5 retn PAGE: 0058C7F6 ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PAGE: 0058C7F6 PAGE: 0058C7F6 loc_58C7F6: ; CODE XREF: DbgkpSuspendProcess() + 10 ↑j PAGE: 0058C7F6 xor al, al PAGE: 0058C7F8 retn PAGE: 0058C7F8 _DbgkpSuspendProcess@ 0 endp |
接下来函数会调用DbgkpQueueMessage来发送调试消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | PAGE: 0058C6FA loc_58C6FA: ; CODE XREF: DbgkpSendApiMessage(x,x) + C↑j PAGE: 0058C6FA mov edx, [ebp + ApiMsg] PAGE: 0058C6FD mov [edx + _DBGKM_APIMSG.ReturedStatus], 103h PAGE: 0058C704 mov eax, large fs: 124h PAGE: 0058C70A mov ecx, [eax + _KTHREAD.ApcState.Process] PAGE: 0058C70D xor eax, eax PAGE: 0058C70F inc eax PAGE: 0058C710 lea esi, [ecx + 248h ] PAGE: 0058C716 lock or [esi], eax PAGE: 0058C719 mov eax, large fs: 124h PAGE: 0058C71F push ebx ; FastMutex PAGE: 0058C720 push ebx ; Event PAGE: 0058C721 push edx ; ApiMsg PAGE: 0058C722 push eax ; Thread PAGE: 0058C723 push ecx ; Process PAGE: 0058C724 call _DbgkpQueueMessage@ 20 |
在根据前面是否成功将线程冻结来觉得是否要恢复线程
1 2 3 4 5 6 7 8 9 10 11 | PAGE: 0058C734 cmp [ebp + SuspendProcess], bl PAGE: 0058C737 jz short loc_58C73E PAGE: 0058C739 call _KeThawAllThreads@ 0 ; KeThawAllThreads() PAGE: 0058C73E PAGE: 0058C73E loc_58C73E: ; CODE XREF: DbgkpSendApiMessage(x,x) + 53 ↑j PAGE: 0058C73E mov eax, esi PAGE: 0058C740 pop esi PAGE: 0058C741 pop ebx PAGE: 0058C742 pop ebp PAGE: 0058C743 retn 8 PAGE: 0058C743 _DbgkpSendApiMessage@ 8 endp |
DbgkpQueueMessage的作用是向一个调试对象的消息队列中追加调试事件。而调试消息队列的每一个节点都是DBGKM_DEBUG_EVENT结构,该结构定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 | typedef struct _DBGKM_DEBUG_EVENT { LIST_ENTRY EventList; / / 用于链接进调试消息队列的双向链表 KEVENT ContinueEvent; / / 用于等待调试器恢复的事件对象 CLIENT_ID ClientId; / / 调试事件所属的线程 ID 和进程 ID PEPROCESS Process; / / 被调试进程的EPROCESS结构地址 PETHREAD Thread; / / 被调试进程中触发调试事件的线程ETHREAD结构地址 NTSTATUS Status; / / 对调试事件中的处理结果 ULONG Flags; / / 标志 PETHREAD BackoutThread; / / 产生杜撰消息的线程 DBGKM_MSG ApiMsg; / / 调试事件的详细信息 }DBGKM_DEBUG_EVENT, * PDBGKM_DEBUG_EVENT; |
因此,DbgkpQueueMessage函数首先就是申请一块内存用来保存这些数据,但是在申请之前将参数Event的数据与2进行与操作以后保存在局部变量var_Event中,也就是判断参数中是否有不需等待(NOWAIT,值为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 | PAGE: 0058B43D ; int __stdcall DbgkpQueueMessage(PEPROCESS Process, PETHREAD Thread, int ApiMsg, PRKEVENT Event, PFAST_MUTEX FastMutex) PAGE: 0058B43D _DbgkpQueueMessage@ 20 proc near ; CODE XREF: DbgkpPostFakeThreadMessages(x,x,x,x,x) + 15E ↓p PAGE: 0058B43D ; DbgkpPostFakeModuleMessages(x,x,x) + 136 ↓p ... PAGE: 0058B43D PAGE: 0058B43D var_B8 = byte ptr - 0B8h PAGE: 0058B43D var_8C = dword ptr - 8Ch PAGE: 0058B43D var_DbgkmDebugEventApiMsg = dword ptr - 8 PAGE: 0058B43D var_Event = dword ptr - 4 PAGE: 0058B43D Process = dword ptr 8 PAGE: 0058B43D Thread = dword ptr 0Ch PAGE: 0058B43D ApiMsg = dword ptr 10h PAGE: 0058B43D Event = dword ptr 14h PAGE: 0058B43D FastMutex = dword ptr 18h PAGE: 0058B43D PAGE: 0058B43D mov edi, edi PAGE: 0058B43F push ebp PAGE: 0058B440 mov ebp, esp PAGE: 0058B442 sub esp, 0B8h PAGE: 0058B448 push ebx PAGE: 0058B449 push esi PAGE: 0058B44A mov esi, [ebp + Event] PAGE: 0058B44D mov [ebp + var_Event], esi PAGE: 0058B450 and [ebp + var_Event], 2 ; 和NOWAIT进行与操作 PAGE: 0058B454 jz short loc_58B49E PAGE: 0058B456 push 'EgbD' ; Tag PAGE: 0058B45B push 0B0h ; NumberOfBytes PAGE: 0058B460 push 8 ; PoolType PAGE: 0058B462 call _ExAllocatePoolWithQuotaTag@ 12 ; ExAllocatePoolWithQuotaTag(x,x,x) PAGE: 0058B467 mov ebx, eax ; 将申请的内存地址赋给ebx PAGE: 0058B469 test ebx, ebx PAGE: 0058B46B jnz short loc_58B477 PAGE: 0058B46D mov eax, STATUS_INSUFFICIENT_RESOURCES PAGE: 0058B472 jmp loc_58B5EB |
通过申请的内存来对一些字段进行赋值
1 2 3 4 5 6 7 8 9 | PAGE: 0058B477 loc_58B477: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 2E ↑j PAGE: 0058B477 mov ecx, [ebp + Process] ; Object PAGE: 0058B47A or esi, 4 PAGE: 0058B47D mov [ebx + _DBGKM_DEBUG_EVENT.Flags], esi PAGE: 0058B480 call @ObfReferenceObject@ 4 ; ObfReferenceObject(x) PAGE: 0058B485 mov ecx, [ebp + Thread] ; Object PAGE: 0058B488 call @ObfReferenceObject@ 4 ; ObfReferenceObject(x) PAGE: 0058B48D mov eax, large fs: 124h PAGE: 0058B493 mov [ebx + _DBGKM_DEBUG_EVENT.BackoutThread], eax |
将调试对象赋值给参数Event的地址,将调试消息类型赋值给eax
1 2 3 4 5 | PAGE: 0058B4B5 mov eax, [ebp + Process] PAGE: 0058B4B8 mov eax, [eax + _EPROCESS.DebugPort] ; 将DebugPort取出赋给eax PAGE: 0058B4BE mov [ebp + Event], eax ; 将调试对象赋给Event PAGE: 0058B4C1 mov eax, [ebp + ApiMsg] PAGE: 0058B4C4 mov eax, [eax + DBGKM_MSG.ApiNumber] ; 将调试消息类型赋给eax |
根据调试消息类型进行相应的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | PAGE: 0058B4C7 cmp eax, 1 PAGE: 0058B4CA jz short loc_58B4D1 PAGE: 0058B4CC cmp eax, 2 PAGE: 0058B4CF jnz short loc_58B4E1 PAGE: 0058B4D1 PAGE: 0058B4D1 loc_58B4D1: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 8D ↑j PAGE: 0058B4D1 mov ecx, [ebp + Thread] PAGE: 0058B4D4 test byte ptr [ecx + _ETHREAD.___u24.CrossThreadFlags], 80h PAGE: 0058B4DB jz short loc_58B4E1 PAGE: 0058B4DD and [ebp + Event], 0 PAGE: 0058B4E1 PAGE: 0058B4E1 loc_58B4E1: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 92 ↑j PAGE: 0058B4E1 ; DbgkpQueueMessage(x,x,x,x,x) + 9E ↑j PAGE: 0058B4E1 cmp eax, 3 PAGE: 0058B4E4 jz short loc_58B4EB PAGE: 0058B4E6 cmp eax, 4 PAGE: 0058B4E9 jnz short loc_58B4FB PAGE: 0058B4EB PAGE: 0058B4EB loc_58B4EB: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + A7↑j PAGE: 0058B4EB mov eax, [ebp + Thread] PAGE: 0058B4EE test byte ptr [eax + 249h ], 1 PAGE: 0058B4F5 jz short loc_58B4FB PAGE: 0058B4F7 and [ebp + Event], 0 |
继续为成员赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | PAGE: 0058B4FB loc_58B4FB: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 5F ↑j PAGE: 0058B4FB ; DbgkpQueueMessage(x,x,x,x,x) + AC↑j ... PAGE: 0058B4FB and [ebx + _DBGKM_DEBUG_EVENT.ContinueEvent.Header.SignalState], 0 PAGE: 0058B4FF mov esi, [ebp + ApiMsg] ; 将ApiMsg地址赋给esi PAGE: 0058B502 mov [ebx + _DBGKM_DEBUG_EVENT.ContinueEvent.Header. Type ], 1 PAGE: 0058B506 mov [ebx + _DBGKM_DEBUG_EVENT.ContinueEvent.Header.Size], 4 PAGE: 0058B50A lea eax, [ebx + _DBGKM_DEBUG_EVENT.ContinueEvent.Header.WaitListHead] PAGE: 0058B50D mov [eax + LIST_ENTRY.Blink], eax PAGE: 0058B510 mov [eax + LIST_ENTRY.Flink], eax PAGE: 0058B512 mov eax, [ebp + Process] PAGE: 0058B515 push edi PAGE: 0058B516 mov [ebx + _DBGKM_DEBUG_EVENT.Process], eax PAGE: 0058B519 mov eax, [ebp + Thread] PAGE: 0058B51C lea edi, [ebx + _DBGKM_DEBUG_EVENT.ApiMsg.h.u2] ; 将结果_DBGKM_DEBUG_EVENT中的ApiMsg地址赋给edi PAGE: 0058B51F push 1Eh PAGE: 0058B521 mov [ebx + _DBGKM_DEBUG_EVENT.Thread], eax PAGE: 0058B524 mov [ebp + var_DbgkmDebugEventApiMsg], edi PAGE: 0058B527 pop ecx PAGE: 0058B528 rep movsd ; 为ApiMsg赋值 |
继续赋值,并判断DebugPort是否存在调试对象,如果不存在则返回错误,此时Event中保存的是DebugPort所保存的调试对象的地址
1 2 3 4 5 6 7 8 9 10 11 | PAGE: 0058B52A mov ecx, [ebp + Event] ; 将调试对象地址赋给ecx PAGE: 0058B52D mov esi, eax PAGE: 0058B52F mov eax, [esi + _ETHREAD.Cid.UniqueProcess] PAGE: 0058B535 mov [ebx + _DBGKM_DEBUG_EVENT.ClientId.UniqueProcess], eax PAGE: 0058B538 mov eax, [esi + _ETHREAD.Cid.UniqueThread] PAGE: 0058B53E xor edi, edi ; edi清 0 PAGE: 0058B540 cmp ecx, edi ; 判断调试对象是否存在 PAGE: 0058B542 mov [ebx + _DBGKM_DEBUG_EVENT.ClientId.UniqueThread], eax PAGE: 0058B545 jnz short loc_58B550 PAGE: 0058B547 mov [ebp + Thread], STATUS_PORT_NOT_SET PAGE: 0058B54E jmp short loc_58B597 |
接下来就会将生成并且赋值好的DBGKM_DEBUG_EVENT插入到调试对象EventList中,判断局部变量var_Event的值是否为0,该值是在函数开始的时候,参数Event的值与2进行与操作的结果,如果为0,则说明Event中不带有不需等待的(NOWAIT)标志
1 2 3 4 5 6 7 8 9 10 11 | AGE: 0058B55C mov edx, [ebp + Event] ; 将调试对象赋给edx PAGE: 0058B55F test byte ptr [edx + _DEBUG_OBJECT.Flags], 1 PAGE: 0058B563 jnz short loc_58B587 PAGE: 0058B565 cmp [ebp + var_Event], edi ; 判断var_Event是否为 0 PAGE: 0058B568 lea eax, [edx + _DEBUG_OBJECT.EventList] ; 插入到调试对象的EventList中 PAGE: 0058B56B mov ecx, [eax + LIST_ENTRY.Blink] PAGE: 0058B56E mov [ebx + LIST_ENTRY.Flink], eax PAGE: 0058B570 mov [ebx + LIST_ENTRY.Blink], ecx PAGE: 0058B573 mov [ecx + LIST_ENTRY.Flink], ebx PAGE: 0058B575 mov [eax + LIST_ENTRY.Blink], ebx PAGE: 0058B578 jnz short loc_58B582 |
如果Event参数不带有无需等待的标志,接下来就会通过调用KeSetEvent来设置调试对象
1 2 3 4 | PAGE: 0058B57A push edi ; Wait PAGE: 0058B57B push edi ; Increment PAGE: 0058B57C push edx ; Event PAGE: 0058B57D call _KeSetEvent@ 12 |
并通过KeWaitForSingleObject来等待ContinueEvent对象
1 2 3 4 5 6 7 | PAGE: 0058B5AC push edi ; Timeout PAGE: 0058B5AD push edi ; Alertable PAGE: 0058B5AE push edi ; WaitMode PAGE: 0058B5AF push edi ; WaitReason PAGE: 0058B5B0 lea eax, [ebx + _DBGKM_DEBUG_EVENT.ContinueEvent] PAGE: 0058B5B3 push eax ; Object PAGE: 0058B5B4 call _KeWaitForSingleObject@ 20 |
当调试器处理好对象后,它会设置ContinueEvent对象,此时处于等待状态的被调试进程就会被唤醒,继续运行。
函数会将DBGKM_DEBUG_EVENT中的ApiMsg赋值到参数ApiMsg中,并取出Status将其赋值到参数Thread的地址
1 2 3 4 5 6 7 8 | PAGE: 0058B5B9 mov eax, [ebx + _DBGKM_DEBUG_EVENT.Status] PAGE: 0058B5BC mov esi, [ebp + var_DBGKM_DEBUG_EVENT] PAGE: 0058B5BF mov edi, [ebp + ApiMsg] PAGE: 0058B5C2 push 1Eh PAGE: 0058B5C4 pop ecx PAGE: 0058B5C5 mov [ebp + Thread], eax PAGE: 0058B5C8 rep movsd PAGE: 0058B5CA jmp short loc_58B5E7 |
将参数Thread中保存的数据作为返回值返回,也就是将Status返回
1 2 3 4 5 6 7 8 9 10 11 | PAGE: 0058B5E7 loc_58B5E7: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 16D ↑j PAGE: 0058B5E7 ; DbgkpQueueMessage(x,x,x,x,x) + 18D ↑j ... PAGE: 0058B5E7 mov eax, [ebp + Thread] PAGE: 0058B5EA pop edi PAGE: 0058B5EB PAGE: 0058B5EB loc_58B5EB: ; CODE XREF: DbgkpQueueMessage(x,x,x,x,x) + 35 ↑j PAGE: 0058B5EB pop esi PAGE: 0058B5EC pop ebx PAGE: 0058B5ED leave PAGE: 0058B5EE retn 14h PAGE: 0058B5EE _DbgkpQueueMessage@ 20 endp |
[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课