CVE-2020-17087整数溢出漏洞分析
字数 1773 2025-08-29 08:31:53
CVE-2020-17087 Windows内核驱动cng.sys整数溢出漏洞分析
漏洞概述
CVE-2020-17087是Windows内核驱动模块cng.sys中存在的一个整数溢出漏洞,攻击者可以利用此漏洞进行越界读写,最终实现本地提权。该漏洞曾被黑客用于Chrome沙箱逃逸(CVE-2020-15999)。
受影响版本:Windows 10 2004及之前版本
漏洞分析环境
- Windows版本:Windows 10 1903 (10.0.18362.836)
- 分析对象:cng.sys驱动模块
漏洞原理
漏洞位于cng!CfgAdtpFormatPropertyBlock函数中,关键问题在于对用户可控的size参数进行6 * size运算时,结果被强制转换为有符号16位整数,导致整数溢出。
关键代码分析
__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; // 整数溢出点:6 * 0x2aab = 2
pool_ptr = BCryptAlloc((unsigned __int16)(6 * size)); // 申请过小的内存池
v9 = pool_ptr;
if (pool_ptr)
{
pool_ptr_w = pool_ptr;
if (size)
{
count = size;
do {
// 每次写入6字节
*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); // 循环次数为size(0x2ab)次
}
*(_QWORD *)(a3 + 8) = v9;
*(_WORD *)(a3 + 2) = v7;
*(_WORD *)a3 = v7 - 2;
}
else
{
return 0xC000009A;
}
}
else
{
return 0xC000000D;
}
return v3;
}
漏洞触发流程
- 当
size被控制为0x2ab时,6 * 0x2ab = 0x1002,但强制转换为16位有符号整数后结果为2 BCryptAlloc函数根据这个过小的NumberOfBytes(2)申请内存池空间- 但后续的do-while循环次数仍为原始的
size值(0x2ab),每次写入6字节 - 最终导致越界写入,触发蓝屏(BSOD)
漏洞触发路径分析
1. 用户态调用路径
用户态通过DeviceIoControlAPI发起IOCTL请求:
- IO控制码(IoCode):
0x390400 - 该请求最终会调用内核驱动模块中的
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]
2. 内核态调用链
完整的调用链如下:
cng!CngDispatch
cng!CngDeviceControl
cng!ConfigIoHandler_Safeguarded
cng!IoUnpack_SG_ParamBlock_Header
cng!ConfigFunctionIoHandler
cng!ConfigurationFunctionIoHandler
cng!IoUnpack_SG_Configuration_ParamBlock
cng!IoUnpack_SG_SzString
cng!IoUnpack_SG_ContextFunctionConfig
cng!IoUnpack_SG_Buffer
cng!BCryptSetContextFunctionProperty
cng!CfgReg_Acquire
cng!CfgAdtReportFunctionPropertyModification
cng!CfgAdtpFormatPropertyBlock (漏洞函数)
3. 关键函数分析
cng!ConfigIoHandler_Safeguarded
该函数根据输入缓冲区的长度申请两个大小相同的池块(pool1和pool2):
- 将输入缓冲区内容拷贝至pool1
- pool2初始化为0
- 调用
cng!IoUnpack_SG_ParamBlock_Header进行初步处理
cng!IoUnpack_SG_ParamBlock_Header
该函数进行以下操作:
- 检查pool1的前4字节必须为
0x1A2B3C4D - 第3个参数保存pool1的第二个4字节(0x10400)
- for循环内将pool2的前8字节置为
0xFFFFFFFFFFFFFFFF
cng!ConfigurationFunctionIoHandler
根据输入缓冲区的第二个四字节(0x10400被截断为0x400),进入分支:
if (a1 == 0x400)
return BCryptSetContextFunctionProperty(...);
cng!BCryptSetContextFunctionProperty
该函数:
- 使用
cbValue和pbValue初始化DestinationString - 调用
cng!CfgReg_Acquire尝试访问注册表项 - 初始化三个UnicodeString(使用pool1+0x100、pool1+0x200、pool1+0x400处的字符)
- 调用
cng!CfgAdtReportFunctionPropertyModification
cng!CfgAdtReportFunctionPropertyModification
该函数最终调用漏洞函数cng!CfgAdtpFormatPropertyBlock,传入的关键参数:
- 第二个参数(size)来自
pool_ptr1 + 0x50处的值(0x2aab)
漏洞利用关键点
- 可控的size参数:通过精心构造的输入缓冲区,可以控制
pool_ptr1 + 0x50处的值 - 整数溢出:
6 * size的结果强制转换为16位有符号整数导致溢出 - 内存分配与写入不匹配:分配小内存但进行大量写入
- 越界写入:最终导致相邻内存被破坏,可能实现任意代码执行
补丁分析
微软通过以下方式修复了该漏洞:
- 在
cng!CfgAdtpFormatPropertyBlock函数中增加了cng!RtlUShortMult函数调用 RtlUShortMult函数对size * 6的结果进行溢出检查
补丁后的关键代码:
__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;
}