Windows-cng.sys池缓冲区溢出漏洞分析-CVE-2020-17087
字数 2696
更新时间 2025-12-31 12:29:16

Windows CNG.sys 池缓冲区溢出漏洞分析(CVE-2020-17087)教学文档

一、漏洞概述

1.1 漏洞基本信息

  • 漏洞名称:Windows CNG.sys 池缓冲区溢出漏洞
  • 漏洞编号:CVE-2020-17087
  • 漏洞类型:整数溢出导致的池缓冲区溢出
  • 影响范围:本地权限提升
  • CVSS v3.0评分:7.8(高危)
  • 基础权限要求:普通用户权限
  • 受影响组件:cng.sys(Cryptography Next Generation驱动程序)

1.2 漏洞环境

  • 操作系统:Windows 10 2004(19041.264)
  • 分析工具
    • Windbg 1.2506.12002.0
    • IDA Pro 9.0
    • VS 2022

二、漏洞原理分析

2.1 漏洞位置

漏洞位于cng.sys驱动程序的CfgAdtpFormatPropertyBlock函数中,该函数处理IOCTL 0x390400请求。

2.2 漏洞机制

2.2.1 整数溢出

关键漏洞代码:

USHORT OutputLength = SourceLength * 6;  // 整数溢出点
PVOID OutputBuffer = ExAllocatePoolWithTag(NonPagedPool, OutputLength, 'gfNC');

漏洞分析

  • SourceLength为输入参数,USHORT为16位无符号整数(最大值65535)
  • SourceLength ≥ 0x2AAB时,0x2AAB * 6 = 0x10002,超过USHORT最大值
  • 发生整数溢出,OutputLength被截断为0x0002
  • 实际分配仅2字节缓冲区,但后续操作会写入0x10002字节数据

2.2.2 缓冲区溢出

在二进制转十六进制的循环处理中:

  • 每个输入字节转换为6字节十六进制表示(如24 → "0x18 ")
  • 加上空格分隔符,导致写入数据远超分配缓冲区大小
  • 溢出发生在非分页池(NonPagedPool)中

三、漏洞复现

3.1 复现步骤

  1. 编译并执行提供的exp1.exe
  2. 执行前使用whoami确认当前权限
  3. 运行exp1.exe后再次执行whoami验证权限提升

3.2 PoC代码分析

#include <cstdio>
#include <windows.h>

int main() {
    // 打开CNG设备
    HANDLE hCng = CreateFileA("\\\\.\\GLOBALROOT\\Device\\Cng", 
                              GENERIC_READ | GENERIC_WRITE, 0, NULL, 
                              OPEN_EXISTING, 0, NULL);
    
    CONST DWORD DataBufferSize = 0x2AAB;  // 触发溢出的关键值
    CONST DWORD IoctlSize = 4096 + DataBufferSize;
    BYTE *IoctlData = (BYTE *)HeapAlloc(GetProcessHeap(), 0, IoctlSize);
    
    // 构造恶意IOCTL数据
    *(DWORD*) &IoctlData[0x50] = DataBufferSize;  // 溢出参数
    
    // 发送恶意IOCTL
    DeviceIoControl(hCng, 0x390400, IoctlData, IoctlSize, 
                   &OutputBuffer, sizeof(OutputBuffer), &BytesReturned, NULL);
    
    return 0;
}

四、利用技术深入分析

4.1 利用框架概述

利用过程采用命名管道(Named Pipe)机制进行内核池布局和利用,主要步骤:

  1. 池风水布局(Heap Feng Shui)
  2. 精确池喷射(Precise Pool Spraying)
  3. 任意读原语构建(Arbitrary Read Primitive)
  4. 任意写原语构建(Arbitrary Write Primitive)
  5. 令牌窃取提权(Token Stealing)

4.2 关键技术组件

4.2.1 命名管道机制

  • NPFS.sys:命名管道文件系统驱动
  • DATA_QUEUE_ENTRY结构
