Windows软件调试初探-用户态调试模型

碎碎念

调试器进程通过调试API与NTDLL.DLL中调试支持函数和调试子系统交互。调试子系统分为3个部分:NTDL.DLL支持函数、内核文件支持函数、调试子系统服务器。

NTDLL.DLL中调试支持函数有3中:以DbgUi开头的供调试器用;以DbgSs开头的供调试子系统使用,已被废弃;其他以Dbg开头的用于实现调试API。

内核文件调试函数以Dbgk开头,负责采集和传递调试事件、控制被调试进程。

调试子系统服务器用于管理调试会话和调试事件,是调试消息(事件)的集散地,也是所有调试设施的核心,位于内核模式中。

Windows用户态调试通过调试事件驱动。调试器程序在与被调试进程建立调试对话后,调试器进程进入调试事件循环,等待调试事件发生,然后处理再等待,直到调试会话终止,如:

1
2
3
4
while (WaitForDebugEvent(&DbgEvt, INIFINITE)) { //等待事件
//处理等待得到的事件
ContinueDebugEvent(DbgEvt.dwProcessId, DbgEvt.dwThreadId, dwContinueState); //处理后恢复调试目标继续执行
};

WaitForDebugEvent用于等待和接收调试事件,收到调试事件后,调试器根据事件类型(ID)来分发和处理,决定是否通知用户并进入交互式调试(命令模式)。此时被调试进程处于挂起状态,处理后用ContinueDebugEvent将处理结果回复给调试子系统,让被调试程序继续运行。

调试信息采集

调试子系统公开给内核其他部件一些接口函数,称为Dbgk采集例程,函数以Dbgk开头但不是Dbgkp。Dbgk采集例程将所有调试事件(消息)分为:

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>
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;

进程管理由Windows执行体中一系列函数完成,即NTOSKRNL.EXE的上半部分,这些函数及其使用的数据结构称为进程管理器,函数大多以Ps或Psp开头。

进程管理器创建新用户态Windows进程时,为该进程建立必要的内核对象与数据结构,并分配栈空间,之后该线程处于挂起状态。之后进程管理器通知环境子系统,子系统做必要的设置和登记。最后进程管理器用PspUserThreadStartup例程,该函数总用子系统内核函数DbgkCreateThread让调试子系统得到处理机会。DbgkCreateThread根据DebugPort是否为空检查新创建线程所在进程是否正被调试,不为空则检查该进程用户态运行时间是否为0来判断该线程是否为进程中第一个线程。为第一个线程则用DbgkpSendApiMessage向DebugPort发送DbgKmCreateProcessApi消息,不是则发送DbgKmCreateThreadApi消息。调试器收到的进程创建CREATE_PROCESS_DEBUG_EVENT和线程创建CREATE_THREAD_DEBUG_EVENT源于这俩消息。

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
NTSTATUS PspUserThreadStartup() {
struct _KTHREAD* CurrentThread; // rbx
__int64 v1; // rcx
__int64 v2; // r8
NTSTATUS* v3; // rdi
int SessionLocaleId; // eax
__int64 v5; // rdx
NTSTATUS result; // eax
unsigned __int8 CurrentIrql; // al
_DWORD* SchedulerAssist; // rdx
bool v9; // zf
if (KiIrqlFlags) {
CurrentIrql = KeGetCurrentIrql();
if ((KiIrqlFlags & 1) != 0 && (unsigned __int8)(CurrentIrql - 2) <= 0xDu) {
SchedulerAssist = KeGetCurrentPrcb()->SchedulerAssist;
v9 = (SchedulerAssist[5] & 0xFFFF0001) == 0;
SchedulerAssist[5] &= 0xFFFF0001;
if (v9)
KiRemoveSystemWorkPriorityKick();
};
};
__writecr8(0LL);
CurrentThread = KeGetCurrentThread();
PspDisablePrimaryTokenExchange(CurrentThread);
if ((*(_DWORD*)(&CurrentThread[1].SwapListEntry + 1) & 2) == 0) {
LOBYTE(v2) = 1;
PspTerminateThreadByPointer(CurrentThread, 3221225547LL, v2);
};
v3 = (NTSTATUS*)&CurrentThread->ApcState.Process[2].Header.WaitListHead.Flink + 1;
if ((*v3 & 1) != 0)
DbgkCreateMinimalThread((__int64)CurrentThread);
else {
SessionLocaleId = MmGetSessionLocaleId(v1, CurrentThread->Teb);
*(_DWORD*)(v5 + 264) = SessionLocaleId;
PspWriteTebIdealProcessor(CurrentThread, CurrentThread);
PspNotifyThreadCreation(CurrentThread);
};
if ((*(_DWORD*)(&CurrentThread[1].SwapListEntry + 1) & 1) != 0)
return KeWaitForSingleObject(CurrentThread, UserRequest, 1, 0, 0LL);
result = *v3;
if ((*v3 & 1) == 0)
return PspInitializeThunkContext();
return result;
};
void* __fastcall DbgkCreateMinimalThread(__int64 a1) {
void* result; // rax
_KPROCESS* v3; // rdi
_QWORD v4[34]; // [rsp+20h] [rbp-128h] BYREF
result = memset(&v4[8], 0, 0xD0uLL);
v3 = *(_KPROCESS**)(a1 + 184);
if (v3[1].Affinity.StaticBitmap[29]) {
memset(v4, 0, 0x40uLL);
v4[7] = *(_QWORD*)(a1 + 1312);
LODWORD(v4[5]) = 1;
v4[0] = 0x800400018LL;
return (void*)DbgkpSendApiMessage(v3, 1, (__int64)v4);
};
return result;
};

