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

漏洞触发流程

  1. size被控制为0x2ab时,6 * 0x2ab = 0x1002,但强制转换为16位有符号整数后结果为2
  2. BCryptAlloc函数根据这个过小的NumberOfBytes(2)申请内存池空间
  3. 但后续的do-while循环次数仍为原始的size值(0x2ab),每次写入6字节
  4. 最终导致越界写入,触发蓝屏(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):

  1. 将输入缓冲区内容拷贝至pool1
  2. pool2初始化为0
  3. 调用cng!IoUnpack_SG_ParamBlock_Header进行初步处理

cng!IoUnpack_SG_ParamBlock_Header

该函数进行以下操作:

  1. 检查pool1的前4字节必须为0x1A2B3C4D
  2. 第3个参数保存pool1的第二个4字节(0x10400)
  3. for循环内将pool2的前8字节置为0xFFFFFFFFFFFFFFFF

cng!ConfigurationFunctionIoHandler

根据输入缓冲区的第二个四字节(0x10400被截断为0x400),进入分支:

if (a1 == 0x400)
    return BCryptSetContextFunctionProperty(...);

cng!BCryptSetContextFunctionProperty

该函数:

  1. 使用cbValuepbValue初始化DestinationString
  2. 调用cng!CfgReg_Acquire尝试访问注册表项
  3. 初始化三个UnicodeString(使用pool1+0x100、pool1+0x200、pool1+0x400处的字符)
  4. 调用cng!CfgAdtReportFunctionPropertyModification

cng!CfgAdtReportFunctionPropertyModification

该函数最终调用漏洞函数cng!CfgAdtpFormatPropertyBlock,传入的关键参数:

  • 第二个参数(size)来自pool_ptr1 + 0x50处的值(0x2aab)

漏洞利用关键点

  1. 可控的size参数:通过精心构造的输入缓冲区,可以控制pool_ptr1 + 0x50处的值
  2. 整数溢出6 * size的结果强制转换为16位有符号整数导致溢出
  3. 内存分配与写入不匹配:分配小内存但进行大量写入
  4. 越界写入:最终导致相邻内存被破坏,可能实现任意代码执行

补丁分析

微软通过以下方式修复了该漏洞:

  1. cng!CfgAdtpFormatPropertyBlock函数中增加了cng!RtlUShortMult函数调用
  2. 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;
}

参考链接

  1. Google Project Zero分析报告
  2. 微软官方漏洞指南
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位整数,导致整数溢出。 关键代码分析 漏洞触发流程 当 size 被控制为 0x2ab 时, 6 * 0x2ab = 0x1002 ,但强制转换为16位有符号整数后结果为 2 BCryptAlloc 函数根据这个过小的 NumberOfBytes (2)申请内存池空间 但后续的do-while循环次数仍为原始的 size 值(0x2ab),每次写入6字节 最终导致越界写入,触发蓝屏(BSOD) 漏洞触发路径分析 1. 用户态调用路径 用户态通过 DeviceIoControl API发起IOCTL请求: IO控制码(IoCode): 0x390400 该请求最终会调用内核驱动模块中的 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] 2. 内核态调用链 完整的调用链如下: 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),进入分支: 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 的结果进行溢出检查 补丁后的关键代码: 参考链接 Google Project Zero分析报告 微软官方漏洞指南