typedef struct _DATA_QUEUE_ENTRY {
    LIST_ENTRY ListEntry;        // 双向链表指针
    PVOID SecurityContext;       // 安全上下文(用于模拟)
    ULONG EntryType;             // 条目类型(缓冲/非缓冲)
    PIRP Irp;                    // 关联的IRP结构
    ULONG QuotaInEntry;          // 配额计数
    ULONG DataSize;              // 数据大小
    // ... 其他字段
} DATA_QUEUE_ENTRY;
  • 条目类型
    • 缓冲条目:数据存储在NPFS分配的池缓冲区中
    • 非缓冲条目:数据存储在用户提供的缓冲区中

4.2.2 池分配策略

池喷射布局表

管道类型 数量 数据大小 目的
spare_pipe 10000 0x1F0 清空FreeChunkTree,强制连续分配
subsegments_pipe 10 0x8E20 隔离布局区域,防止溢出扩散
victim_pipes 10000 0x2D0/0x4000 主要攻击目标
bcrypt_pipe 1 0x8D2 触发漏洞的管道

4.3 利用步骤详解

4.3.1 阶段一:池布局准备

  1. 激活LFH:通过EnableLookaside函数激活低碎片堆(LFH)
  2. 池风水:精确控制内存分配模式,确保可预测的布局
  3. hole创建:在布局中预留特定位置用于漏洞触发

4.3.2 阶段二:漏洞触发与覆盖

  1. 发送恶意IOCTL:触发cng.sys中的整数溢出
  2. 覆盖DATA_QUEUE_ENTRY:精确覆盖相邻chunk的flink指针
  3. 构造伪造条目:通过覆盖实现内存任意读写能力

4.3.3 阶段三:任意读原语

// 伪造的非缓冲条目结构
struct UNDERCOVER_ENTRY {
    LIST_ENTRY ListEntry;        // 被覆盖的链表指针
    PVOID SecurityContext;       // 安全上下文
    ULONG EntryType;             // 条目类型
    PIRP Irp;                    // 指向用户控制的IRP
    ULONG QuotaInEntry;          // 配额
    ULONG DataSize;              // 数据大小
};

实现机制

  • 覆盖victim_pipe条目的flink指向用户态构造的伪造条目
  • 通过PeekNamedPipe操作读取伪造条目指向的内核数据
  • 利用IRP的SystemBuffer字段实现任意地址读取

4.3.4 阶段四:任意写原语

  1. 伪造IRP结构:构建用于权限提升的恶意IRP
  2. 线程挂起条目:创建stalled_entry获取线程和进程信息
  3. 令牌替换:通过任意写将system进程令牌复制到当前进程

4.3.5 阶段五:权限提升

  1. 获取EPROCESS:通过线程结构找到系统进程
  2. 令牌窃取:读取system进程的token值
  3. 令牌覆盖:将当前进程token替换为system token

4.4 关键技术难点

4.4.1 精确池控制

  • LFH激活:确保特定大小的分配从LFH进行
  • Lookaside列表:利用Lookaside机制提高分配可预测性
  • 子段隔离:防止溢出影响其他关键结构

4.4.2 地址信息泄露

  • PeekNamedPipe利用:通过该函数获取内核地址信息
  • 链表遍历:利用DATA_QUEUE_ENTRY的双向链表特性
  • 偏移计算:精确计算各种结构体在内核中的偏移

4.4.3 稳定性保障

  • 错误处理:完善的错误检测和恢复机制
  • 竞争条件避免:通过合适的同步机制防止竞争
  • 多重验证:对关键操作结果进行多重验证

五、防御和缓解措施

5.1 微软官方补丁

  • 修复cng.sys中的整数溢出检查
  • 增加输入参数验证机制
  • 改进池分配大小计算

5.2 系统级防护

  1. 驱动签名验证:确保加载的驱动程序经过数字签名
  2. 池保护机制:启用PoolTag和PoolMonitor检测异常池操作
  3. 权限最小化:遵循最小权限原则,减少攻击面

