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😷