PspExitThread负责线程退出和清除。若正在退出的线程不是进程中最后一个线程,则其用DbgkExitThread通知指定线程退出;若是最后一个进程,则其用DbgkExitProcess通知指定进程退出。

当DebugPort不为0则DbgkExitThread将该进程挂起,并用DbgkpSendApiMessage向DebugPort发送DbgKmExitThreadApi消息,发送函数返回后恢复进程运行。DbgkExitProcess只需发送DbgKmExitProcessApi消息。调试器收到的EXIT_THREAD_DEBUG_EVENT和EXIT_PROCESS_DEBUG_EVENT源于这俩消息。

内核中内存管理器负责DLL的映射和反映射。内存管理器用Section对象表示一块可被多个进程共享的内存区域,有NtMapViewOfSection用来映射模块,NtUnmapViewOfSection用来反映射模块。NtMapViewOfSectionMmMapViewOfSection把一个模块映像(表示为Section对象)映射到指定进程空间,并用DbgkMapViewOfSection通知调试子系统。进程初始化期间加载DLL的调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nt!LpcRequestWaitReplyPort //用LPC发送并等待回复
nt!DbgkpSendApiMessage //发送调试信息
nt!DbgkMapViewOfSection //通知调试子系统
nt!NtMapViewOfSection //系统服务内核函数
nt!KiSystemService //系统服务分发例程
ntdll!ZwMapViewOfSection //调用系统服务
ntdll!LdrpMapDll //加载器模块映射函数
ntdll!LdrpLoadImportModule //加载依赖的模块
ntdll!LdrpWalkImportDescriptor //遍历模块输入表
ntdll!LdrpLoadImportModule //加载依赖的模块
ntdll!LdrpWalkImportDescriptor //遍历模块输入表
ntdll!LdrpInitializeProcess
ntdll!LdrpInitialize //加载器初始化函数
ntdll!KiUserApcDispatcher //异步过程调用到用户空间

DbgkMapViewOfSection检查当前进程DebugPort不为空则用DbgkpSendApiMessage发送DbgKmLoadDllApi消息。MmUnmapViewOfSection调用DbgkUnMapViewOfSection,其检测DebugPort不为空则发送DbgKmUnloadDllApi消息。调试器收到的LOAD_DLL_DEBUG_EVENT和UNLOAD_DLL_DEBUG_EVENT事件源于这俩消息。-

内核中KiDispatchException为分发异常的枢纽,给每个异常安排最多两轮被处理的机会,每轮用DbgkForwardException通知调试子系统。进程DebugPort字段不为空时,DbgkForwaredExceptionDbgkpSendApiMessage发送DbgKmExceptionApi消息。调试器收到的EXCEPTION_DEBUG_EVENT和OUTPUT_DEBUG_STRING_EVENT源于该消息。

