Windows内核是否安全,不会在用户模式下发生损坏
Is Windows Kernel safe from corruptions that could happen in User mode
我目前正在使用liveKD执行内核调试。
在所有发生阻塞的情况下(一个永远不会返回的::CloseHandle()
函数调用),我碰巧有一个堆栈跟踪,它在synchronisationEvent
上的内核中进行阻塞。
但是,当我执行!object 12345678
时,如果123456789是进程的线程信息中报告的synchronisationEvent
,它会显示Not a valid object (ObjectType invalid).
我担心用户模式下应用程序级别的损坏是否会损坏内核?windows是否保证内存空间的分离,以防止类似的事情发生?
应用程序代码集中使用C++、COM/DCOM和Win32。
一条评论要求提供有关堆栈跟踪和句柄类型的更多信息。在这种情况下,它与串行com端口有关。但我想我也有它用于文件句柄(还没有调试这些情况)这就是我的堆叠竞赛:
THREAD 856a2d48 Cid 0660.0350 Teb:7ff25000 Win32线程:ffaaedd8 WAIT:(Executive)KernelMode不可报警860c6f9c同步事件IRP列表:babea5d8:(0006,01d8)标志:00000404 Mdl:000000000不模拟DeviceMap 89809fc8拥有进程86212d40图像:DataCaptorIS.exe附件进程不适用图像:不适用等待开始TickCount 27315407 Ticks:6067021(1:02:17:26.134)上下文切换计数2259 IdealProcessor:0用户时间00:00:976内核时间00:00:02.184Win32起始地址0x775c03e9堆栈初始化8aa0dfd0当前8aa0da98基本8aa0e000限制8aa0b000调用0优先级9基本优先级8异常提升0前景提升0 IoPriority 2页面优先级5ChildEBP RetAddr Args到Child8aa0dab0 824bfed 856a2d48 00000000 8ab00120 nt!KiSwapContext+0x26(FPO:[使用EBP][0,0,4])8aa0dae8 824beb4b 856a2e08 856a2d48 860c6f9c nt!KiSwapThread+0x2668aa0db10 824b856f 856a2d48 856a2e08 00000000 nt!KiCommitThreadWait+0x1df8aa0db88 914539fb 860c6f9c 0000000000000000nt!KeWaitForSingleObject+0x3938aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c系列!串行关闭+0x332(FPO:[非FPO])在公元前859308年8月8日发生的一起事件中!IofCallDriver+0x638aa0dc08 82478c1e 861986d0 860c6890 00000800小夜曲!Serum_CreateClose+0x77(FPO:[非FPO])8aa0dc20 82673be6 84aa7a00 bc8592f0 00000000nt!IofCallDriver+0x638aa0dc64 826647c9 bc859308 bc859308bc8592f0 nt!IopDeleteFile+0x10c8aa0dc7c 824ba1e 00000000 856a2d48 bc8592f0 nt!ObpRemoveObjectRoute+0x598aa0dc90 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 a7d2900 86212d40 nt!ObpCloseHandleTableEntry+0x21d8aa0dd0c 82687616 86212d40 856a2d01 0763f3b4 nt!ObpCloseHandle+0x7f8aa0dd28 8247f8a6 00000c80 0763f3b8 775d7094 nt!NtClose+0x4e8aa0dd28 775d7094 00000c80 0763f3b8 775d7094nt!KiSystemServicePostCall(FPO:[0,3]TrapFrame@8aa0dd34)警告:机架IP不在任何已知模块中。以下框架可能是错误的。0763f3B8000000000000000000000000000000000x775d7094
此堆栈跟踪声明线程正在等待synchronisationEvent
860c6f9c。
命令kd> !object 860c6f9c
返回Not a valid object (ObjectType invalid)
。我不知道这是否意味着synchronisationEvent
在内核中已损坏。当我在进程的其他同步事件上应用该命令时,我会得到一个类似于的输出
0:kd>!对象95369c68对象:95369c68类型:(84aa6378)事件ObjectHeader:95369c50(新版本)HandleCount:1 PointerCount:2
在应用程序级别,在用户模式下,这种情况发生在应该取消并清除任何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(在另一台有相同错误的机器上):
0:kd>dp 86184f9c-@@c++(sizeof(nt!_object_header)-#RTL_FIELD_SIZE(nt!_object_headers,Body))86184f84 0000000 6 00000000 0000000086184f94 000000000000000 100040001 0000000086184fa4 85917420 85917420 000000000000000086184fb4 0000000000000000000000000000000天86184fc4 861848900000000004000000000080086184fd4 0000000085747a68 00000000000000000000000086184fe4 000000000000000086184fec86184fec86184ff4 86184ff6 86184ff8 96d4c000 040d0000
并尝试显示对象标题:
0:kd>dt-nt_object_header 86184f9c-@@c++(sizeof(nt!_object_header)-#RTL_FIELD_SIZE(nt!_object_headers,Body))找不到指定的字段成员
这肯定是应该的。如果内核在用户模式下是可破坏的,那么你就会有一个安全缺陷,可能会影响机器上的所有用户。您可以通过使内核崩溃来拒绝服务。您可以利用内核缓冲区溢出来提升权限。您可以通过使用内核信息泄露漏洞来窃取他人的数据。
仅仅因为你给内核提供了坏数据并不意味着它有这样的漏洞。内核可能足够聪明,可以检测并防止此类问题,也可能执行任何可能因用户输入到用户中而损坏的代码。
如果你真的发现了一个错误,让你崩溃内核,读取别人传递给内核的数据,或者类似的东西,那么你应该向微软报告。如果您怀疑自己发现了什么,请尝试联系MS支持,看看他们是否能提供帮助。他们是操作系统的专家,最有可能确定您的可疑缺陷是否是真正的缺陷。
这只是!object
无法处理给定地址的原因的后续说明。
TL;DR:您的SynchronizationEvent没有损坏。传递给!object
命令的地址不是内核对象,它只是一个内核结构。
kd!object
命令只是查找一个为所有内核对象准备的特殊结构(即nt!_OBJECT_HEADER
)。更确切地说,一个内核结构在其前面有一个nt!_OBJECT_HEADER
时就变成了一个对象。一旦一个结构用这个_OBJECT_HEADER
做了准备,它就变成了内核对象,然后由内核对象管理器处理(特别是所有以Ob
前缀开头的内核函数,但内核中还有其他涉及对象管理的函数)。
如果内核想要创建一个事件,但特别是如果这个对象不必跨越用户陆地/内核陆地边界(或者如果不需要引用计数),那么内核可能只创建一个没有nt!_OBJECT_HEADER
的nt!_KEVENT
结构。
检查地址是否是内核对象
看看你的堆栈轨迹,我们有这两行:
8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject+0x393
8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose+0x332 (FPO: [Non-Fpo])
幸运的是,serial.sys
是一个Microsoft驱动程序,所以我们有符号信息。看看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
:中完成
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
。
关于KEVENTs的更多信息
假设我有一个带有SynchronizationEvent的线程:
kd> !thread ffffe001ef7a5400
THREAD ffffe001ef7a5400 Cid 0538.054c Teb: 00007ff7a9869000 Win32Thread: fffff901406825d0 WAIT: (Executive) KernelMode 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
字段,它指示线程正在等待的对象。
我们知道这是一个事件,但所有可调度的对象都有一个调度头,所以我们可以在nt!_DISPATCHER_HEADER
结构的帮助下dt
Object
指针。
KEVENT
可以作为由KeInitializeEvent()
初始化的独立数据结构存在,也可以作为用NtCreateEvent()
创建的内核(事件)对象存在:如果事件是用KeInitializeEvent()
初始化的,则它不是内核对象,而如果用NtCreateEvent()
初始化,则它是内核对象。
CCD_ 41结构只是围绕CCD_ 42结构的包装。
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
从内核本身访问。当从内核调用时,它不进行检查,因为内核信任在内核模式下运行的任何代码。
- 用户定义的文本运算符(在原始模式下)存在问题
- 内核模式到用户模式通信
- 微筛选器从用户模式应用程序接收常量值
- Visual Studio在发布模式下构建,但用户收到消息说他们需要调试文件
- QtLineEdit 占位符:如何让用户在插入模式下将字符 1 替换为 1
- 使用用户模式和内核之间共享内存的慢速通信
- C++;仅当用户输入与预设模式匹配时才接受用户输入
- 在 windbg 中的用户模式转储中查找 hwnd 信息
- 如何在具有 WppEnabled 标志的用户模式应用程序中使用 WPP 跟踪
- 从用户应用程序访问环0模式(以及Borland允许这样做的原因)
- 服务和用户模式进程之间的共享全局事件不起作用
- 在用户模式下从另一个进程回调,没有额外的线程
- Windows内核是否安全,不会在用户模式下发生损坏
- 用户定义的字符串文字和模式与sscanf匹配
- Windows 用户模式调试器传输 Visual Studio 2012
- 从用户模式发送IRP_MJ_SYSTEM_CONTROL请求
- 从用户模式程序在 Windows 上发送和接收 ARP 数据
- 用户模式计划程序线程的最大数量
- 强制用户模式程序在非分页内存下运行
- 当我们从用户模式转移到内核模式时,哪些寄存器会发生变化?!以及转移到内核模式的原因是什么