首页
社区
课程
招聘
[原创]Windows内核学习笔记之调试(上)
2022-1-2 21:14 10471

[原创]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

下篇:Windows内核学习笔记之调试(下)


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

最后于 2022-1-3 21:13 被1900编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (2)
雪    币: 89
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
sanqiu 2022-6-27 04:10
2
0
这是基于什么平台测试的
雪    币: 22399
活跃值: (25302)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
1900 6 2022-6-27 09:03
3
0
sanqiu 这是基于什么平台测试的
xp
游客
登录 | 注册 方可回帖
返回