调试信息发送

调试子系统内核函数用该结构描述和传递调试信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _DBGKM_APIMSG {
PORT_MESSAGE h; //废弃
DBGKM_APINUMBER ApiNumber; //消息类型
NTSTATUS ReturnedStatus; //调试器回复状态
union {
DBGKM_EXCEPTION Exception; //异常
DBGKM_CREATE_THREAD CreateThread; //创建线程
DBGKM_CREATE_PROCESS CreateProcess; //创建进程
DBGKM_EXIT_THREAD ExitThread; //线程退出
DBGKM_EXIT_PROCESS ExitProcess; //进程退出
DBGKM_LOAD_DLL LoadDll; //映射DLL
DBGKM_UNLOAD_DLL UnloadDll; //反映射DLL
}u;
} DBGKM_APIMSG, * PDBGKM_APIMSG;

调试消息采集函数填写上述结构,将其作为参数传给DbgkpSendApiMessage函数,该函数用于将一条调试信息发送到调试子系统服务器:

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
NTSTATUS DbgkpSendApiMessage(
_In_out_ PDBGKM_APIMSG ApiMsg, //详细信息
_In_ PVOID Port, //要发往的端口
_In_ BOOLEAN SuspendProcess
);
__int64 __fastcall DbgkpSendApiMessage(_KPROCESS* Object, char a2, __int64 a3) {
struct _KTHREAD* CurrentThread; // r12
int v7; // ebp
int v8; // esi
CurrentThread = KeGetCurrentThread();
if ((PerfGlobalGroupMask & 0x400000) != 0)
EtwTraceDebuggerEvent(KeGetCurrentThread()->ApcState.Process, CurrentThread, 1LL);
do {
v7 = 0;
if (Object == KeGetCurrentThread()->ApcState.Process && (a2 & 1) != 0)
v7 = (unsigned __int8)DbgkpSuspendProcess(Object);
*(_DWORD*)(a3 + 44) = 259;
v8 = DbgkpQueueMessage(Object, CurrentThread, 0LL);
if (v7) {
PsThawMultiProcess(Object, 0LL, 1LL);
KeLeaveCriticalRegion();
};
} while (v8 >= 0 && *(_DWORD*)(a3 + 44) == 1073807361);
return (unsigned int)v8;
};

Port一般是EPROCESS结构中DebugPort字段,偶尔是进程异常端口ExceptionPort字段。SuspendProcess为真则用DbgkpSuspendProcess挂起当前进程并发送消息,收到消息回复后用DbgkpResumeProcess唤醒当前进程,这俩函数用于控制被调试进程。其用DbgkpQueueMessage发送消息,一般需要用等待函数,等收到调试器回复后返回。

调试子系统向调试器发送调试事件前用DbgkpSuspendProcess,其内部用KeFreezeAllThread冻结被调试进程中非调试线程。调试线程会以阻塞的方式用DbgkpQueueMessage发送消息并进入等待状态。由此当被调试进程被中断到调试器时,整个被调试程序无响应。

之后调试子系统服务器通知调试器读取调试消息,调试器处理后回复给调试子系统,调试子系统唤醒被调试进程等待线程。唤醒后执行DbgkpResumeProcess,其内部用KeThawAllThreads回复被调试进程中所有进程。

每个线程KTHREAD结构中,FreezeCount和SuspendCount字段与线程执行状态相关。可调度执行的线程这俩字段为0。当被调试进程中断到调试器时,KeFreezeAllThread冻结非当前进程,即当前线程FreezeCount为0,其他线程为1。调试器收到调试事件后对被调试进程中所有线程依次用SuspendThread,则所有线SuspendCount计数为1。

调试子系统服务器

DebugObject内核对象用于用户态调试:

1
2
3
4
5
6
typedef struct _DEBUG_OBJECT {
KEVENT EventsPresent; //用于指示有调试事件发生的事件对象
FAST_MUTEX Mutex; //用于同步的互斥对象
LIST_ENTRY EventList; //保存调试事件的链表 调试消息队列
ULONG Flags; //标志位
} DEBUG_OBJECT, * PDEBUG_OBJECT;