5.3 开发建议

  1. 安全编码:对数值运算进行边界检查
  2. 静态分析:使用代码分析工具检测整数溢出漏洞
  3. 模糊测试:对驱动程序IOCTL接口进行充分的模糊测试

六、总结与启示

CVE-2020-17087是一个典型的内核池溢出漏洞,其利用技术展示了现代Windows内核漏洞利用的多个重要方面:

  1. 漏洞利用复杂性:从简单的缓冲区溢出到完整的权限提升链
  2. 内核池管理理解:深入理解Windows内存管理机制是成功利用的关键
  3. 利用技术演进:结合了池风水、任意读写原语等高级技术

该漏洞的分析对于理解Windows内核安全机制、驱动漏洞利用技术以及相应的防御措施具有重要参考价值。

Windows CNG.sys 池缓冲区溢出漏洞分析(CVE-2020-17087)教学文档

一、漏洞概述

1.1 漏洞基本信息

  • 漏洞名称:Windows CNG.sys 池缓冲区溢出漏洞
  • 漏洞编号:CVE-2020-17087
  • 漏洞类型:整数溢出导致的池缓冲区溢出
  • 影响范围:本地权限提升
  • CVSS v3.0评分:7.8(高危)
  • 基础权限要求:普通用户权限
  • 受影响组件:cng.sys(Cryptography Next Generation驱动程序)

1.2 漏洞环境

  • 操作系统:Windows 10 2004(19041.264)
  • 分析工具
    • Windbg 1.2506.12002.0
    • IDA Pro 9.0
    • VS 2022

二、漏洞原理分析

2.1 漏洞位置

漏洞位于cng.sys驱动程序的CfgAdtpFormatPropertyBlock函数中,该函数处理IOCTL 0x390400请求。

2.2 漏洞机制

2.2.1 整数溢出

关键漏洞代码:

USHORT OutputLength = SourceLength * 6;  // 整数溢出点
PVOID OutputBuffer = ExAllocatePoolWithTag(NonPagedPool, OutputLength, 'gfNC');

漏洞分析

  • SourceLength为输入参数,USHORT为16位无符号整数(最大值65535)
  • SourceLength ≥ 0x2AAB时,0x2AAB * 6 = 0x10002,超过USHORT最大值
  • 发生整数溢出,OutputLength被截断为0x0002
  • 实际分配仅2字节缓冲区,但后续操作会写入0x10002字节数据

2.2.2 缓冲区溢出

在二进制转十六进制的循环处理中:

  • 每个输入字节转换为6字节十六进制表示(如24 → "0x18 ")
  • 加上空格分隔符,导致写入数据远超分配缓冲区大小
  • 溢出发生在非分页池(NonPagedPool)中

三、漏洞复现

3.1 复现步骤

  1. 编译并执行提供的exp1.exe
  2. 执行前使用whoami确认当前权限
  3. 运行exp1.exe后再次执行whoami验证权限提升

3.2 PoC代码分析

#include <cstdio>
#include <windows.h>

int main() {
    // 打开CNG设备
    HANDLE hCng = CreateFileA("\\\\.\\GLOBALROOT\\Device\\Cng", 
                              GENERIC_READ | GENERIC_WRITE, 0, NULL, 
                              OPEN_EXISTING, 0, NULL);
    
    CONST DWORD DataBufferSize = 0x2AAB;  // 触发溢出的关键值
    CONST DWORD IoctlSize = 4096 + DataBufferSize;
    BYTE *IoctlData = (BYTE *)HeapAlloc(GetProcessHeap(), 0, IoctlSize);
    
    // 构造恶意IOCTL数据
    *(DWORD*) &IoctlData[0x50] = DataBufferSize;  // 溢出参数
    
    // 发送恶意IOCTL
    DeviceIoControl(hCng, 0x390400, IoctlData, IoctlSize, 
                   &OutputBuffer, sizeof(OutputBuffer), &BytesReturned, NULL);
    
    return 0;
}

