CVE-2021-1648漏洞分析与绕过技术解析

本文详细分析了CVE-2021-1648漏洞的技术细节,包括如何绕过CVE-2020-0986补丁的防护机制,涉及任意地址读取和信息泄露漏洞,深入探讨了splwow64服务中的安全缺陷。

CVE-2021-1648的故事

作者: 360 Vulcan Team的k0shl

摘要

该漏洞原计划在2020年10月修复,但MSRC似乎发现了服务中其他严重的安全问题,因此他们将补丁推迟了四个月。

背景

在这篇博客中,我不想过多讨论splwow64的机制,之前已经有很多关于CVE-2020-0986的分析,让我们专注于漏洞本身。

在CVE-2020-0986被修补后,我快速对splwow64和gdi32full进行了bindiff,发现补丁后添加了两个检查。

一个是微软添加了两个打印机句柄(或称cookie?)检查函数"FindDriverForCookie"和"FindPrinterHandle",它们将检查存储在全全局变量中的打印机驱动句柄。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall FindDriverForCookie(__int64 a1)
{
  v3 = qword_1800EABA0;
  if ( qword_1800EABA0 )
  {
    do
    {
      if ( a1 == *(_QWORD *)(v3 + 56) ) //检查驱动索引
        break;
      v3 = *(_QWORD *)(v3 + 8);
    }
    while ( v3 );
    if ( v3 )
      ++*(_DWORD *)(v3 + 44);
  }
  RtlLeaveCriticalSection(&semUMPD);
  return v3;// 返回驱动堆
}

__int64 *__fastcall FindPrinterHandle(__int64 a1, int a2, int a3)
{
  for ( i = *(__int64 **)(v3 + 64); i && (*((_DWORD *)i + 2) != v5 || *((_DWORD *)i + 3) != v4); i = (__int64 *)*i ) //检查打印机句柄
    ;
}

另一个是MSRC添加了两个指针检查函数"UMPDStringPointerFromOffset"和"UMPDPointerFromOffset"来检查指针是否有效。

FindDriverForCookie和FindPrinterHandle绕过

首先,我不知道微软添加FindDriverForCookie和FindPrinterHandle的目的,也许不是为了缓解?快速审查后,我发现有一个名为0x6A的命令可以设置打印机句柄,我们可以在服务的全局变量中控制该值,从而绕过这两个检查函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
__int64 __fastcall bAddPrinterHandle(__int64 a1, int a2, int a3, __int64 a4)
{
  v9 = RtlAllocateHeap(*(_QWORD *)(__readgsqword(0x60u) + 48), 0i64, 24i64);
  v10 = (_QWORD *)v9;
  if ( v9 )
  {
    *(_DWORD *)(v9 + 8) = v6;
    *(_DWORD *)(v9 + 12) = v5;
    *(_QWORD *)(v9 + 16) = v8;
    RtlEnterCriticalSection(&semUMPD);
    *v10 = *(_QWORD *)(v4 + 0x40);
    v7 = 1;
    *(_QWORD *)(v4 + 0x40) = v10; //添加可由用户控制的打印句柄
    RtlLeaveCriticalSection(&semUMPD);
  }
  return v7;
}

通过调用命令0x6A,函数bAddPrinterHandle将把打印句柄添加到存储在全局变量|qword_1800EABA0|中的驱动堆。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//设置打印句柄为0xdeadbeef00006666
0:007> p
gdi32full!bAddPrinterHandle+0x54:
00007ff8`380fc3bc 44897808        mov     dword ptr [rax+8],r15d ds:00000000`0108a428=00000000
0:007> p
gdi32full!bAddPrinterHandle+0x58:
00007ff8`380fc3c0 4489700c        mov     dword ptr [rax+0Ch],r14d ds:00000000`0108a42c=00000000
0:007> r r14d
r14d=deadbeef
0:007> r r15d
r15d=6666

//驱动堆存储在全局变量中
0:007> dq gdi32full+0xEABA0 l1
00007ff8`381baba0  00000000`0108d000
0:007> dq 108d000+0x40 l1
00000000`0108d040  00000000`0108a420
0:007> dq 108a420+0x8 l1
00000000`0108a428  deadbeef`00006666

因此我们可以在调用命令0x6D时轻松绕过打印机句柄检查,并触发漏洞代码。

1
2
3
4
5
6
7
8
case 0x6Du:
    v31 = FindDriverForCookie(*(_QWORD *)(v6 + 24));
    v32 = v31;
    if ( !v31 )
      goto LABEL_137;
    v33 = FindPrinterHandle(v31, *(_DWORD *)(v6 + 32), *(_DWORD *)(v6 + 36));
    ...
    [漏洞代码]

CVE-2021-1648:任意地址读取

让我们谈谈信息泄露,CVE-2020-1648包含一个任意地址读取信息泄露。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if ( v51 != -1 )
{
   v57 = **(unsigned __int16 ***)(v6 + 0x50); //未检查v57
   if ( v57 )
   {
      v58 = v57[34];
      v59 = v58 + v57[35];
      if ( (unsigned int)v59 >= v58 && (unsigned int)v59 <= 0x1FFFE )
        memcpy_0(*(void **)(v6 + 88), v57, v59); //任意地址读取
   }
}