例如WaitForDebugEvent对应的NtWaitForDebugEvent等待的就是EventsPresent对象。Flags位1代表结束调试会话时是否终止被调试进程,如DebugSetProcessKillOnExit就是设置这个标志位。

NtCreateDebugObject用于创建调试对象。调试器与调试子系统建立连接时,调试子系统为其创建一个调试对象,将其保存在调试器当前线程TEB的DbgSsReserved字段。

对于在调试器中启动被调试程序,系统创建进程时将调试器线程TEB的DbgSsReserved字段中保存的调试对象句柄传给创建进程的内核服务,内核中进程创建函数将该句柄对应的对象指针赋给新创建进程的EPROCESS结构的DebugPort字段。对于把调试器附加到已运行进程中,系统用内核DbgkpSetProcessDebugObject将创建好的调试对象附加到被调试进程,该函数除了将调试对象赋给EPROCESS的DebugPort字段,还用DbgkpMarkProcessPeb设置PEB的BeingDebugged字段。

DbgkpQueueMessage向一个调试对象的消息队列中追加调试事件。调试消息队列中每个节点为一个DBGKM_DEBUG_EVENT结构。

1
2
3
4
5
6
7
8
9
10
11
typedef struct _DBGKM_DEBUG_EVENT{
LIST_ENTRY EventList; //与兄弟节点相互链接
KEVENT ContinueEvent; //用于等待调试器回复的事件对象
CLIENT_ID ClientId; //俩DWORD 调试事件所属线程ID和进程ID
PEPROCESS Process; //被调试进程EPROCESS结构地址
PETHREAD Thread; //被调试进程中触发调试事件线程的ETHREAD地址
NTSTATUS Status; //对调试事件的处理结果
ULONG Flags; //标志
PETHREAD BackoutThread; //产生杜撰消息的线程
DBGKM_MSG ApiMsg; //调试事件详细信息
}DBGKM_DEBUG_EVENT, * PDBGKM_DEBUG_EVENT

DbgkpQueueMessage参数制定不需等待NOWAIT标志,则立刻通知调试器读取消息并返回。否则设置调试对象的EventPresent对象,通知调试器有消息读取,用KeWaitForSingleObject等待DBGKM_DEBUG_EVENT中ContinueEvent对象,等待调试器回复。调试器处理后,用ContinueDebugEvent调用nt!NtDebugContinue,根据参数指定的CLIENT_ID结构遍历调试对象消息队列,找到匹配的调试事件后用DbgkpWakeTarget设置要恢复的调试事件对象的ContinueEvent对象,使处于等待的被调试线程被唤醒而继续执行。栈帧如下,发生在被调试进程中。

1
2
3
4
5
6
7
8
9
nt!KeSetEvent //设置事件
nt!DbgkpQueueMessage //放入调试事件列表
nt!DbgkpSendApiMessage //格式化为消息结构
nt!DbgkForwardException //转发给调试子系统
nt!KiDispatchException //异常分发
nt!CommonDispatchException //建立异常结构
nt!KiTrap //执行INT3异常处理例程
ntdll!DbgBreakPoint //执行INT3指令 产生断点异常
ntdll!DbgUiRemoteBreakin //调用DbgUi中断函数

工作进程唤醒后读取调试对象中的消息队列。每读到一个调试事件时NtWaitForDebugEventDbgkpConvertKernelToUserStateChange将DBGKM_DEBUG_EVENT结构转为用户模式下的DBGUI_WAIT_STATE_CHANGE结构。栈帧如下:

1
2
3
4
5
6
7
8
9
10
11
12
nt!DbgkpConvertKernelToUserStateChange //读取调试事件
nt!NtWaitForDebugEvent //内核服务
nt!KiSystemService //内核服务分发
SharedUserData!SystemCallStub //系统调用
ntdll!ZwWaitForDebugEvent //调用等待调试事件的内核服务
dbgeng!LiveUserDebugServices::WaitForEvent
dbgeng!LiveUserTargetInfo::WaitForEvent
dbgeng!WaitForAnyTarget //依次等待每个调试目标
dbgeng!RawWaitForEvent //调试引擎内部函数
dbgeng!DebugClient::WaitForEvent //调试引擎等待接口
WinDBG!EngineLoop //调试循环
kernel32!BaseThreadStart //调试线程启动

