作者:lu4nx@知道创宇404积极防御实验室
时间:2021年1月25日
最近比较火的一个漏洞,Windows 10 中通过浏览器等途径直接访问路径 \\.\globalroot\device\condrv\kernelconnect 时,会导致系统蓝屏。
在「此电脑」 > 「属性」 > 「高级系统设置],选择「启动和故障恢复」的「设置」,设置「写入调试信息」为「小内存转储(256KB)」,然后访问路径:\\.\globalroot\device\condrv\kernelconnect。蓝屏后重启系统,用 WinDbg 载入 C:\Windows\Minidump 下的刚转储的 .dmp 文件,并执行:
从输出结果中找到调用栈,如下:
PROCESS_NAME: explorer.exe STACK_TEXT: ffffd28c`2d35d2d0 fffff805`47037159 : 00000000`00000000 fffff805`4703704d 00000000`00000000 00000000`00000000 : condrv!CdpDispatchCleanup+0x1f ffffd28c`2d35d300 fffff805`475d87b8 : 00000000`00000000 ffff8909`f300e080 00000000`00000000 ffff8909`f04cf9a0 : nt!IofCallDriver+0x59 ffffd28c`2d35d340 fffff805`47601ddc : 00000000`00000000 ffffd28c`2d35d890 00000000`00000000 00000000`00000000 : nt!IopCloseFile+0x188 ffffd28c`2d35d3d0 fffff805`475f666f : ffff8909`eff699f0 00000000`00000000 ffff8909`eb813490 00000000`00000001 : nt!IopParseDevice+0x1f3c ffffd28c`2d35d540 fffff805`475f4ad1 : ffff8909`eb813400 ffffd28c`2d35d788 00000000`00000040 ffff8909`e9cbd140 : nt!ObpLookupObjectName+0x78f ffffd28c`2d35d700 fffff805`476b1afb : ffff8909`00000001 00000000`1034d0a0 00000000`00000001 00000000`1034e25c : nt!ObOpenObjectByNameEx+0x201 ffffd28c`2d35d840 fffff805`471d5355 : ffff8909`f0ee5080 00000000`00000000 ffff8909`f0ee5080 00000000`1034e25c : nt!NtQueryAttributesFile+0x1eb ffffd28c`2d35db00 00007ffa`a4ddce94 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25 00000000`1034d038 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007ffa`a4ddce94
最终执行到以下指令时引发蓝屏:
condrv!CdpDispatchCleanup+0x1f
查看 condrv!CdpDispatchCleanup+0x1f 的汇编代码:
1: kd> u condrv!CdpDispatchCleanup+0x1f condrv!CdpDispatchCleanup+0x1f: fffff805`8e31b04f 488b01 mov rax,qword ptr [rcx] fffff805`8e31b052 4c8b4820 mov r9,qword ptr [rax+20h] fffff805`8e31b056 4d85c9 test r9,r9 fffff805`8e31b059 7521 jne condrv!CdpDispatchCleanup+0x4c (fffff805`8e31b07c) fffff805`8e31b05b 33c9 xor ecx,ecx fffff805`8e31b05d 894a30 mov dword ptr [rdx+30h],ecx fffff805`8e31b060 48894a38 mov qword ptr [rdx+38h],rcx fffff805`8e31b064 33d2 xor edx,edx
注意第一句 mov 的寻址地址取的是 RCX 寄存器中的值,从上面的分析结果就翻到发生事故时各个寄存器的值:
CONTEXT: ffffd28c2d35c8e0 -- (.cxr 0xffffd28c2d35c8e0) rax=ffff8909f04cfab8 rbx=ffff8909f300e080 rcx=0000000000000000 rdx=ffff8909f04cf9a0 rsi=0000000000000000 rdi=ffff8909f04cf9a0 rip=fffff8058e31b04f rsp=ffffd28c2d35d2d0 rbp=0000000000000000 r8=ffff8909f04cf9a0 r9=ffff8909eff699f0 r10=fffff8058e31b030 r11=0000000000000000 r12=0000000000000000 r13=0000000000000000 r14=0000000000000000 r15=ffff8909eff699f0 iopl=0 nv up ei ng nz na pe nc cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010282 condrv!CdpDispatchCleanup+0x1f: fffff805`8e31b04f 488b01 mov rax,qword ptr [rcx] ds:002b:00000000`00000000=????????????????
可以看到 RCX 指向的空地址,当 mov 指令寻址时,由于 RCX 为空地址,因此引发了空指针异常导致蓝屏。
查看 CdpDispatchCleanup 函数完整的汇编代码:
1: kd> uf condrv!CdpDispatchCleanup condrv!CdpDispatchCleanup: fffff805`8e31b030 4883ec28 sub rsp,28h fffff805`8e31b034 488b82b8000000 mov rax,qword ptr [rdx+0B8h] fffff805`8e31b03b 4c8bc2 mov r8,rdx fffff805`8e31b03e 488b4830 mov rcx,qword ptr [rax+30h] fffff805`8e31b042 4885c9 test rcx,rcx fffff805`8e31b045 0f84250e0000 je condrv!CdpDispatchCleanup+0xe40 (fffff805`8e31be70) Branch condrv!CdpDispatchCleanup+0x1b: fffff805`8e31b04b 488b4918 mov rcx,qword ptr [rcx+18h] fffff805`8e31b04f 488b01 mov rax,qword ptr [rcx] fffff805`8e31b052 4c8b4820 mov r9,qword ptr [rax+20h] fffff805`8e31b056 4d85c9 test r9,r9 fffff805`8e31b059 7521 jne condrv!CdpDispatchCleanup+0x4c (fffff805`8e31b07c) Branch condrv!CdpDispatchCleanup+0x2b: fffff805`8e31b05b 33c9 xor ecx,ecx fffff805`8e31b05d 894a30 mov dword ptr [rdx+30h],ecx fffff805`8e31b060 48894a38 mov qword ptr [rdx+38h],rcx fffff805`8e31b064 33d2 xor edx,edx fffff805`8e31b066 498bc8 mov rcx,r8 fffff805`8e31b069 48ff15a8b0ffff call qword ptr [condrv!_imp_IofCompleteRequest (fffff805`8e316118)] fffff805`8e31b070 0f1f440000 nop dword ptr [rax+rax] fffff805`8e31b075 33c0 xor eax,eax condrv!CdpDispatchCleanup+0x47: fffff805`8e31b077 4883c428 add rsp,28h fffff805`8e31b07b c3 ret condrv!CdpDispatchCleanup+0x4c: fffff805`8e31b07c 8b10 mov edx,dword ptr [rax] fffff805`8e31b07e 498bc1 mov rax,r9 fffff805`8e31b081 482bca sub rcx,rdx fffff805`8e31b084 498bd0 mov rdx,r8 fffff805`8e31b087 ff152bb2ffff call qword ptr [condrv!_guard_dispatch_icall_fptr (fffff805`8e3162b8)] fffff805`8e31b08d ebe8 jmp condrv!CdpDispatchCleanup+0x47 (fffff805`8e31b077) Branch condrv!CdpDispatchCleanup+0xe40: fffff805`8e31be70 33c9 xor ecx,ecx fffff805`8e31be72 c742300d0000c0 mov dword ptr [rdx+30h],0C000000Dh fffff805`8e31be79 48894a38 mov qword ptr [rdx+38h],rcx fffff805`8e31be7d 33d2 xor edx,edx fffff805`8e31be7f 498bc8 mov rcx,r8 fffff805`8e31be82 48ff158fa2ffff call qword ptr [condrv!_imp_IofCompleteRequest (fffff805`8e316118)] fffff805`8e31be89 0f1f440000 nop dword ptr [rax+rax] fffff805`8e31be8e b80d0000c0 mov eax,0C000000Dh fffff805`8e31be93 e9dff1ffff jmp condrv!CdpDispatchCleanup+0x47 (fffff805`8e31b077) Branch
主要关注触发空指针异常的那段逻辑代码:
fffff805`8e31b034 488b82b8000000 mov rax,qword ptr [rdx+0B8h] fffff805`8e31b03b 4c8bc2 mov r8,rdx fffff805`8e31b03e 488b4830 mov rcx,qword ptr [rax+30h] // RCX 是 RAX 指向的结构体成员 fffff805`8e31b042 4885c9 test rcx,rcx fffff805`8e31b045 0f84250e0000 je condrv!CdpDispatchCleanup+0xe40 (fffff805`8e31be70) fffff805`8e31b04b 488b4918 mov rcx,qword ptr [rcx+18h] // RCX+18h 是 RCX 的成员变量 fffff805`8e31b04f 488b01 mov rax,qword ptr [rcx] // 由于这里没判断 RCX+18h 是否为空,导致 bug 发生
从对 RDX、RAX、RCX 指针的偏移引用来看,这 3 个寄存器应该是都是指向的结构体,而 RCX+18h 这个成员变量指向的空地址。
CdpDispatchCleanup 是派遣函数,只有需要处理 IRP 时才会调用;而 RCX+18h 这个成员函数为 0 应该赋值失败导致的,所以觉得问题应该出现在创建分发函数时出的问题。
把 C:\Windows\System32\drivers\condrv.sys 拖到 Ghidra 里去分析,根据"\KernelConnect"路径找到对应的创建函数 CdCreateKernelConnection:
longlong * CdCreateKernelConnection(PIRP irp) { ...省略.. if (irp->RequestorMode != '\0') { return (longlong *)0xc0000022; } lVar4 = *(longlong *)&(irp->Tail).field_0x40; puVar9 = CdpFindEaBufferItem(*(uint **)((longlong)&irp->AssociatedIrp + 4),"attach"); if ((puVar9 == (uint *)0x0) || (*(short *)((longlong)puVar9 + 6) != 8)) { return (longlong *)0xc000000d; } ...省略... }
根据"参考文章"中的提点,找到了漏洞触发的原因是这段:
if (irp->RequestorMode != '\0') { return (longlong *)0xc0000022; }
I/O 控制函数都会返回一个状态码,参考 https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55 十六进制对应的状态码,0xc0000022 对应为 STATUS_ACCESS_DENIED,这段逻辑代码用来判断 irp->RequestorMode 是否来自应用层,对于应用层的访问则返回 STATUS_ACCESS_DENIED 来拒绝,但是这里没有调用 IoCompleteRequest 来结束请求,因此才导致来后面会调用到派遣函数 CdpDispatchCleanup。正确的写法应该类似为:
if (irp->RequestorMode != '\0') { irp->IoStatus.Status = STATUS_ACCESS_DENIED; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_ACCESS_DENIED; }
《关于此次condrv.sys拒绝服务漏洞分析》:https://www.secpulse.com/archives/151714.html
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1469/