Windows内核重拾:DebugObject
0x00 什么是DebugObject
在Windows系统中对调试的支持主要在三个模块中,分别为内核执行体(多核处理器一般为ntkrnlmp.exe,以Dbgk为前缀,主要负责注册和监听调试事件、管理调试对象等)、原生系统库ntdll.dll(DbgUi为前缀,负责将底层的调试对象封装起来)、子系统dll(kernelbase.dll)。而调试对象(DebugObject)是一个结构体,支持用户模式调试,由一系列的标志(决定对象的状态)、一个事件、一个调试事件双链表组成。1
2
3
4
5
6
7
8
9
10typedef struct _DEBUG_OBJECT {
// Event thats set when the EventList is populated.
KEVENT EventsPresent;
// Mutex to protect the structure
FAST_MUTEX Mutex;
// Queue of events waiting for debugger intervention
LIST_ENTRY EventList;
// Flags for the object
ULONG Flags;
} DEBUG_OBJECT, *PDEBUG_OBJECT;
0x01 DebugObject是如何产生的
DebugObject在内核中由NtCreateDebugObject函数产生,每次启动调试都需要调用NtCreateDebugObject产生一个DebugObject,在NtCreateDebugObject函数中会调用ObInsertObject将创建的DebugObject插入到一个句柄表中,后续再利用句柄访问这个DebugObject。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
73NTSTATUS
NtCreateDebugObject (
OUT PHANDLE DebugObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG Flags
)
{
NTSTATUS Status;
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
PDEBUG_OBJECT DebugObject;
PAGED_CODE();
// Get previous processor mode and probe output arguments if necessary.
// Zero the handle for error paths.
PreviousMode = KeGetPreviousMode();
try {
if (PreviousMode != KernelMode) {
ProbeForWriteHandle (DebugObjectHandle);
}
*DebugObjectHandle = NULL;
} except (ExSystemExceptionFilter ()) { // If previous mode is kernel then don't handle the exception
return GetExceptionCode ();
}
if (Flags & ~DEBUG_KILL_ON_CLOSE) {
return STATUS_INVALID_PARAMETER;
}
// Create a new debug object and initialize it.
Status = ObCreateObject (PreviousMode,
DbgkDebugObjectType,
ObjectAttributes,
PreviousMode,
NULL,
sizeof (DEBUG_OBJECT),
0,
0,
&DebugObject);
if (!NT_SUCCESS (Status)) {
return Status;
}
ExInitializeFastMutex (&DebugObject->Mutex);
InitializeListHead (&DebugObject->EventList);
KeInitializeEvent (&DebugObject->EventsPresent, NotificationEvent, FALSE);
if (Flags & DEBUG_KILL_ON_CLOSE) {
DebugObject->Flags = DEBUG_OBJECT_KILL_ON_CLOSE;
} else {
DebugObject->Flags = 0;
}
// Insert the object into the handle table
Status = ObInsertObject (DebugObject,
NULL,
DesiredAccess,
0,
NULL,
&Handle);
if (!NT_SUCCESS (Status)) {
return Status;
}
try {
*DebugObjectHandle = Handle;
} except (ExSystemExceptionFilter ()) {
//
// The caller changed the page protection or deleted the memory for the handle.
// No point closing the handle as process rundown will do that and we don't know its still the same handle
//
Status = GetExceptionCode();
}
return Status;
}
NtCreateDebugObject函数主要实现在ObCreateObject函数,关键参数为DbgkDebugObjectType,DbgkDebugObjectType是一个全局变量,类型为_OBJECT_TYPE。每次调试生成的DebugObject都会依赖于DbgkDebugObjectType这个全局变量,因此我们可以通过修改DbgkDebugObjectType某些标志位做一些猥琐的事情,比如说反调试,并且由于只是内核中的一个标志位很难找到不能调试的原因。1
2
3
4
5
6
7
8
9
10
11
12
13kd> dt nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY
+0x010 Name : _UNICODE_STRING
+0x020 DefaultObject : Ptr64 Void
+0x028 Index : UChar
+0x02c TotalNumberOfObjects : Uint4B
+0x030 TotalNumberOfHandles : Uint4B
+0x034 HighWaterNumberOfObjects : Uint4B
+0x038 HighWaterNumberOfHandles : Uint4B
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b0 TypeLock : _EX_PUSH_LOCK
+0x0b8 Key : Uint4B
+0x0c0 CallbackList : _LIST_ENTRY
DbgkDebugObjectType初始化在DbgkInitialize函数中,也是被ObCreateObjectType创建,关键参数为一个_OBJECT_TYPE_INITIALIZER结构体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
27kd> dt _OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE_INITIALIZER
+0x000 Length : Uint2B
+0x002 ObjectTypeFlags : UChar
+0x002 CaseInsensitive : Pos 0, 1 Bit
+0x002 UnnamedObjectsOnly : Pos 1, 1 Bit
+0x002 UseDefaultObject : Pos 2, 1 Bit
+0x002 SecurityRequired : Pos 3, 1 Bit
+0x002 MaintainHandleCount : Pos 4, 1 Bit
+0x002 MaintainTypeList : Pos 5, 1 Bit
+0x002 SupportsObjectCallbacks : Pos 6, 1 Bit
+0x004 ObjectTypeCode : Uint4B
+0x008 InvalidAttributes : Uint4B
+0x00c GenericMapping : _GENERIC_MAPPING
+0x01c ValidAccessMask : Uint4B
+0x020 RetainAccess : Uint4B
+0x024 PoolType : _POOL_TYPE
+0x028 DefaultPagedPoolCharge : Uint4B
+0x02c DefaultNonPagedPoolCharge : Uint4B
+0x030 DumpProcedure : Ptr64 void
+0x038 OpenProcedure : Ptr64 long
+0x040 CloseProcedure : Ptr64 void
+0x048 DeleteProcedure : Ptr64 void
+0x050 ParseProcedure : Ptr64 long
+0x058 SecurityProcedure : Ptr64 long
+0x060 QueryNameProcedure : Ptr64 long
+0x068 OkayToCloseProcedure : Ptr64 unsigned char