读取一个调试事件后NtWaitForDebugEvent在DBGKM_DEBUG_EVENT的Flags字段设置已读标志。

当将调试器附加到一个已运行的进程时,为了向调试器报告以前发生的目前仍有意义的调试事件,调试子系统捏造一些调试信息来模拟过去的调试事件,称为杜撰调试消息。

内核服务NtDebugActiveProcess与已运行进程建立调试会话,其在调用DbgkpPostFakeProcessCreateMessages后,用DbgkpSetProcessDebugObject将调试对象设置到要调试的进程之前。DbgkpPostFakeProcessCreateMessagesDbgkpPostFakeThreadMessages遍历被调试进程的所有线程,再用DbgkpPostFakeModuleMessages投放杜撰模块加载消息,这俩函数都用DbgkpQueueMessage向消息队列添加调试消息。DbgkpSetProcessDebugObject将调试对象设置到要调试的进程,并遍历事件队列中所有事件,设置调试对象EventsPresent字段。当NtDebugActiveProcess服务返回后,调试器用NtWaitForDebugEvent可立刻等待成功并读取事件队列中调试事件。

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
__int64 __fastcall NtDebugActiveProcess(ULONG_PTR a1, void* a2) {
KPROCESSOR_MODE PreviousMode; // r14
__int64 result; // rax
__int64 v5; // rcx
struct _KTHREAD* CurrentThread; // rax
struct _EX_RUNDOWN_REF* v7; // rbx
_KPROCESS* Process; // rsi
NTSTATUS v9; // edi
unsigned __int64 Count; // rdi
__int64 v11; // rcx
__int16 v12; // ax
__int16 v13; // ax
BOOLEAN v14; // al
struct _KEVENT* v15; // rsi
PVOID Object[2]; // [rsp+40h] [rbp-59h] BYREF
_QWORD v17[14]; // [rsp+50h] [rbp-49h] BYREF
PreviousMode = KeGetCurrentThread()->PreviousMode;
Object[0] = 0LL;
Object[1] = 0LL;
result = ObpReferenceObjectByHandleWithTag(a1, 0x4F676244u, (__int64)Object, 0LL, 0LL);
if ((int)result >= 0) {
CurrentThread = KeGetCurrentThread();
v7 = (struct _EX_RUNDOWN_REF*)Object[0];
Process = CurrentThread->ApcState.Process;
if (Object[0] == Process || Object[0] == PsInitialSystemProcess)
v9 = -1073741790;
else {
LOBYTE(v5) = PreviousMode;
if ((unsigned __int8)PsTestProtectedProcessIncompatibility(v5, CurrentThread->ApcState.Process, Object[0]))
v9 = -1073740014;
else {
Count = v7[124].Count;
if ((Count & 1) == 0 || (memset(v17, 0, 0x68uLL), v17[1] = Count, v17[2] = 1LL, LOBYTE(v11) = 2, v9 = VslpEnterIumSecureMode(v11, 12LL, 0LL, v17), v9 >= 0)) {
if (!Process[1].Affinity.StaticBitmap[30] || (v12 = WORD2(Process[2].Affinity.StaticBitmap[20]), v12 != 332) && v12 != 452 || v7[176].Count && ((v13 = WORD2(v7[301].Ptr), v13 == 332) || v13 == 452)) {
Object[0] = 0LL;
v9 = ObReferenceObjectByHandle(a2, 2u, DbgkDebugObjectType, PreviousMode, Object, 0LL);
if (v9 >= 0) {
v14 = ExAcquireRundownProtection_0(v7 + 139);
v15 = (struct _KEVENT*)Object[0];
if (v14) {
DbgkpPostFakeProcessCreateMessages((ULONG_PTR)v7);
v9 = DbgkpSetProcessDebugObject((ULONG_PTR)v7, v15);
ExReleaseRundownProtection_0(v7 + 139);
}
else
v9 = -1073741558;
ObfDereferenceObject(v15);
};
}
else
v9 = -1073741637;
};
};
};
ObfDereferenceObjectWithTag(v7, 0x4F676244u);
return (unsigned int)v9;
};
return result;
};
__int64 __fastcall DbgkpPostFakeProcessCreateMessages(ULONG_PTR BugCheckParameter1, __int64 a2, _QWORD* a3) {
__int64 v4; // rbx
__int64 result; // rax
PVOID Object; // [rsp+30h] [rbp-68h] BYREF
__int64 v9; // [rsp+38h] [rbp-60h] BYREF
_OWORD v10[3]; // [rsp+40h] [rbp-58h] BYREF
v4 = 0LL;
Object = 0LL;
memset(v10, 0, sizeof(v10));
v9 = 0LL;
result = DbgkpPostFakeThreadMessages(BugCheckParameter1, a2, 0LL, &Object, &v9);
if ((int)result >= 0) {
KiStackAttachProcess(BugCheckParameter1);
DbgkpPostModuleMessages(BugCheckParameter1, Object, a2);
KiUnstackDetachProcess(v10, 0LL);
ObfDereferenceObjectWithTag(Object, 0x4F676244u);
result = 0LL;
v4 = v9;
};
*a3 = v4;
return result;
};