四、利用技术深入分析

4.1 利用框架概述

利用过程采用命名管道(Named Pipe)机制进行内核池布局和利用,主要步骤:

  1. 池风水布局(Heap Feng Shui)
  2. 精确池喷射(Precise Pool Spraying)
  3. 任意读原语构建(Arbitrary Read Primitive)
  4. 任意写原语构建(Arbitrary Write Primitive)
  5. 令牌窃取提权(Token Stealing)

4.2 关键技术组件

4.2.1 命名管道机制

  • NPFS.sys:命名管道文件系统驱动
  • DATA_QUEUE_ENTRY结构
typedef struct _DATA_QUEUE_ENTRY {
    LIST_ENTRY ListEntry;        // 双向链表指针
    PVOID SecurityContext;       // 安全上下文(用于模拟)
    ULONG EntryType;             // 条目类型(缓冲/非缓冲)
    PIRP Irp;                    // 关联的IRP结构
    ULONG QuotaInEntry;          // 配额计数
    ULONG DataSize;              // 数据大小
    // ... 其他字段
} DATA_QUEUE_ENTRY;
  • 条目类型
    • 缓冲条目:数据存储在NPFS分配的池缓冲区中
    • 非缓冲条目:数据存储在用户提供的缓冲区中

4.2.2 池分配策略

池喷射布局表

管道类型 数量 数据大小 目的
spare_pipe 10000 0x1F0 清空FreeChunkTree,强制连续分配
subsegments_pipe 10 0x8E20 隔离布局区域,防止溢出扩散
victim_pipes 10000 0x2D0/0x4000 主要攻击目标
bcrypt_pipe 1 0x8D2 触发漏洞的管道

4.3 利用步骤详解

4.3.1 阶段一:池布局准备

  1. 激活LFH:通过EnableLookaside函数激活低碎片堆(LFH)
  2. 池风水:精确控制内存分配模式,确保可预测的布局
  3. hole创建:在布局中预留特定位置用于漏洞触发

4.3.2 阶段二:漏洞触发与覆盖

  1. 发送恶意IOCTL:触发cng.sys中的整数溢出
  2. 覆盖DATA_QUEUE_ENTRY:精确覆盖相邻chunk的flink指针
  3. 构造伪造条目:通过覆盖实现内存任意读写能力

4.3.3 阶段三:任意读原语

// 伪造的非缓冲条目结构
struct UNDERCOVER_ENTRY {
    LIST_ENTRY ListEntry;        // 被覆盖的链表指针
    PVOID SecurityContext;       // 安全上下文
    ULONG EntryType;             // 条目类型
    PIRP Irp;                    // 指向用户控制的IRP
    ULONG QuotaInEntry;          // 配额
    ULONG DataSize;              // 数据大小
};

实现机制

  • 覆盖victim_pipe条目的flink指向用户态构造的伪造条目
  • 通过PeekNamedPipe操作读取伪造条目指向的内核数据
  • 利用IRP的SystemBuffer字段实现任意地址读取

4.3.4 阶段四:任意写原语

  1. 伪造IRP结构:构建用于权限提升的恶意IRP
  2. 线程挂起条目:创建stalled_entry获取线程和进程信息
  3. 令牌替换:通过任意写将system进程令牌复制到当前进程

4.3.5 阶段五:权限提升

  1. 获取EPROCESS:通过线程结构找到系统进程
  2. 令牌窃取:读取system进程的token值
  3. 令牌覆盖:将当前进程token替换为system token

4.4 关键技术难点

4.4.1 精确池控制

  • LFH激活:确保特定大小的分配从LFH进行
  • Lookaside列表:利用Lookaside机制提高分配可预测性
  • 子段隔离:防止溢出影响其他关键结构