0x02 DebugObject用于反调试实例
TenProtect反外挂开启后,调试进程会附加失败错误号为0xC0000022(不仅仅是调试游戏进程,调试任何进程都会产生这个错误),具体原因为A process has requested access to an object, but has not been granted those access rights.

根据网上公布的bypass的代码,可以很容易的发现TP主要是将DbgkDebugObjectType.TypeInfo.ValidAccessMask标志位清零,ValidAccessMask从名称上就可以知道是和权限相关,而原本默认为DEBUG_ALL_ACCESS(0x1F000F),被清零后就什么权限都没有了,创建了一个毫无用处的DebugObject,并且由于DbgkDebugObjectType是全局的与特定进程无关,导致调试任意进程都会失败。
1 | NTSTATUS |
通过双机调试及相关资料,可以找到ValidAccessMask被清零后调试失败的具体代码位置在NtDebugActiveProcess函数中,在NtDebugActiveProcess函数中会通过ObReferenceObjectByHandle对传入的DebugObjectHandle对象句柄的访问权限进行检查,需要DEBUG_PROCESS_ASSIGN权限,但ValidAccessMask被清零后创建的DebugObject没有DEBUG_PROCESS_ASSIGN权限,函数会返回c0000022错误。
1 | kd> bu nt!NtCreateDebugObject+0x86 |
0x03 简单总结
- Debugobject是一个对调试很重要的一个结构体,用于支持用户模式调试。
- Debugobject创建过程中会依赖一个名为DbgkDebugObjectType的内核全局变量,通过修改这个全局变量可以影响调试过程的创建,并且影响是全局的,对所有进程有效。
- TP通过将DbgkDebugObjectType.TypeInfo.ValidAccessMask清零达到反调目的,可能也是无奈之举毕竟X64的PatchGuard让在内核中能干的事越来越少,至于为什么修改ValidAccessMask不会触发PatchGuard可能DbgkDebugObjectType现在还没纳入PatchGuard保护范围。
- 代码可以参考https://gist.github.com/geemion/b61aa49e1b19dc8421b953ec3939fa4f
0x04 参考资料
- [1] 深入解析Windows操作系统
- [2] WRK1.2
- [3] VaildAccessMask