栈帧如下,发生在调试器进程中。

1
2
3
4
5
6
nt!DbgkpQueueMessage //放入消息队列
nt!DbgkpPostFakeThreadMessages //杜撰线程创建消息
nt!DbgkpPostFakeProcessCreateMessages
nt!NtDebugActiveProcess //调试已运行进程
nt!KiSystemService //系统服务分发函数
SharedUserData!SystemCallStub //系统调用

调试结束后要撤销调试会话时,系统用DbgkClearProcessDebugObject将被调试进程DebugPort恢复为NULL。恢复时函数遍历调试对象的消息队列,将关于该进程的调试事件清除,但不破坏调试对象。

支持用户态调试的内核服务:

服务名 描述
NtCreateDebugObject 创建调试对象
NtRemoveProcessDebug 分离调试对象
NtDebugActiveProcess 与已运行进程建立调试会话
NtSetInformationDebugObject 设置调试对象属性
NtDebugContinue 回复调试事件,恢复被调试进程
NtWaitForDebugEvent 等待调试事件
NtQueryDerbugFilterState 查询调试信息输出的过滤级别
NtSetDebugFilterState 设置调试信息输出的过滤级别

支持用户态调试的内核函数:

函数名 描述
DbgkCreateThread 采集线程创建事件
DbgkClearProcessDebugObject 将调试对象从指定进程中分离
DbgkpConvertKernelToUserStateChange 将DBGKM_DEBUG_EVENT转为DBGUI_WAIT_STATE_CHANGE
DbgkDebugObjectType 调用对象类型的全局指针
DbgkMarkProcessPeb 建立和解除调试会话时修改被调试进程中PEB的BeginDebugged字段
DbgkpSetProcessDebugObject 采集模块映射事件
DbgkMapViewOfSection 采集进程退出事件
DbgkExitProcess 访问指定进程DebugPort字段指定的调试对象
DbgkOpenProcessDebugPort 访问指定进程DebugPort字段指定的调试对象
DbgkpWakeTarget 设置ContinueEvent对象,唤醒等待调试器恢复的线程
DbgkpQueueMessage 向调试事件队列中加入消息
DbgkpResumeProcess 恢复执行被调试进程
DbgkpOpenHandles 打开进程/线程对象,增加引用计数
DbgkInitialize 系统启动早期初始化调试对象
DbgkpFreeDebugEvent 释放调试事件
DbgkUnMapViewOfSection 采集模块反映射事件
DbgkForwardException 向调试子系统通报异常
DbgkpPostFakeProcessCreateMessages 向调试子系统发送杜撰的进程创建消息
DbgkpPostFakeThreadMessages 向调试子系统发送杜撰的线程创建消息
DbgkpSendApiMessageLpc 向当前ExceptionPort字段的异常端口发送异常第二轮处理机会
DbgkpCloseObject 关闭调试对象,枚举系统内所有进程,某进程DebugPort为要关闭的对象则将其置0
DbgkpPostFakeModuleMessages 向调试子系统发送杜撰的模块信息
DbgkpSendApiMessage 发送调试事件
DbgkExitThread 采集线程退出事件
DbgkpProcessDebugPortMutex 全局互斥量对象,保护EPROCESS的DebugPort字段的访问
DbgkCopyProcessDebugPort 创建新进程时根据需要将父进程DebugPort复制到新进程中
DbgkpSectionToFileHandle 取得Section对象对应的文件句柄
DbgkpSuspendProcess 挂起被调试进程

