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 复现步骤
- 编译并执行提供的exp1.exe
- 执行前使用
whoami确认当前权限 - 运行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)机制进行内核池布局和利用,主要步骤:
- 池风水布局(Heap Feng Shui)
- 精确池喷射(Precise Pool Spraying)
- 任意读原语构建(Arbitrary Read Primitive)
- 任意写原语构建(Arbitrary Write Primitive)
- 令牌窃取提权(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 阶段一:池布局准备
- 激活LFH:通过
EnableLookaside函数激活低碎片堆(LFH) - 池风水:精确控制内存分配模式,确保可预测的布局
- hole创建:在布局中预留特定位置用于漏洞触发
4.3.2 阶段二:漏洞触发与覆盖
- 发送恶意IOCTL:触发cng.sys中的整数溢出
- 覆盖DATA_QUEUE_ENTRY:精确覆盖相邻chunk的flink指针
- 构造伪造条目:通过覆盖实现内存任意读写能力
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 阶段四:任意写原语
- 伪造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内核安全机制、驱动漏洞利用技术以及相应的防御措施具有重要参考价值。
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 复现步骤
- 编译并执行提供的exp1.exe
- 执行前使用
whoami确认当前权限 - 运行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)机制进行内核池布局和利用,主要步骤:
- 池风水布局(Heap Feng Shui)
- 精确池喷射(Precise Pool Spraying)
- 任意读原语构建(Arbitrary Read Primitive)
- 任意写原语构建(Arbitrary Write Primitive)
- 令牌窃取提权(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 阶段一:池布局准备
- 激活LFH:通过
EnableLookaside函数激活低碎片堆(LFH) - 池风水:精确控制内存分配模式,确保可预测的布局
- hole创建:在布局中预留特定位置用于漏洞触发
4.3.2 阶段二:漏洞触发与覆盖
- 发送恶意IOCTL:触发cng.sys中的整数溢出
- 覆盖DATA_QUEUE_ENTRY:精确覆盖相邻chunk的flink指针
- 构造伪造条目:通过覆盖实现内存任意读写能力
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 阶段四:任意写原语
- 伪造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内核安全机制、驱动漏洞利用技术以及相应的防御措施具有重要参考价值。