condrv.sys 拒绝服务漏洞分析

Table of Contents

1. 概述

最近比较火的一个漏洞,Windows 10 中通过浏览器等途径直接访问路径 \\.\globalroot\device\condrv\kernelconnect 时,会导致系统蓝屏。

2. 漏洞分析

在「此电脑」 > 「属性」 > 「高级系统设置],选择「启动和故障恢复」的「设置」,设置「写入调试信息」为「小内存转储(256KB)」,然后访问路径:\\.\globalroot\device\condrv\kernelconnect。蓝屏后重启系统,用 WinDbg 载入 C:\Windows\Minidump 下的刚转储的 .dmp 文件,并执行:

!analyze -v

从输出结果中找到调用栈,如下:

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;
}

3. 参考