调试子系统支持跨Windows登录对话进行调试,通过终端服务或快速用户切换功能。

NTDLL.DLL中调试支持例程

NTDLL.DLL中调试函数分为DbgUi、Dbg函数两类。

DbgUi函数有以下,是调试子系统向调试器提供的接口。

函数 说明
DbgUiDebugActiveProcess 上层为kernel32!DebugActiveProcess,下层为nt!NtDebugActiveProcess
DbgUiConnectToDbg 连接调试子系统,调用ZwCreateDebugObject
DbgUiConvertStateChangeStructure 将DBGUI_WAIT_STATE_CHANGE转为调试器所需DEBUG_EVENT结构
DbgUiGetThreadDebugObject 从调试器工作线程TEB读取调试对象
DbgUiSetThreadDebugObject 将调试对象记录到TEB
DbgUiIssueRemoteBreakin 在被调试进程中创建远程线程以使其中断到调试器,上层为kernel32!DebugBreakProcess
DbgUiContinue 恢复被调试进程,用NtDebugContinue
DbgUiWaitStateChange 等待调试事件,用NtWaitForDebugEvent,后者等待TEB中DbgSsReserved中DebugObject对象
DbgUiStopDebugging 停止调试,用NtRemoveProcessDebug

Dbg函数有:

函数 说明
DbgBreakPoint INT 3
DbgUserBreakPoint 断点指令
DbgPrint 打印调试信息
DbgPrompt 提示输入
DbgPrintReturnControlC
DbgBreakPointWithStatus
DbgSetDebugFilterState 设置调试信息输出的过滤级别,内部用NtSetDebugFilterState
DbgQueryDebugFilterState
DbgPrintEx

调试API

大多数都从KERNEL32.DLL中导出,有些在KERNEL32.DLL中实现,有些在NTDLL.DLL中实现。

函数 描述 实现
CheckRemoteDebuggerPresent 判断指定进程是否处于被调试状态 NtQueryInformationProcess查询PEB
ContinueDebugEvent 供调试器恢复被调试进程运行,回复调试事件 ntdll!DbgUiContinue
DebugActiveProcess 供调试器附加到已运行的进程 ntdll!DbgUiDebugActiveProcess
DebugActiveProcessStop 分离调试会话 将进程ID转为句柄后用ntdll!DbgUiStopDebugging
DebugBreak 在当前进程中产生断点异常 ntdll!DbgBreakpoint
DebugBreakProcess 在指定进程中产生断点异常 ntdll!DbgIssueRemoteBreakin
DebugSetProcessKillOnExit 指定调试器线程退出时是否终止被调试进程 DbgUiGetThreadDebugObjectNtSetInformationDebugObject
FatalExit 废弃 ExitProcess
FlushInstructionCache 当调试器修改代码段时用该函数冲转缓存 NtFlushInstructionCache
GetThreadContext 获取指定线程上下文结构 NtGetContextThread
GetThreadSelectorEntry 从指定线程LDT中获取指定选择子所对应表项Entry NtQueryInformationThread
IsDebuggerPresent 判断调用进程是否被调试 检查PEB的BeingDebugged字段
OutputDebugString 供应用程序输出调试信息 通过产生异常实现RaiseException(DBG_PRINTEXCEPTION_C,0,2,...)
ReadProcessMemory 读取指定进程空间中指定内存区域 NtReadVirtualMemory
SetThreadContext 设置指定线程上下文信息 NtSetContextThread
WaitForDebugEvent 供调试器工作线程等待调试事件 ntdll!DbgUiWaitStateChange
WriteProcessMemory 向指定进程空间指定内存区域写入数据 NtWriteVirtualMemory