4.4.2 地址信息泄露

  • PeekNamedPipe利用:通过该函数获取内核地址信息
  • 链表遍历:利用DATA_QUEUE_ENTRY的双向链表特性
  • 偏移计算:精确计算各种结构体在内核中的偏移

4.4.3 稳定性保障

  • 错误处理:完善的错误检测和恢复机制
  • 竞争条件避免:通过合适的同步机制防止竞争
  • 多重验证:对关键操作结果进行多重验证

五、防御和缓解措施

5.1 微软官方补丁

  • 修复cng.sys中的整数溢出检查
  • 增加输入参数验证机制
  • 改进池分配大小计算

5.2 系统级防护

  1. 驱动签名验证:确保加载的驱动程序经过数字签名
  2. 池保护机制:启用PoolTag和PoolMonitor检测异常池操作
  3. 权限最小化:遵循最小权限原则,减少攻击面

5.3 开发建议

  1. 安全编码:对数值运算进行边界检查
  2. 静态分析:使用代码分析工具检测整数溢出漏洞
  3. 模糊测试:对驱动程序IOCTL接口进行充分的模糊测试

六、总结与启示

CVE-2020-17087是一个典型的内核池溢出漏洞,其利用技术展示了现代Windows内核漏洞利用的多个重要方面:

  1. 漏洞利用复杂性:从简单的缓冲区溢出到完整的权限提升链
  2. 内核池管理理解:深入理解Windows内存管理机制是成功利用的关键
  3. 利用技术演进:结合了池风水、任意读写原语等高级技术

该漏洞的分析对于理解Windows内核安全机制、驱动漏洞利用技术以及相应的防御措施具有重要参考价值。

