看到GPZ很久之前披露的一个在野cng.sys驱动漏洞,本菜鸟尝试对其进行简单分析,希望能从中学习到一些知识
漏洞描述漏洞概述:
内核驱动模块cng.sys
中存在整数溢出漏洞,利用此漏洞进行越界读写,最终可实现本地提取,此漏洞被黑客用于Chrome Sandbox Escape(CVE-2020-15999)
影响的windows版本:
Windows 10 2004以及之前的版本
漏洞分析Windows版本:win10 1903 10.0.18362.836
根据GPZ给出的Poc,可以定位到漏洞函数cng!CfgAdtpFormatPropertyBlock
,size
可以被用户控制,6 * size
的结果强行转换为无符号的16位整数,0x10000 // 0x6
的结果为0x2aa,0x10000 / 6
的结果为10922.666666666666,假设控制size的大小为0x2ab,将其乘上6就溢出为2,BCryptAlloc
函数内部会调用ExAllocatePoolWithTag或者SkAllocatePool在类型为NonPagedPoolNx池动态申请池空间,此时NumberOfBytes
为2,申请的空间就十分小,然而do-while循环的次数为size(即0x2ab),每次向池空间写入6字节,最终导致越界写入,触发BSOD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
__int64 __fastcall CfgAdtpFormatPropertyBlock(char
*
a1, unsigned __int16 size, __int64 a3)
{
unsigned
int
v3;
/
/
ebx
char
*
v6;
/
/
r14
__int16 v7;
/
/
di
_WORD
*
pool_ptr;
/
/
rax
_WORD
*
v9;
/
/
rdx
_WORD
*
pool_ptr_w;
/
/
rcx
__int64 count;
/
/
r8
_WORD
*
v12;
/
/
rcx
char v13;
/
/
al
v3
=
0
;
v6
=
a1;
if
( a1 && size && a3 )
{
v7
=
6
*
size;
pool_ptr
=
BCryptAlloc((unsigned __int16)(
6
*
size));
/
/
6
*
0x2aab
=
2
v9
=
pool_ptr;
if
( pool_ptr )
{
pool_ptr_w
=
pool_ptr;
if
( size )
{
count
=
size;
do
{
/
/
store
6
bytes every time
*
pool_ptr_w
=
(unsigned __int8)a0123456789abcd[(unsigned __int64)(unsigned __int8)
*
v6 >>
4
];
v12
=
pool_ptr_w
+
1
;
v13
=
*
v6
+
+
;
*
v12
+
+
=
(unsigned __int8)a0123456789abcd[v13 &
0xF
];
*
v12
=
0x20
;
pool_ptr_w
=
v12
+
1
;
-
-
count;
}
while
( count );
}
*
(_QWORD
*
)(a3
+
8
)
=
v9;
*
(_WORD
*
)(a3
+
2
)
=
v7;
*
(_WORD
*
)a3
=
v7
-
2
;
}
else
{
return
0xC000009A
;
}
}
else
{
return
0xC000000D
;
}
return
v3;
}
PVOID __fastcall BCryptAlloc(SIZE_T NumberOfBytes)
{
char DeviceContext;
/
/
al
DeviceContext
=
(char)WPP_MAIN_CB.Queue.Wcb.DeviceContext;
if
( !LODWORD(WPP_MAIN_CB.Queue.Wcb.DeviceContext) )
DeviceContext
=
GetTrustedEnvironment();
if
( (DeviceContext &
2
) !
=
0
)
return
(PVOID)SkAllocatePool(
0x200i64
, NumberOfBytes,
'bgnC'
);
/
/
NonPagedPoolNx
else
return
ExAllocatePoolWithTag((POOL_TYPE)
0x200
, NumberOfBytes,
'bgnC'
);
/
/
NonPagedPoolNx
}
漏洞调试 cng!CngDispatch从驱动的cng!DriverEntry入口函数入手,发现好几个MajorFunction都设置为cng!CngDispatch
1
2
3
4
5
6
7
DriverObject
-
>MajorFunction[
0
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
2
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
3
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
4
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
5
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
0xA
]
=
(PDRIVER_DISPATCH)CngDispatch;
DriverObject
-
>MajorFunction[
0xE
]
=
(PDRIVER_DISPATCH)CngDispatch;
跟进cng!CngDispatch函数,可以看到switch中以Major Function Code作为条件,当前案例中关注的是case 0xe,宏IRP_MJ_DEVICE_CONTROL的值为0xe,用户态调用DeviceIoControl这个API最终执行到内核驱动模块中的DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]
IoCode即为IO控制码,从崩溃的栈帧回溯的执行路径获悉IoCode为0x390400,但是很遗憾,我的分析属于一种根据结果来推断过程(根据崩溃的栈帧),这也是四哥痛骂的那波人之一(我太菜了),代码审计的逻辑是从cng!CngDispatch函数开始,进入到cng!CngDeviceControl函数中根据不同IoCode进行递归,然后进行代码审计。本身来说这个漏洞成因相对简单,需要学习的是寻找触发漏洞路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
switch ( CurrentStackLocation
-
>MajorFunction )
{
...
case
0xEu
:
IoCode
=
CurrentStackLocation
-
>Parameters.Read.ByteOffset.LowPart;
if
( (IoCode &
3
)
=
=
2
&& CurrentStackLocation
-
>Parameters.Read.Length )
{
...
}
else
{
OutputBuffer
=
irp
-
>AssociatedIrp.MasterIrp;
InputBuffer
=
OutputBuffer;
OutputLen
=
CurrentStackLocation
-
>Parameters.Read.Length;
}
irp
-
>IoStatus.Status
=
CngDeviceControl(
InputBuffer,
CurrentStackLocation
-
>Parameters.Create.Options,
/
/
InputLen
OutputBuffer,
&OutputLen,
IoCode,
irp
-
>RequestorMode);
irp
-
>IoStatus.Information
=
OutputLen;
break
;
}
cng!CngDeviceControl在cng!CngDispatch函数中下断点,然后查看一下cng!CngDeviceControl函数的6个参数,分别为输入缓冲区、输入缓冲区长度、输出缓冲区、输出缓冲区长度的指针、IO控制码、请求模式
1
1
: kd> ba e1
/
w
"@$curprocess.Name == \"poc.exe\""
cng!CngDispatch
+
85
从上图的第5个参数可以看到传入的IoCode为0x390400,因为抵达漏洞函数需要执行过函数cng!ConfigIoHandler_Safeguarded,cng!CngDeviceControl函数中存在这样的判断
1
2
if
( IoCode
=
=
0x390400
)
return
ConfigIoHandler_Safeguarded(InputBuffer, NumberOfBytes, (IRP
*
)OutputBuffer, OutputLen);
cng!ConfigIoHandler_Safeguardedcng!ConfigIoHandler_Safeguarded使用的参数为cng!CngDeviceControl前四个参数:rcx,rdx,r8,r9
跟进cng!ConfigIoHandler_Safeguarded函数,可以看到函数根据输入缓冲区的长度申请了两个大小相同的池块,姑且称为pool1、pool2,将输入缓冲区的内容拷贝至pool1,pool2初始化为0,在后续的分析过程,将pool1的首地址称为pool_ptr1,pool2的首地址称为pool_ptr2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
__int64 __fastcall ConfigIoHandler_Safeguarded(
PVOID InputBuffer,
SIZE_T InputLen,
PVOID OutputBuffer,
ULONG
*
OutputLen)
{
...
size
=
InputLen;
if
( OutputBuffer )
v7
=
*
OutputLen;
/
/
v7
=
8
else
v7
=
0
;
*
OutputLen
=
0
;
v8
=
8
;
len
=
v7;
/
/
len
=
8
if
( v7 <
8
)
{
if
( v7 )
memset(OutputBuffer,
0
, v7);
return
0xC0000023
;
}
else
{
v9
=
(unsigned
int
)InputLen;
pool_ptr1
=
BCryptAlloc((unsigned
int
)InputLen);
/
/
allocate pool1
v11
=
BCryptAlloc(v9);
/
/
allocate pool2
pool_ptr2
=
v11;
if
( pool_ptr1 && v11 )
{
memmove(pool_ptr1, InputBuffer, v9);
/
/
copy InputBuffer to pool1
memset(pool_ptr2,
0
, v9);
/
/
clear pool2
v13
=
IoUnpack_SG_ParamBlock_Header(pool_ptr1, size, &v19, pool_ptr2);
if
( v13 )
{
v15
=
WinErrorToNtStatus(v13);
}
else
{
v14
=
ConfigFunctionIoHandler(
v19,
(
int
)pool_ptr1,
size,
(struct _CRYPT_CONTEXT_FUNCTION_PROVIDERS
*
)OutputBuffer,
&
len
,
(__int64)pool_ptr2);
...
}
...
}
...
}
在调试器中查看执行memmove前后的变化,验证以上说法
cng!IoUnpack_SG_ParamBlock_Header从伪代码中可以得知调用cng!IoUnpack_SG_ParamBlock_Header的返回值必须为0,否则函数直接返回,于是继续跟进cng!IoUnpack_SG_ParamBlock_Header,函数首先进行pool_ptr1 + 2 < pool_ptr1的判断,然后又进行pool_ptr1 + 2 > (_DWORD )((char )pool_ptr1 + size这样的判断,不太清楚这样做的意义,获取pool1的前4字节,与0x1A2B3C4D
进行比较,因此输入缓冲区的前4字节设置为0x1A2B3C4D,第3个参数所指内存会保存pool1的第二个4字节,数值为0x10400,for循环内会将pool2的前8字节置位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
__int64 __fastcall IoUnpack_SG_ParamBlock_Header(_DWORD
*
pool_ptr1, unsigned
int
size, _DWORD
*
a3, _QWORD
*
pool_ptr2)
{
...
v4
=
0
;
if
( !pool_ptr1 )
return
1i64
;
if
( pool_ptr1
+
2
< pool_ptr1 )
return
1i64
;
v5
=
size;
if
( pool_ptr1
+
2
> (_DWORD
*
)((char
*
)pool_ptr1
+
size) ||
*
pool_ptr1 !
=
0x1A2B3C4D
)
return
1i64
;
if
( a3 )
*
a3
=
pool_ptr1[
1
];
if
( !pool_ptr2 )
return
0i64
;
v6
=
pool_ptr2
+
1
;
if
( pool_ptr2
+
1
>
=
pool_ptr2 )
{
if
( v6 <
=
(_QWORD
*
)((char
*
)pool_ptr2
+
size) )
{
v7
=
0
;
for
( i
=
pool_ptr2; !
*
i;
+
+
i )
{
if
( (unsigned
int
)
+
+
v7 >
=
8
)
{
*
pool_ptr2
=
0xFFFFFFFFFFFFFFFFui64
;
return
0i64
;
}
...
}
...
}
...
}
...
}
查看pool2在执行完循环前后的变化
1
2
3
4
5
6
1
: kd> dq @rax l4
ffffbb0f`
7e160000
00000000
`
00000000
00000000
`
00000000
ffffbb0f`
7e160010
00000000
`
00000000
00000000
`
00000000
1
: kd> dq @r9 l4
ffffbb0f`
7e160000
ffffffff`ffffffff
00000000
`
00000000
ffffbb0f`
7e160010
00000000
`
00000000
00000000
`
00000000
cng!ConfigFunctionIoHandler回到cng!ConfigIoHandler_Safeguarded函数,函数继续调用cng!ConfigFunctionIoHandler函数,第一个参数传入的值为0x10400,HIWORD(a1)返回的结果为1,就会进入到cng!ConfigurationFunctionIoHandler函数,a2、a3、a4、a5、a6对应的是pool_ptr1、InputLen、OutputBuffer、&OutputLen、pool_ptr2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NTSTATUS __fastcall ConfigFunctionIoHandler(
unsigned
int
a1,
int
a2,
unsigned
int
a3,
struct _CRYPT_CONTEXT_FUNCTION_PROVIDERS
*
a4,
_DWORD
*
a5,
__int64 a6)
{
switch ( HIWORD(a1) )
{
case
0u
:
return
RegistrationFunctionIoHandler(a1, a2, a3, (
int
)a4, (ULONG)a5, a6);
case
1u
:
return
ConfigurationFunctionIoHandler(a1, a2, a3, a4, a5, a6);
case
2u
:
return
ResolutionFunctionIoHandler(a1, a2, a3, (
int
)a4, (ULONG)a5, a6);
}
return
0xC00000AF
;
}
在调试器中查看cng!ConfigurationFunctionIoHandler函数的参数
cng!ConfigurationFunctionIoHandlercng!ConfigurationFunctionIoHandler函数开头调用cng!IoUnpack_SG_Configuration_ParamBlock,从函数名跟上文的分析来看,该函数会对池块的某些部分进行一些必要的检查并写入一些值,函数的返回值依然需要为0
cng!IoUnpack_SG_Configuration_ParamBlock跟进cng!IoUnpack_SG_Configuration_ParamBlock函数,先对pool1的大小与8进行对比,由于我们使用的大小为0x3aab,还不能直接返回0,将pool1的首地址加上0x68得到v18,当前实例中保证pool1 < v18 < (pool1 + poolsize),紧接着就是一连串的判断后决定是否调用cng!IoUnpack_SG_SzString、cng!IoUnpack_SG_ContextFunctionConfig、cng!IoUnpack_SG_Buffer函数,由于a4、a6、a7等都为变量的地址(不为0),故都会进入分支执行这些函数
在调试器中查看一下第一次执行完cng!IoUnpack_SG_SzString函数后的变化
在调试器中查看一下最后一次执行完cng!IoUnpack_SG_ContextFunctionConfig函数后的变化
在调试器中查看一下执行完cng!IoUnpack_SG_Buffer函数后的变化
从上面几张图片可以看到,执行完cng!IoUnpack_SG_SzString、cng!IoUnpack_SG_ContextFunctionConfig、cng!IoUnpack_SG_Buffer函数后,缓冲区偏移0x10、0x18、0x20、0x28、0x30、0x40、0x48、0x58的内容在原有的基础上加上了pool1的首地址
cng!IoUnpack_SG_Buffer对cng!IoUnpack_SG_Buffer函数进行简单的分析,可以发现函数内向pool_ptr2偏移0x58字节写入8字节的0xff,在pool_ptr2偏移poi(pool_ptr1 + 0x58)(当前数值为0x1000)处开始写入poi(pool_ptr1 + 0x50)(当前数值为0x2ab)字节的0xff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
...
if
( pool_ptr1_off58 )
{
v9
=
(__int64)pool_ptr1_off58
-
pool_ptr1;
if
( pool_ptr2 )
{
v10
=
(char
*
)(v9
+
pool_ptr2);
/
/
v10
=
pool_ptr2
+
0x58
if
( v9
+
pool_ptr2 >
=
pool_ptr2 )
{
if
( v10
+
8
>
=
v10 && (unsigned __int64)(v10
+
8
) <
=
pool_ptr2
+
size )
{
v11
=
0
;
v12
=
(char
*
)(v9
+
pool_ptr2);
/
/
v12
=
pool_ptr2
+
0x58
while
( !
*
v12 )
{
+
+
v11;
+
+
v12;
if
( v11 >
=
8
)
{
*
(_QWORD
*
)v10
=
0xFFFFFFFFFFFFFFFFui64
;
/
/
*
(pool_ptr2
+
0x58
)
=
0xFFFFFFFFFFFFFFFF
goto LABEL_10;
}
...
}
...
}
...
}
...
LABEL_10:
v13
=
0i64
;
if
(
*
pool_ptr1_off58 !
=
0xFFFFFFFFFFFFFFFFui64
)
v13
=
*
pool_ptr1_off58
+
pool_ptr1;
*
pool_ptr1_off58
=
v13;
/
/
*
(pool_ptr1
+
0x58
)
+
=
pool_ptr1
}
if
( !v13 )
return
0i64
;
if
( v13 >
=
pool_ptr1 && pool_ptr1_off50_val
+
v13 >
=
v13 && pool_ptr1_off50_val
+
v13 <
=
size
+
pool_ptr1 )
{
v17
=
v13
-
pool_ptr1;
/
/
v17
=
*
(pool_ptr1
+
0x58
)
-
pool_ptr1
=
0x1000
if
( pool_ptr2 )
{
v18
=
(void
*
)(v17
+
pool_ptr2);
if
( v17
+
pool_ptr2 >
=
pool_ptr2 )
{
v19
=
pool_ptr2
+
pool_ptr1_off50_val
+
v17;
/
/
v19
=
pool_ptr2
+
0x1000
+
0x2aab
if
( v19 >
=
(unsigned __int64)v18 && v19 <
=
v6
+
pool_ptr2 )
{
v20
=
0
;
if
( !(_DWORD)pool_ptr1_off50_val )
{
LABEL_37:
memset((void
*
)(v17
+
pool_ptr2),
0xFF
, pool_ptr1_off50_val);
return
0i64
;
}
...
}
...
}
...
}
...
}
...
在调试器中查看pool2的内容进行验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
: kd> dq
0xffffbb0f7e160058
l1
ffffbb0f`
7e160058
ffffffff`ffffffff
1
: kd> dq
0xffffbb0f7e161000
ffffbb0f`
7e161000
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161010
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161020
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161030
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161040
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161050
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161060
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e161070
ffffffff`ffffffff ffffffff`ffffffff
1
: kd> dq
0xffffbb0f7e163aab
-
10
ffffbb0f`
7e163a9b
ffffffff`ffffffff ffffffff`ffffffff
ffffbb0f`
7e163aab
dcde6800`
00000000
000000ff
`
8d6f0e3e
ffffbb0f`
7e163abb
00000000
`
00000000
15fab900
`
00000000
ffffbb0f`
7e163acb
000000ff
`ffbb0f7e
00000000
`
00000000
ffffbb0f`
7e163adb
00000000
`
00000000
00000000
`
00000000
ffffbb0f`
7e163aeb
00000000
`
00000000
00000000
`
00000000
ffffbb0f`
7e163afb
00000000
`
00000000
00000000
`
00000000
ffffbb0f`
7e163b0b
00000000
`
00000000
00000000
`
00000000
执行完以上部分,紧接着就是一组赋值操作,然后返回0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if
( a3 )
*
a3
=
*
(_DWORD
*
)(pool_ptr1
+
8
);
if
( a4 )
*
a4
=
*
(_QWORD
*
)(pool_ptr1
+
0x10
);
if
( a5 )
*
a5
=
*
(_DWORD
*
)(pool_ptr1
+
0x18
);
if
( a6 )
*
a6
=
*
(_QWORD
*
)(pool_ptr1
+
0x20
);
if
( a7 )
*
a7
=
*
(_QWORD
*
)(pool_ptr1
+
0x28
);
if
( a8 )
*
a8
=
*
(_QWORD
*
)(pool_ptr1
+
0x30
);
if
( a9 )
*
a9
=
*
(_DWORD
*
)(pool_ptr1
+
0x38
);
if
( a10 )
*
a10
=
*
(_QWORD
*
)(pool_ptr1
+
0x40
);
if
( a11 )
*
a11
=
*
(_QWORD
*
)(pool_ptr1
+
0x48
);
if
( a12 )
*
a12
=
*
(_DWORD
*
)(pool_ptr1
+
0x50
);
if
( a13 )
*
a13
=
*
(_QWORD
*
)(pool_ptr1
+
0x58
);
if
( a14 )
*
a14
=
*
(_QWORD
*
)(pool_ptr1
+
0x60
);
return
0i64
;
返回到cng!ConfigurationFunctionIoHandler函数后,由于输入缓冲区的第二个四字节0x10400被截断为0x400,就会执行到这样的分支
1
2
3
4
5
6
7
8
9
if
( a1
=
=
0x400
)
return
BCryptSetContextFunctionProperty(
dwTable,
/
/
*
a3
=
*
(_DWORD
*
)(pool_ptr1
+
8
)
pszContext,
/
/
*
a4
=
*
(_QWORD
*
)(pool_ptr1
+
0x10
)
dwInterface,
/
/
*
a5
=
*
(_DWORD
*
)(pool_ptr1
+
0x18
)
pszFunction,
/
/
*
a6
=
*
(_QWORD
*
)(pool_ptr1
+
0x20
)
pszProperty,
/
/
*
a8
=
*
(_QWORD
*
)(pool_ptr1
+
0x30
)
cbValue,
/
/
*
a12
=
*
(_DWORD
*
)(pool_ptr1
+
0x50
)
pbValue);
/
/
*
a13
=
*
(_QWORD
*
)(pool_ptr1
+
0x58
)
cng!BCryptSetContextFunctionProperty跟进cng!BCryptSetContextFunctionProperty函数,继续分析此函数,cng!ValidateTableId所在的分支无法满足条件,利用cbValue和pbValue的值初始化DestinationString,利用此DestinationString调用cng!CfgReg_Acquire
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
NTSTATUS __stdcall BCryptSetContextFunctionProperty(
ULONG dwTable,
/
/
pool_ptr1_off_8
LPCWSTR pszContext,
/
/
pool_ptr1_off_10
ULONG dwInterface,
/
/
pool_ptr1_off_18
LPCWSTR pszFunction,
/
/
pool_ptr1_off_20
LPCWSTR pszProperty,
/
/
pool_ptr1_off_30
ULONG cbValue,
/
/
pool_ptr1_off_50
PUCHAR pbValue)
/
/
pool_ptr1_off_58
{
...
*
(_QWORD
*
)&DestinationString.Length
=
0i64
;
v36
=
0
;
v34
=
0
;
v35
=
0
;
v37
=
0i64
;
v38
=
0i64
;
v39
=
0i64
;
DestinationString.
Buffer
=
0i64
;
v42
=
0i64
;
if
( !ValidateTableId(dwTable)
/
/
dwTable
=
1
, false
|| !(unsigned
int
)ValidateInterfaceId(dwInterface,
0
)
/
/
dwInterface
=
3
, false
|| !v13
/
/
pszFunction, false
||
*
v13
=
=
(_WORD)v12
/
/
*
pszFunction !
=
0
, false
|| !pszProperty
/
/
pszProperty, false
||
*
pszProperty
=
=
(_WORD)v12 )
/
/
*
pszProperty !
=
0
, false
{
v16
=
0x57
;
LABEL_36:
if
( !v10 )
goto LABEL_38;
goto LABEL_37;
}
if
( cbValue && pbValue )
/
/
true
{
*
(_QWORD
*
)&DestinationString.Length
=
pbValue;
LODWORD(DestinationString.
Buffer
)
=
cbValue;
HIDWORD(DestinationString.
Buffer
)
=
cbValue;
LODWORD(v42)
=
v12;
}
v14
=
CfgReg_Acquire(v11, dwTable,
0x3001Fi64
);
...
}
cng!CfgReg_Acquire进一步跟进cng!CfgReg_Acquire,发现cng!VerifyRegistryAccess函数尝试对System\CurrentControlSet\Control\Cryptography\Configuration\Local注册表项执行操作,cng!VerifyRegistryAccess内部调用cng!KeRegOpenKey尝试打开注册表表项
根据cng!VerifyRegistryAccess的返回值为5得知打开失败,函数将5返回给cng!BCryptSetContextFunctionProperty
紧接着在cng!BCryptSetContextFunctionProperty的执行流程大体如下,分别使用pool1_ptr+0x100、pool1_ptr+0x200、pool1_ptr+0x400处的字符初始化三个新的UnicodeString
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
v14
=
CfgReg_Acquire(v11, dwTable,
0x3001Fi64
);
/
/
v14
=
5
v16
=
v14;
if
( v14 )
{
v17
=
v14
=
=
5
;
/
/
v17
=
true
goto LABEL_43;
}
...
LABEL_43:
if
( !v17 )
goto LABEL_49;
}
*
(_QWORD
*
)&DestinationString.Length
=
0i64
;
DestinationString.
Buffer
=
0i64
;
*
(_QWORD
*
)&v40.Length
=
0i64
;
v40.
Buffer
=
0i64
;
*
(_QWORD
*
)&v43.Length
=
0i64
;
v43.
Buffer
=
0i64
;
RtlInitUnicodeString(&DestinationString, pszContext);
/
/
pool_ptr1_off_10
RtlInitUnicodeString(&v40, pszFunction);
/
/
pool_ptr1_off_20
RtlInitUnicodeString(&v43, pszProperty);
/
/
pool_ptr1_off_30
cng!CfgAdtReportFunctionPropertyModification在初始化三个UnicodeString后,进入下面的分支调用cng!CfgAdtReportFunctionPropertyModification函数
在调试器中查看一下函数的参数,最少有10个,IDA反编译出来的结果中有11个
从上图中可以看到,在rsp+40的位置存储了0x2aab,这个数值来自于pool_ptr1 + 0x50的位置,查看一下寄存器r14w的数据来源,可以看到函数序言部分将pool_ptr1 + 0x50处的值赋给r14d
cng!CfgAdtReportFunctionPropertyModification函数内部调用最终的漏洞函数cng!CfgAdtpFormatPropertyBlock,调试器中查看一下cng!CfgAdtpFormatPropertyBlock的参数,可以看到第二个参数来自于cng!CfgAdtReportFunctionPropertyModification的第九个参数,数值为0x2aab
cng!CfgAdtpFormatPropertyBlock由于漏洞原理在开篇提及,此处就对一些关键位置查看下。查看一下cng!BCryptAlloc的参数,果然溢出为2
调用完该函数申请到的池块大小为0x20字节
然而循环的次数为0x2aab次,每次向池块中写入6字节,最终就会覆写到相邻的内存,最终导致BSOD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
NOTE: The trap frame does
not
contain
all
registers.
Some register values may be zeroed
or
incorrect.
rax
=
0000000000000030
rbx
=
0000000000000000
rcx
=
ffffda02a29ff000
rdx
=
ffffda02a29fe610 rsi
=
0000000000000000
rdi
=
0000000000000000
rip
=
fffff80063ae2904 rsp
=
ffffd7804ace5f20 rbp
=
0000000000002aab
r8
=
0000000000002903
r9
=
0000000000000002
r10
=
fffff80063b1ce70
r11
=
0000000000000002
r12
=
0000000000000000
r13
=
0000000000000000
r14
=
0000000000000000
r15
=
0000000000000000
iopl
=
0
nv up ei pl zr ac po nc
cng!CfgAdtpFormatPropertyBlock
+
0x88
:
fffff800`
63ae2904
668901
mov word ptr [rcx],ax ds:ffffda02`a29ff000
=
????
Resetting default scope
STACK_TEXT:
ffffd780`
4ace5348
fffff800`
62cadef2
: ffffda02`a29ff000
00000000
`
00000003
ffffd780`
4ace54b0
fffff800`
62b292f0
: nt!DbgBreakPointWithStatus
ffffd780`
4ace5350
fffff800`
62cad5e7
: fffff800`
00000003
ffffd780`
4ace54b0
fffff800`
62bdc3f0
ffffd780`
4ace59f0
: nt!KiBugCheckDebugBreak
+
0x12
ffffd780`
4ace53b0
fffff800`
62bc7de7
: fffff800`
62e694f8
fffff800`
62cd7a45
ffffda02`a29ff000 ffffda02`a29ff000 : nt!KeBugCheck2
+
0x947
ffffd780`
4ace5ab0
fffff800`
62c0d19e
:
00000000
`
00000050
ffffda02`a29ff000
00000000
`
00000002
ffffd780`
4ace5d90
: nt!KeBugCheckEx
+
0x107
ffffd780`
4ace5af0
fffff800`
62a9a59f
: fffff800`
62a05000
00000000
`
00000002
00000000
`
00000000
ffffda02`a29ff000 : nt!MiSystemFault
+
0x19dcee
ffffd780`
4ace5bf0
fffff800`
62bd5d5e
:
00000000
`
00000000
00000000
`
00000000
ffffda02`a2f84000
00000000
`
00000fff
: nt!MmAccessFault
+
0x34f
ffffd780`
4ace5d90
fffff800`
63ae2904
: ffffd780`
4ace5fe0
ffffd780`
4ace6508
ffffda02`a2f84000 ffff062f`
1ac23f52
: nt!KiPageFault
+
0x35e
ffffd780`
4ace5f20
fffff800`
63ae224e
:
00000000
`
00000000
ffffd780`
4ace6050
ffffda02`a2f84000
00000000
`
00000001
: cng!CfgAdtpFormatPropertyBlock
+
0x88
ffffd780`
4ace5f50
fffff800`
63ae0282
:
00000000
`
00000005
ffffd780`
4ace6720
ffffda02`a2f84000 ffffda02`a2f83200 : cng!CfgAdtReportFunctionPropertyOperation
+
0x23e
ffffd780`
4ace6470
fffff800`
63ac9580
: ffffd780`
4ace6720
ffffda02`a2f83100 ffffd780`
4ace65f0
ffffda02`a2f83200 : cng!BCryptSetContextFunctionProperty
+
0x3a2
ffffd780`
4ace6570
fffff800`
63a92e86
:
00000000
`
00003aab
00000000
`
00000008
00000000
`
00003aab
ffffda02`a2f7f000 : cng!_ConfigurationFunctionIoHandler
+
0x3bd5c
ffffd780`
4ace6660
fffff800`
63a92d22
:
00000000
`
00003aab
fffff800`
62a5c339
ffffda02`a2f82fe0
00000000
`
00000004
: cng!ConfigFunctionIoHandler
+
0x4e
ffffd780`
4ace66a0
fffff800`
63a91567
:
00000000
`
00000000
fffff800`
00003aab
00000000
`
00000000
00000000
`
00010400
: cng!ConfigIoHandler_Safeguarded
+
0xd2
ffffd780`
4ace6710
fffff800`
63a8e0ea
:
00000000
`
00000000
ffffda02`a2295ed0 ffffda02`a2295e00
00000000
`
00000000
: cng!CngDeviceControl
+
0x97
ffffd780`
4ace67e0
fffff800`
62a314e9
: ffffda02`a2295e00
00000000
`
00000000
00000000
`
00000002
00000000
`
00000001
: cng!CngDispatch
+
0x8a
ffffd780`
4ace6820
fffff800`
62fd6a55
: ffffd780`
4ace6b80
ffffda02`a2295e00
00000000
`
00000001
ffffda02`a2a34820 : nt!IofCallDriver
+
0x59
ffffd780`
4ace6860
fffff800`
62fd6860
:
00000000
`
00000000
ffffd780`
4ace6b80
ffffda02`a2295e00 ffffd780`
4ace6b80
: nt!IopSynchronousServiceTail
+
0x1a5
ffffd780`
4ace6900
fffff800`
62fd5c36
:
000002e2
`
391a3000
00000000
`
00000000
00000000
`
00000000
00000000
`
00000000
: nt!IopXxxControlFile
+
0xc10
ffffd780`
4ace6a20
fffff800`
62bd9558
:
00000000
`
00000000
00000000
`
00000000
00000000
`
00000000
000000fa
`
99aff5e8
: nt!NtDeviceIoControlFile
+
0x56
ffffd780`
4ace6a90
00007ffc
`
3233c1a4
:
00007ffc
`
2fe7eaa7
00000000
`
00000000
0000ed39
`
9d02f136
00000000
`
00000000
: nt!KiSystemServiceCopyEnd
+
0x28
000000fa
`
99aff918
00007ffc
`
2fe7eaa7
:
00000000
`
00000000
0000ed39
`
9d02f136
00000000
`
00000000
000000fa
`
99aff940
: ntdll!NtDeviceIoControlFile
+
0x14
000000fa
`
99aff920
00007ffc
`
32016430
:
00000000
`
00390400
00000000
`
00000024
00007ff7
`
98f322b8
000000fa
`
99aff9e8
: KERNELBASE!DeviceIoControl
+
0x67
000000fa
`
99aff990
00007ff7
`
98f311fb
:
000002e2
`
391a13c0
000002e2
`
391970a0
00000000
`
00000000
00000000
`
00000000
: KERNEL32!DeviceIoControlImplementation
+
0x80
000000fa
`
99aff9e0
000002e2
`
391a13c0
:
000002e2
`
391970a0
00000000
`
00000000
00000000
`
00000000
000000fa
`
99affa20
: poc
+
0x11fb
000000fa
`
99aff9e8
000002e2
`
391970a0
:
00000000
`
00000000
00000000
`
00000000
000000fa
`
99affa20
00000010
`
00000008
:
0x000002e2
`
391a13c0
000000fa
`
99aff9f0
00000000
`
00000000
:
00000000
`
00000000
000000fa
`
99affa20
00000010
`
00000008
000000fa
`
99affa28
:
0x000002e2
`
391970a0
补丁分析对比一下补丁前后的文件变化,可以看到只有cng!CfgAdtpFormatPropertyBlock函数发生了变化
查看一下函数的前后变化,发现补丁后函数调用cng!RtlUShortMult函数对传进来的size进行了处理,然后再传给cng!BCryptAlloc函数申请池空间
cng!RtlUShortMult函数十分简短,就是对size * 6的结果进行溢出判断(与0xffff比较)
1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall RtlUShortMult(unsigned __int16 a1, __int64 a2, unsigned __int16
*
a3)
{
unsigned
int
v3;
/
/
ecx
unsigned __int16 v4;
/
/
dx
v3
=
6
*
a1;
if
( v3 >
0xFFFF
)
v4
=
0xFFFF
;
else
v4
=
v3;
*
a3
=
v4;
return
v3 >
0xFFFF
?
0xC0000095
:
0
;
}
参考链接
https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2020/CVE-2020-17087.html
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17087
https://www.anquanke.com/post/id/221964
IDA插件开发入门
最后于 2022-3-16 07:53
被Jimpp编辑
,原因:
上传的附件: