我目前正在使用liveKD执行内核debugging。
在所有的情况下,我的阻塞发生(a ::CloseHandle()
函数调用永远不会返回)我碰巧有一个stacktrace阻塞在一个synchronisationEvent
的内核。
但是当我这样做!object 12345678
如果123456789是我的synchronisationEvent
在进程的线程信息中报告的,它说Not a valid object (ObjectType invalid).
我担心如果在用户模式下的应用程序级别的损坏可能会损坏内核? 窗户是否可以保证像隔离空间这样的东西会阻止类似的东西?
该应用程序的代码密集使用C ++,COM / DCOM和Win32。
一个评论询问了关于堆栈跟踪和句柄types的更多信息。 在这种情况下,它与串行通信端口有关。 但我想我也有它的文件句柄(尚未debugging这些情况下)这是我有一种堆栈跟踪:
THREAD 856a2d48 Cid 0660.0350 Teb:7ff25000 Win32Thread:ffaaedd8 WAIT :(执行)KernelMode不可警告 860c6f9c SynchronizationEvent IRP列表: babea5d8:(0006,01d8)标志:00000404 Mdl:00000000 不是冒充 DeviceMap 89809fc8 拥有过程86212d40 Image:DataCaptorIS.exe 附加stream程N / A图片:N / A 等待开始TickCount 27315407 Ticks:6067021(1:02:17:26.134) 上下文切换计数2259 IdealProcessor:0 UserTime 00:00:04.976 内核时间00:00:02.184 Win32的起始地址0x775c03e9 堆栈初始化8aa0dfd0当前8aa0da98基本8aa0e000限制8aa0b000调用0 优先级9 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5 ChildEBP RetAddr与孩子联系 8aa0dab0 824bfced 856a2d48 00000000 8ab00120 nt!KiSwapContext + 0x26(FPO:[使用EBP] [0,0,4]) 8aa0dae8 824beb4b 856a2e08 856a2d48 860c6f9c nt!KiSwapThread + 0x266 8aa0db10 824b856f 856a2d48 856a2e08 00000000 nt!KiCommitThreadWait + 0x1df 8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject + 0x393 8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose + 0x332(FPO:[Non-Fpo]) 8aa0dbe4 886206b9 babea5d8 861986d0 bc859308 nt!IofCallDriver + 0x63 8aa0dc08 82478c1e 861986d0 860c6890 00000800 serenum!Serenum_CreateClose + 0x77(FPO:[Non-Fpo]) 8aa0dc20 82673be6 84aa7a00 bc8592f0 00000000 nt!IofCallDriver + 0x63 8aa0dc64 826647c9 bc859308 bc859308 bc8592f0 nt!IopDeleteFile + 0x10c 8aa0dc7c 824ba1e0 00000000 856a2d48 bc8592f0 nt!ObpRemoveObjectRoutine + 0x59 8aa0dc90 824ba150 bc859308 82687556 960a5578 nt!ObfDereferenceObjectWithTag + 0x88(FPO:[0,0,3]) 8aa0dc98 82687556 960a5578 856a2d48 00000c80 nt!ObfDereferenceObject + 0xd(FPO:[0,1,0]) 8aa0dcdc 8268727c 960a5578 a7d21900 86212d40 nt!ObpCloseHandleTableEntry + 0x21d 8aa0dd0c 82687616 86212d40 856a2d01 0763f3b4 nt!ObpCloseHandle + 0x7f 8aa0dd28 8247f8a6 00000c80 0763f3b8 775d7094 nt!NtClose + 0x4e 8aa0dd28 775d7094 00000c80 0763f3b8 775d7094 nt!KiSystemServicePostCall(FPO:[0,3] TrapFrame @ 8aa0dd34) 警告:框架IP不在任何已知的模块。 以下框架可能是错误的。 0763f3b8 00000000 00000000 00000000 00000000 0x775d7094
此堆栈跟踪指出线程正在等待synchronisationEvent
860c6f9c。
命令kd> !object 860c6f9c
返回Not a valid object (ObjectType invalid)
。 我不知道这是否意味着内核中的synchronisationEvent
已损坏。 当我在该过程的其他synchronisationEvent上应用该命令时,我得到的输出如下所示:
0:kd>!对象95369c68 对象:95369c68types:(84aa6378)事件 ObjectHeader:95369c50(新版本) HandleCount:1 PointerCount:2
在应用程序级别,在usermode中,这种情况发生在应该取消和清除任何IRP的代码之后:
::CancelIoEx(m_handle_to_serial_port_com); WaitForRequestToComplete(); // our function calls ::GetOverlappedResult(..., bWait) for any OVERLAPPED that was pending, with bWait == TRUE ::PurgeComm(m_handle_to_serial_port_com); WaitForRequestToComplete(); // our function calls ::GetOverlappedResult(..., bWait) for any OVERLAPPED that was pending, with bWait == TRUE ::CloseHandle(m_handle_to_serial_port_com); // the closeHandle which never returns
问题确实是随机发生的。 有时需要几天才能复制。
显示地址为86184f9c的synchronisationEvent对象的内存(在具有相同错误的另一台机器上):
0:kd> dp 86184f9c - @@ c ++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header,Body)) 86184f84 00000006 00000000 00000000 00000000 86184f94 00000000 00000001 00040001 00000000 86184fa4 85917420 85917420 00000000 00000000 86184fb4 00000000 00000000 00000000 0000000d 86184fc4 86184890 00000040 00000000 00000800 86184fd4 00000000 85747a68 00000000 00000000 86184fe4 00000000 00000000 86184fec 86184fec 86184ff4 86184ff4 86184ff4 96d4c000 040d0000
并试图显示对象头:
0:kd> dt nt!_object_header 86184f9c - @@ c ++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header,Body)) 找不到指定的字段成员。
这绝对是应该的。 如果内核在用户模式下受到篡改,那么您将会遇到一个安全漏洞,可能会影响机器上的所有用户。 你可以通过使内核崩溃来拒绝服务。 您可以通过利用内核缓冲区溢出来提升权限。 你可以通过使用内核信息泄露漏洞来窃取别人的数据。
仅仅因为你喂给内核不好的数据并不意味着它有这样的阴谋。 内核可能足够聪明以检测和防止这些问题,或者它可能会执行任何代码,可能遭受来自userland用户输入的损坏。
如果实际上发现了一个可以让内核崩溃的错误,读取传递给内核的其他人的数据,或者类似的东西,那么你应该把它报告给Microsoft。 如果您怀疑自己找到了某些东西,请尝试联系MS支持人员,看看他们能否提供帮助。 他们是操作系统方面的专家,很可能能够识别出您的怀疑缺陷是否是一个真正的缺陷。
这仅仅是为什么!object
不能在给定的地址上工作。
TL; DR:您的Synchronization事件没有损坏。 传递给!object
命令的地址不是一个内核对象,它只是一个内核结构。
kd !object
命令只是寻找一个特殊的结构(即nt!_OBJECT_HEADER
),它是所有内核对象的前缀。 更准确地说,内核结构只要在它之前有一个nt!_OBJECT_HEADER
就成为一个对象。 一旦一个结构被这个_OBJECT_HEADER
前缀,它就成为一个内核对象,然后被内核对象管理器(特别是所有以Ob
前缀开头的内核函数,但是在内核中有对象管理中涉及的其他函数)处理。
如果内核想要创建一个事件,但是特别是如果这个对象不必越过用户地/内核地界限(或者如果不需要参考计数),那么内核可能会创建一个nt!_KEVENT
结构,而没有nt!_OBJECT_HEADER
。
检查地址是否是内核对象(或不)
看看你的堆栈跟踪,我们有这两行:
8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject+0x393 8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose+0x332 (FPO: [Non-Fpo])
幸运的是, serial.sys
是一个微软驱动程序,所以我们有符号信息。 看看serial!SerialClose
内的代码serial!SerialClose
大约偏移量0x332,我们有这样的代码:
PAGESER:0001EEFC lea eax, [esi+654h] PAGESER:0001EF02 push ebx ; Timeout PAGESER:0001EF03 push ebx ; Alertable PAGESER:0001EF04 push ebx ; WaitMode PAGESER:0001EF05 push ebx ; WaitReason PAGESER:0001EF06 push eax ; Object PAGESER:0001EF07 call ds:__imp__KeWaitForSingleObject@20 ; KeWaitForSingleObject(x,x,x,x,x)
事件( KEVENT
类型)等待的代码来自[esi+0x654]
…在函数开始处的回溯,我们有:
PAGESER:0001EBD5 mov esi, [ebp+DeviceObject] PAGESER:0001EBD8 push edi PAGESER:0001EBD9 mov esi, [esi+_DEVICE_OBJECT.DeviceExtension]
所以esi(在[esi+0x654]
)是设备对象的设备扩展名。
在整个串行驱动程序代码中搜索此偏移量将返回几个实例。 事件初始化在serial!SerialCreateDevObj
完成serial!SerialCreateDevObj
:
PAGESRP0:000194AF push esi ; State PAGESRP0:000194B0 push 1 ; Type PAGESRP0:000194B2 lea eax, [ebx+654h] PAGESRP0:000194B8 push eax ; Event PAGESRP0:000194B9 call edi ; KeInitializeEvent(x,x,x) ; KeInitializeEvent(x,x,x)
这告诉我们KEVENT
是内核结构,而不是内核对象,因为它使用KeInitializeEvent
。
KEVENT更多一点
假设我有一个带有SynchronizationEvent的线程:
kd> !thread ffffe001ef7a5400 THREAD ffffe001ef7a5400 Cid 0538.054c Teb: 00007ff7a9869000 Win32Thread: fffff901406825d0 WAIT: (Executive) coreelMode Non-Alertable ffffd0006dba6278 SynchronizationEvent ...
内核(和kd)知道线程正在等待,因为这个线程有一个waitblocklist,它不是空的:
kd> dt _kthread ffffe001ef7a5400 waitblocklist ntdll!_KTHREAD +0x0d0 WaitBlockList : 0xffffe001`ef7a5540 _KWAIT_BLOCK
等待是不可警告的,因为可alertable
字段是0:
kd> dt _kthread ffffe001ef7a5400 alertable ntdll!_KTHREAD +0x074 Alertable : 0y0
这是一个内核模式(!=用户模式)等待,因为线程waitmode
是0:
kd> dt _kthread ffffe001ef7a5400 waitmode ntdll!_KTHREAD +0x187 WaitMode : 0 ''
WaitBlockList
线程是_KWAIT_BLOCK
类型的结构:
kd> dt _kwait_block 0xffffe001`ef7a5540 ntdll!_KWAIT_BLOCK +0x000 WaitListEntry : _LIST_ENTRY [ 0xffffd000`6dba6280 - 0xffffd000`6dba6280 ] +0x010 WaitType : 0x1 '' +0x011 BlockState : 0x4 '' +0x012 WaitKey : 0 +0x014 SpareLong : 0n1089 +0x018 Thread : 0xffffe001`ef7a5400 _KTHREAD +0x018 NotificationQueue : 0xffffe001`ef7a5400 _KQUEUE +0x020 Object : 0xffffd000`6dba6278 Void +0x028 SparePtr : (null)
如果你看上面的_KWAIT_BLOCK
,你可以看到有一个Object
字段,它表示线程正在等待的对象。
我们知道这是一个事件,但是所有的可分派对象都有一个dispatch头,所以我们可以在nt!_DISPATCHER_HEADER
结构的帮助下dt
Object
指针。
KEVENT
可以作为由KeInitializeEvent()
初始化的独立数据结构或作为由NtCreateEvent()
创建的内核(事件)对象存在:如果事件使用KeInitializeEvent()
初始化,那么它不是内核对象,而如果使用NtCreateEvent()
那么它就是一个内核对象。
nt!_KEVENT
结构只是一个nt!_KEVENT
结构的包装。
0: kd> dt _KEVENT nt!_KEVENT +0x000 Header : _DISPATCHER_HEADER kd> dt _dispatcher_header 0xffffd000`6dba6278 ntdll!_DISPATCHER_HEADER +0x000 Lock : 0n1594228737 +0x000 LockNV : 0n1594228737 +0x000 Type : 0x1 '' +0x001 Signalling : 0 '' +0x002 Size : 0x6 '' +0x003 Reserved1 : 0x5f '_' +0x000 TimerType : 0x1 '' +0x001 TimerControlFlags : 0 '' +0x001 Absolute : 0y0 +0x001 Wake : 0y0 +0x001 EncodedTolerableDelay : 0y000000 (0) +0x002 Hand : 0x6 '' +0x003 TimerMiscFlags : 0x5f '_' +0x003 Index : 0y011111 (0x1f) +0x003 Inserted : 0y1 +0x003 Expired : 0y0 +0x000 Timer2Type : 0x1 '' +0x001 Timer2Flags : 0 '' +0x001 Timer2Inserted : 0y0 +0x001 Timer2Expiring : 0y0 +0x001 Timer2CancelPending : 0y0 +0x001 Timer2SetPending : 0y0 +0x001 Timer2Running : 0y0 +0x001 Timer2Disabled : 0y0 +0x001 Timer2ReservedFlags : 0y00 +0x002 Timer2Reserved1 : 0x6 '' +0x003 Timer2Reserved2 : 0x5f '_' +0x000 QueueType : 0x1 '' +0x001 QueueControlFlags : 0 '' +0x001 Abandoned : 0y0 +0x001 DisableIncrement : 0y0 +0x001 QueueReservedControlFlags : 0y000000 (0) +0x002 QueueSize : 0x6 '' +0x003 QueueReserved : 0x5f '_' +0x000 ThreadType : 0x1 '' +0x001 ThreadReserved : 0 '' +0x002 ThreadControlFlags : 0x6 '' +0x002 CycleProfiling : 0y0 +0x002 CounterProfiling : 0y1 +0x002 GroupScheduling : 0y1 +0x002 AffinitySet : 0y0 +0x002 ThreadReservedControlFlags : 0y0000 +0x003 DebugActive : 0x5f '_' +0x003 ActiveDR7 : 0y1 +0x003 Instrumented : 0y1 +0x003 Minimal : 0y1 +0x003 Reserved4 : 0y011 +0x003 UmsScheduled : 0y1 +0x003 UmsPrimary : 0y0 +0x000 MutantType : 0x1 '' +0x001 MutantSize : 0 '' +0x002 DpcActive : 0x6 '' +0x003 MutantReserved : 0x5f '_' +0x004 SignalState : 0n0 +0x008 WaitListHead : _LIST_ENTRY [ 0xffffe001`ef7a5540 - 0xffffe001`ef7a5540 ]
只要给出一个内核事件,就可以知道哪个线程正在等待事件成为处于信号状态(正在设置事件),但是最大的问题是你无法知道谁应该设置这个特定的事件。
在你的情况下,只有三个函数将事件设置在串口驱动程序中: serial!SerialSetPendingDpcEvent
, serial!SerialDpcEpilogue
和serial!SerialInsertQueueDpc
。 获得这些功能肯定是另一个问题…
CloseHandle()只有在句柄的所有挂起的IO完成后才会结束。 根据你发布的线程信息,这个线程至少有一个未决的IRP – 我会看看是否是你试图关闭的同一个对象。
FWIW, I / O取消并不总是被支持 – 您的串行设备是否支持取消?
在环0中运行的内核不能被在环3中运行的进程所改变。Windows在微处理器的硬件支持下中继以实现存储器和I / O隔离,因此在内核中任何进程都不能访问存储器空间,也不在其他用户的内存空间。
访问内核的唯一方式是通过系统调用。 Windows中的系统调用采用称为“本机API”的API形式。 一个例子是NtCreateFile
,它是代表对CreateFile
函数的调用所调用的函数。 NtCreateFile
必须检查所有参数的有效性。 这个相同的函数可以从ZwCreateFile
的内核中获得。 当从内核调用时,它不进行检查,因为内核信任在内核模式下运行的任何代码。