Windows CNG.sys 池缓冲区溢出漏洞分析(CVE-2020-17087)教学文档 一、漏洞概述 1.1 漏洞基本信息 漏洞名称 :Windows CNG.sys 池缓冲区溢出漏洞 漏洞编号 :CVE-2020-17087 漏洞类型 :整数溢出导致的池缓冲区溢出 影响范围 :本地权限提升 CVSS v3.0评分 :7.8(高危) 基础权限要求 :普通用户权限 受影响组件 :cng.sys(Cryptography Next Generation驱动程序) 1.2 漏洞环境 操作系统 :Windows 10 2004(19041.264) 分析工具 : Windbg 1.2506.12002.0 IDA Pro 9.0 VS 2022 二、漏洞原理分析 2.1 漏洞位置 漏洞位于cng.sys驱动程序的 CfgAdtpFormatPropertyBlock 函数中,该函数处理IOCTL 0x390400请求。 2.2 漏洞机制 2.2.1 整数溢出 关键漏洞代码: 漏洞分析 : SourceLength 为输入参数, USHORT 为16位无符号整数(最大值65535) 当 SourceLength ≥ 0x2AAB 时, 0x2AAB * 6 = 0x10002 ,超过USHORT最大值 发生整数溢出, OutputLength 被截断为0x0002 实际分配仅2字节缓冲区,但后续操作会写入0x10002字节数据 2.2.2 缓冲区溢出 在二进制转十六进制的循环处理中: 每个输入字节转换为6字节十六进制表示(如24 → "0x18 ") 加上空格分隔符,导致写入数据远超分配缓冲区大小 溢出发生在非分页池(NonPagedPool)中 三、漏洞复现 3.1 复现步骤 编译并执行提供的exp1.exe 执行前使用 whoami 确认当前权限 运行exp1.exe后再次执行 whoami 验证权限提升 3.2 PoC代码分析 四、利用技术深入分析 4.1 利用框架概述 利用过程采用命名管道(Named Pipe)机制进行内核池布局和利用,主要步骤: 池风水布局(Heap Feng Shui) 精确池喷射(Precise Pool Spraying) 任意读原语构建(Arbitrary Read Primitive) 任意写原语构建(Arbitrary Write Primitive) 令牌窃取提权(Token Stealing) 4.2 关键技术组件 4.2.1 命名管道机制 NPFS.sys :命名管道文件系统驱动 DATA_ QUEUE_ ENTRY结构 : 条目类型 : 缓冲条目 :数据存储在NPFS分配的池缓冲区中 非缓冲条目 :数据存储在用户提供的缓冲区中 4.2.2 池分配策略 池喷射布局表 : | 管道类型 | 数量 | 数据大小 | 目的 | |---------|------|----------|------| | spare_ pipe | 10000 | 0x1F0 | 清空FreeChunkTree,强制连续分配 | | subsegments_ pipe | 10 | 0x8E20 | 隔离布局区域,防止溢出扩散 | | victim_ pipes | 10000 | 0x2D0/0x4000 | 主要攻击目标 | | bcrypt_ pipe | 1 | 0x8D2 | 触发漏洞的管道 | 4.3 利用步骤详解 4.3.1 阶段一:池布局准备 激活LFH :通过 EnableLookaside 函数激活低碎片堆(LFH) 池风水 :精确控制内存分配模式,确保可预测的布局 hole创建 :在布局中预留特定位置用于漏洞触发 4.3.2 阶段二:漏洞触发与覆盖 发送恶意IOCTL :触发cng.sys中的整数溢出 覆盖DATA_ QUEUE_ ENTRY :精确覆盖相邻chunk的flink指针 构造伪造条目 :通过覆盖实现内存任意读写能力 4.3.3 阶段三:任意读原语 实现机制 : 覆盖victim_ pipe条目的flink指向用户态构造的伪造条目 通过PeekNamedPipe操作读取伪造条目指向的内核数据 利用IRP的SystemBuffer字段实现任意地址读取 4.3.4 阶段四:任意写原语 伪造IRP结构 :构建用于权限提升的恶意IRP 线程挂起条目 :创建stalled_ entry获取线程和进程信息 令牌替换 :通过任意写将system进程令牌复制到当前进程 4.3.5 阶段五:权限提升 获取EPROCESS :通过线程结构找到系统进程 令牌窃取 :读取system进程的token值 令牌覆盖 :将当前进程token替换为system token 4.4 关键技术难点 4.4.1 精确池控制 LFH激活 :确保特定大小的分配从LFH进行 Lookaside列表 :利用Lookaside机制提高分配可预测性 子段隔离 :防止溢出影响其他关键结构 4.4.2 地址信息泄露 PeekNamedPipe利用 :通过该函数获取内核地址信息 链表遍历 :利用DATA_ QUEUE_ ENTRY的双向链表特性 偏移计算 :精确计算各种结构体在内核中的偏移 4.4.3 稳定性保障 错误处理 :完善的错误检测和恢复机制 竞争条件避免 :通过合适的同步机制防止竞争 多重验证 :对关键操作结果进行多重验证 五、防御和缓解措施 5.1 微软官方补丁 修复cng.sys中的整数溢出检查 增加输入参数验证机制 改进池分配大小计算 5.2 系统级防护 驱动签名验证 :确保加载的驱动程序经过数字签名 池保护机制 :启用PoolTag和PoolMonitor检测异常池操作 权限最小化 :遵循最小权限原则,减少攻击面 5.3 开发建议 安全编码 :对数值运算进行边界检查 静态分析 :使用代码分析工具检测整数溢出漏洞 模糊测试 :对驱动程序IOCTL接口进行充分的模糊测试 六、总结与启示 CVE-2020-17087是一个典型的内核池溢出漏洞,其利用技术展示了现代Windows内核漏洞利用的多个重要方面: 漏洞利用复杂性 :从简单的缓冲区溢出到完整的权限提升链 内核池管理理解 :深入理解Windows内存管理机制是成功利用的关键 利用技术演进 :结合了池风水、任意读写原语等高级技术 该漏洞的分析对于理解Windows内核安全机制、驱动漏洞利用技术以及相应的防御措施具有重要参考价值。