Windows内核重拾:DebugObject

文章目录
  1. 1. 0x00 什么是DebugObject
  2. 2. 0x01 DebugObject是如何产生的
  3. 3. 0x02 DebugObject用于反调试实例
  4. 4. 0x03 简单总结
  5. 5. 0x04 参考资料

0x00 什么是DebugObject

在Windows系统中对调试的支持主要在三个模块中,分别为内核执行体(多核处理器一般为ntkrnlmp.exe,以Dbgk为前缀,主要负责注册和监听调试事件、管理调试对象等)、原生系统库ntdll.dll(DbgUi为前缀,负责将底层的调试对象封装起来)、子系统dll(kernelbase.dll)。而调试对象(DebugObject)是一个结构体,支持用户模式调试,由一系列的标志(决定对象的状态)、一个事件、一个调试事件双链表组成。

1
2
3
4
5
6
7
8
9
10
typedef 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
73
NTSTATUS
NtCreateDebugObject (
OUT PHANDLE DebugObjectHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG Flags
)

{

NTSTATUS Status;
HANDLE Handle;
KPROCESSOR_MODE PreviousMode;
PDEBUG_OBJECT DebugObject;

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
13
kd> 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
27
kd> 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


Win7 X64下DebugObject几个关键结构体关系

0x02 DebugObject用于反调试实例

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


TProtect附加失败错误

根据网上公布的bypass的代码,可以很容易的发现TP主要是将DbgkDebugObjectType.TypeInfo.ValidAccessMask标志位清零,ValidAccessMask从名称上就可以知道是和权限相关,而原本默认为DEBUG_ALL_ACCESS(0x1F000F),被清零后就什么权限都没有了,创建了一个毫无用处的DebugObject,并且由于DbgkDebugObjectType是全局的与特定进程无关,导致调试任意进程都会失败。

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
NTSTATUS
NtDebugActiveProcess (
IN HANDLE ProcessHandle,
IN HANDLE DebugObjectHandle
)

{

NTSTATUS Status;
KPROCESSOR_MODE PreviousMode;
PDEBUG_OBJECT DebugObject;
PEPROCESS Process;
PETHREAD LastThread;
PAGED_CODE ();
PreviousMode = KeGetPreviousMode();
Status = ObReferenceObjectByHandle (ProcessHandle,
PROCESS_SET_PORT,
PsProcessType,
PreviousMode,
&Process,
NULL);
if (!NT_SUCCESS (Status)) {
return Status;
}
// Don't let us debug ourselves or the system process.
if (Process == PsGetCurrentProcess () || Process == PsInitialSystemProcess) {
ObDereferenceObject (Process);
return STATUS_ACCESS_DENIED;
}
Status = ObReferenceObjectByHandle (DebugObjectHandle,
DEBUG_PROCESS_ASSIGN,
DbgkDebugObjectType,
PreviousMode,
&DebugObject,
NULL);
if (NT_SUCCESS (Status)) {
// We will be touching process address space. Block process rundown.
if (ExAcquireRundownProtection (&Process->RundownProtect)) {
// Post the fake process create messages etc.
Status = DbgkpPostFakeProcessCreateMessages (Process,
DebugObject,
&LastThread);
// Set the debug port. If this fails it will remove any faked messages.
Status = DbgkpSetProcessDebugObject (Process,
DebugObject,
Status,
LastThread);
ExReleaseRundownProtection (&Process->RundownProtect);
} else {
Status = STATUS_PROCESS_IS_TERMINATING;
}
ObDereferenceObject (DebugObject);
}
ObDereferenceObject (Process);

return Status;
}

通过双机调试及相关资料,可以找到ValidAccessMask被清零后调试失败的具体代码位置在NtDebugActiveProcess函数中,在NtDebugActiveProcess函数中会通过ObReferenceObjectByHandle对传入的DebugObjectHandle对象句柄的访问权限进行检查,需要DEBUG_PROCESS_ASSIGN权限,但ValidAccessMask被清零后创建的DebugObject没有DEBUG_PROCESS_ASSIGN权限,函数会返回c0000022错误。

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
kd> bu nt!NtCreateDebugObject+0x86
kd> g
Breakpoint 1 hit
nt!NtCreateDebugObject+0x86:
fffff800`02ebce26 e8b5bef1ff call nt!ObCreateObject (fffff800`02dd8ce0)
kd> .process
Implicit process is now fffffa80`03904b30
kd> .process fffffa80`03904b30
Implicit process is now fffffa80`03904b30
WARNING: .cache forcedecodeuser is not enabled
kd> kv
Child-SP RetAddr : Args to Child : Call Site
fffff880`04ef9b60 fffff800`02ade8d3 : fffffa80`03782b60 00000000`001f000f 00000000`00000000 00000000`00000001 : nt!NtCreateDebugObject+0x95
fffff880`04ef9be0 00000000`77541c4a : 00000000`73d1ebdd 00000000`0059f658 00000000`001f000f 00000000`020eec20 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`04ef9be0)
00000000`020ee368 00000000`73d1ebdd : 00000000`0059f658 00000000`001f000f 00000000`020eec20 00000000`020ee350 : ntdll!ZwCreateDebugObject+0xa
00000000`020ee370 00000000`73d0cf87 : 00000000`00000000 00000000`00000000 00000000`020eec20 00000000`01fbf9b4 : wow64!whNtCreateDebugObject+0x6d
00000000`020ee3c0 00000000`73c92776 : 00000000`752e60e2 00000000`00000023 00000000`00000246 00000000`01fbfab4 : wow64!Wow64SystemServiceEx+0xd7
00000000`020eec80 00000000`73d0d07e : 00000000`00000000 00000000`73c91920 00000000`00000000 00000000`00000000 : wow64cpu!TurboDispatchJumpAddressEnd+0x2d
00000000`020eed40 00000000`73d0c549 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : wow64!RunCpuSimulation+0xa
00000000`020eed90 00000000`7756e707 : 00000000`00000000 00000000`fffdf000 00000000`fffd8000 00000000`00000000 : wow64!Wow64LdrpInitialize+0x429
00000000`020ef2e0 00000000`7751c32e : 00000000`020ef3a0 00000000`00000000 00000000`fffdf000 00000000`00000000 : ntdll! ?? ::FNODOBFM::`string'+0x29364
00000000`020ef350 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!LdrInitializeThunk+0xe
.....
Breakpoint 4 hit
nt!NtDebugActiveProcess+0x100:
fffff800`02f2d2f0 e8ebc5e8ff call nt!ObReferenceObjectByHandle (fffff800`02db98e0)
kd> p
nt!NtDebugActiveProcess+0x105:
fffff800`02f2d2f5 8bf8 mov edi,eax
kd> r rax
rax=00000000c0000022

0x03 简单总结

  • Debugobject是一个对调试很重要的一个结构体,用于支持用户模式调试。
  • Debugobject创建过程中会依赖一个名为DbgkDebugObjectType的内核全局变量,通过修改这个全局变量可以影响调试过程的创建,并且影响是全局的,对所有进程有效。
  • TP通过将DbgkDebugObjectType.TypeInfo.ValidAccessMask清零达到反调目的,可能也是无奈之举毕竟X64的PatchGuard让在内核中能干的事越来越少,至于为什么修改ValidAccessMask不会触发PatchGuard可能DbgkDebugObjectType现在还没纳入PatchGuard保护范围。
  • 代码可以参考https://gist.github.com/geemion/b61aa49e1b19dc8421b953ec3939fa4f

0x04 参考资料