命令0x6D的代码太长,所以我不会在博客中全部发布。简而言之,它会检查memcpy的目标地址是否在"有效"范围内,即|v6+0x58|的范围,但源地址|v57|未被检查,因此我们可以读取任意地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0:007> r
rax=0000000000868a00 rbx=000000000001fffe rcx=0000000000000000
rdx=4141414141414141 rsi=0000000000150200 rdi=00000000008688d0
rip=00007ff9fc008403 rsp=000000000210f480 rbp=000000000210f4f9
r8=100297f000000002  r9=000000000022f000 r10=00000fff3c9c801d
r11=000000000210f350 r12=0000000000868920 r13=0000000000868910
r14=0000000000000001 r15=0000000000461c50
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
gdi32full!GdiPrinterThunk+0x1a73:
00007ff9fc008403 0fb74a44  movzx ecx,word ptr [rdx+44h] ds:4141414141414185=????

堆栈跟踪:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
0:007> k
Child-SP          RetAddr           Call Site
000000000210f480 00007ff7558e78ab gdi32full!GdiPrinterThunk+0x1a73
000000000210f560 00007ff7558e84de splwow64+0x78ab
000000000210f650 00007ff7558e9f28 splwow64+0x84de
000000000210f6b0 00007ff9fe3f2e93 splwow64+0x9f28
000000000210f6e0 00007ff9fe3f45b4 ntdll!RtlDeleteCriticalSection+0x363
000000000210f730 00007ff9fc487bd4 ntdll!RtlInitializeResource+0xce4
000000000210faf0 00007ff9fe42ce51 KERNEL32!BaseThreadInitThunk+0x14
000000000210fb20 0000000000000000 ntdll!RtlUserThreadStart+0x21

CVE-2021-1648的另外两个案例

我向MSRC报告的另外两个案例是关于绕过偏移检查函数"UMPDStringPointerFromOffset"和"UMPDPointerFromOffset",我认为MSRC在这两个函数的范围检查中犯了一个错误。

Splwow64是一个特殊的服务,它在x86-64 Windows操作系统中与x86兼容,因此它总是分配32位的堆,但在CVE-2020-0986补丁中,“UMPDStringPointerFromOffset"和"UMPDPointerFromOffset"只检查偏移量和|portview+offset|是否小于0x7fffffff。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
signed __int64 __fastcall UMPDPointerFromOffset(unsigned __int64 *a1, __int64 a2, unsigned int a3)
{
   [...]
    if ( v3 <= 0x7FFFFFFF && v3 + a3 <= 0x7FFFFFFF )
    {
      *a1 = v3 + a2;
      return 1i64;
    }
  [...]
}

signed __int64 __fastcall UMPDStringPointerFromOffset(unsigned __int64 *a1, __int64 a2)
{
  [...]
  if ( v3 > 0x7FFFFFFF )
    goto LABEL_12;
  v4 = (0x7FFFFFFF - v3) >> 1;
  *a1 = v3 + a2;
  v5 = (unsigned int)v4;
  if ( v3 + a2 )
    v2 = wcsnlen((const wchar_t *)(v3 + a2), (unsigned int)v4);
  [...]
  return result;
}

但是在splwow64服务中,很多堆甚至栈都在低地址分配,像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
0:004> pc
splwow64!TLPCMgr::ProcessRequest+0x99:
00007ff6`846d7c71 e826490000      call    splwow64!operator new[] (00007ff6`846dc59c)
0:004> p
splwow64!TLPCMgr::ProcessRequest+0x9e:
00007ff6`846d7c76 488bf0          mov     rsi,rax
0:004> r rax
rax=00000000007d7c70
0:004> r rsp
rsp=000000000217f400

因此有可能通过占用splwow64服务中的一些重要堆或栈来进行利用,我在报告中建议MSRC检查指针范围是否在portview部分内,而不是0x7fffffff。

两个案例崩溃转储:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0:006> r
rax=0000000000000000 rbx=00000000012f8360 rcx=000000001363d9e0
rdx=00000000012f8360 rsi=0000000002d60200 rdi=000000001363d9d8
rip=00007fff728956d2 rsp=0000000002cdf230 rbp=0000000000000001
r8=0000000000000028  r9=0000000012345678 r10=000000007fffffff
r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001208210
r14=000000000120aa50 r15=00007fff72860000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
gdi32full!UMPDStringPointerFromOffset+0x12:
00007fff728956d2 4c8b09          mov     r9,qword ptr [rcx] ds:000000001363d9e0=????????????????
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0:006> r
rax=0000000000000001 rbx=0000000001628360 rcx=0000000042a3c4a1
rdx=0000000001628360 rsi=0000000000ff0200 rdi=0000000000000000
rip=00007fff7289568a rsp=0000000002ecf3d8 rbp=0000000000000001
r8=0000000000000028  r9=0000000041414141 r10=000000007fffffff
r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001407160
r14=000000000140a000 r15=00007fff72860000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
gdi32full!UMPDPointerFromOffset+0xa:
00007fff7289568a 4c8b09          mov     r9,qword ptr [rcx] ds:0000000042a3c4a1=????????????????

故事结束

似乎微软重新设计了splwow64打印机服务,因此他们将补丁推迟了四个月,自从我开始研究Windows以来,等待补丁的时间真的很长。希望新的打印机服务能更加安全:P。

时间线

  • 2020-07-27 向MSRC报告
  • 2020-08-19 MSRC决定推迟补丁
  • 2020-08-22 奖励颁发
  • 2021-01-13 补丁发布

2021-01-13

阅读次数:35207

评论

Facebooksequre7

回复

2021-02-28 18:11:59

hi hlw wow😷

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计