首页
社区
课程
招聘
[讨论]Windbg学习使用2
发表于: 1天前 447

[讨论]Windbg学习使用2

1天前
447

1.Windows内核系列:页表自映射原理与页表基址的逆推

结合你之前掌握的 x86-64 四级分页、VA↔PA 转换,以及游戏内核驱动、逆向、反对抗的实战场景,下面系统讲解 页表自映射 和 页表基址逆推 两大核心知识点。 这两块是 Windows x64 内核驱动的高频刚需: 页表自映射:让内核可以用固定虚拟地址直接读写各级页表,省去反复手动 VA→PA 遍历,大幅简化页表遍历、权限修改、内存扫描代码; 页表基址逆推:从已知虚拟地址/页表项,反向算出各级页表物理基址、甚至进程 CR3,用于溯源、内存排查、漏洞分析、对抗隐藏内存。 全程基于 x86-64、4KB 标准页、Windows 64 位默认规则,并穿插游戏驱动实战用法。 ## 一、前置回顾(快速对齐) ### 1. 64位虚拟地址结构(有效低48位) x86-64 四级分页(4KB页),虚拟地址 VA 低48位划分为 4 级索引 + 页内偏移: 位范围 位数 名称 符号缩写 取值范围 47 ~ 39 9 PML4 索引 I4 0~511 38 ~ 30 9 PDPT 索引 I3 0~511 29 ~ 21 9 PD 索引 I2 0~511 20 ~ 12 9 PT 索引 I1 0~511 11 ~ 0 12 页内偏移 Offset 0~4095 单级页表大小:512项 × 8字节 = 4KB,和页尺寸一致; 页表项通用规则:物理基址 = 页表项值 & 0xFFFFFFFFFF000(清空低12位标志位)。 ### 2. 正向流程(复习) CR3(PM4基址) → I4 → PML4E → I3 → PDPTE → I2 → PDE → I1 → PTE → 物理页基址 + Offset → 物理地址。 ## 二、页表自映射(Page Table Self-Mapping) ### 2.1 什么是页表自映射? 自映射:操作系统主动把页表本身的物理内存,映射到一段固定内核虚拟地址区间,形成「页表自己映射自己」的闭环。 ### 核心价值 正常访问页表内容,必须走:CR3 → 四级索引 → 物理地址,代码繁琐、重复度高; 开启自映射后,直接用虚拟地址就能读写 PML4/PDPT/PD/PT 所有页表项,不需要手动做 VA→PA 转换。 关键限制:自映射区域属于内核高地址空间,用户态程序无法访问(会触发访问违规/蓝屏),仅内核驱动可用。 ### 2.2 Windows x64 固定规则(全网通用) Windows 10/11 x64 系统有一个全局固定配置: 1. PML4 表一共 512 项(索引 0 ~ 511); 2. 将 PML4[511](索引 = 0x1FF)这一项,指向 PML4 自身的物理基址(即 CR3 & 0xFFFFFFFFFF000); 3. 这个索引 0x1FF 称为自映射索引,是整个机制的核心。 简写: SELF_MAP_INDEX = 0x1FF  // 固定 511 PML4[0x1FF] = PML4 物理基址 因为 PML4 第 511 项指向自己,四级页表形成闭环,所有层级页表都能通过一段连续虚拟地址访问。 ### 2.3 各级页表的自映射虚拟地址推导 我们利用 I4 = 0x1FF 这个固定值,分别构造访问 PML4、PDPT、PD、PT 的虚拟地址。 约定: 任意索引:idx4(PML4项索引)、idx3(PDPT项索引)、idx2(PD项索引)、idx1(PT项索引) 页表项在页内偏移:off = 索引 × 8(每项8字节) #### 1)访问 PML4 页表本身(最高级页表) 要读取 PML4 中第 idx4 个表项(PML4E): 固定 I4 = 0x1FF(走自映射项),I3=idx4,其余索引置0,偏移 = idx4 × 8 地址拼接(位运算): VA_PML4 = (0x1FF << 39) | (idx4 << 30) | (0 << 21) | (0 << 12) | (idx4 * 8) 作用:直接通过 VA_PML4 虚拟地址,读写当前进程 PML4 的任意表项。 #### 2)访问任意 PDPT 页表 PML4 第 idx4 项指向某一个 PDPT,读取该 PDPT 内第 idx3 项(PDPTE): VA_PDPT = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (0 << 12) | (idx3 * 8) #### 3)访问任意 PD 页表 PDPT 第 idx3 项指向某一个 PD,读取该 PD 内第 idx2 项(PDE): VA_PD = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (idx2 << 12) | (idx2 * 8) #### 4)访问任意 PT 页表(最常用) PD 第 idx2 项指向某一个 PT,读取该 PT 内第 idx1 项(PTE): VA_PT = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (idx2 << 12) | (idx1 * 8) 极简记忆 所有自映射地址的最高9位(I4)永远是 0x1FF,后面三段索引依次对应下一级页表。 ### 2.4 举个实例(直观理解) 读取 PML4[0] 表项: idx4 = 0 VA = (0x1FF << 39) | (0 << 30) | 0 | 0 | 0 在内核驱动中直接读取这个虚拟地址,拿到的就是 PML4 第 0 项的原始值,无需 VA→PA 转换。 ### 2.5 游戏驱动中的实战用途 1. 快速遍历进程所有页表 传统方式:遍历每一个虚拟地址 → 手动四级VA→PA,效率极低; 自映射方式:直接循环自映射虚拟地址区间,批量读取 PTE/PDE,扫描游戏所有内存页。 2. 修改页表权限(对抗反作弊) 游戏/反作弊会把关键内存设为 只读(RW=0)、不可执行(XD=1); 利用自映射直接定位 PTE,修改标志位,解除保护、实现代码注入/内存修改。 3. 检测页表篡改(反外挂) 反作弊会监控 PML4[0x1FF] 自映射项,一旦发现被篡改,判定存在恶意驱动。 4. WinDbg 调试底层依赖 !pte、!process、dq 内核地址 等调试命令,底层全部依赖页表自映射。 ## 三、页表基址逆推(反向推导) 正向:CR3 → 各级索引 → 物理地址 逆推:已知「虚拟地址 / 页表项 / 物理地址」,反向算出 PT/PD/PDPT/PML4 物理基址、进程CR3。 这部分是溯源分析、漏洞利用、内存隐藏排查的核心,分三大实战场景,同时兼容 4KB普通页、2MB大页、1GB大页(Unity/UE游戏高频大页,必须处理)。 ### 通用前置规则 1. 页表物理基址:Base = Entry & 0xFFFFFFFFFF000(清空低12位标志) 2. 大页判断位: PDE 第7位(PS)=1 → 2MB大页(终止于PD层,无PT) PDPTE 第7位(PS)=1 → 1GB大页(终止于PDPT层,无PD/PT) 3. 索引提取公式(任意虚拟地址VA): I4 = (VA >> 39) & 0x1FF;  // PML4 索引 I3 = (VA >> 30) & 0x1FF;  // PDPT 索引 I2 = (VA >> 21) & 0x1FF;  // PD 索引 I1 = (VA >> 12) & 0x1FF;  // PT 索引 Offset = VA & 0xFFF;      // 页内偏移 ### 场景1:已知虚拟地址 VA,逆推 各级页表基址 + CR3(最常用) #### 适用场景 拿到游戏内一个虚拟地址(比如血量、坐标地址),反向找到它所在的 PT/PD/PDPT/PML4 物理基址,最终算出进程 CR3。 #### 步骤(4KB标准页,逐层逆推) 步骤1:拆分VA,提取4级索引 + 偏移 I4, I3, I2, I1, Offset = 从VA中拆分 步骤2:获取 PTE(PT表项)→ 得到 PT 物理基址 1. 通过自映射/正向遍历,读取该VA对应的 PTE 值; 2. PT_Base = PTE & 0xFFFFFFFFFF000 (PT页表物理基址) 步骤3:获取 PDE(PD表项)→ 得到 PD 物理基址 1. 根据 I2 找到对应 PDE; 2. PD_Base = PDE & 0xFFFFFFFFFF000 步骤4:获取 PDPTE → 得到 PDPT 物理基址 1. 根据 I3 找到对应 PDPTE; 2. PDPT_Base = PDPTE & 0xFFFFFFFFFF000 步骤5:获取 PML4E → 得到 PML4 物理基址 = CR3基址 1. 根据 I4 找到对应 PML4E; 2. PML4_Base = PML4E & 0xFFFFFFFFFF000 3. 最终:CR3 = PML4_Base(CR3低12位为标志位,基址部分等于PML4物理地址) #### 大页兼容(游戏必加) 遍历中一旦检测到 PS=1,层级直接截断,不再向下逆推: 1. 2MB大页(PDE.PS=1) 地址终止于PD层,无PT;物理页基址直接由PDE提取,逆推到 PD_Base 即可结束。 2. 1GB大页(PDPTE.PS=1) 地址终止于PDPT层,无PD/PT;逆推到 PDPT_Base 即可结束。 ### 场景2:已知某一级页表项,逆推「上级页表基址」 举例1:已知 PTE,逆推 PT 所在的 PD 基址 1. PTE 属于 PT 页表,PT 的物理基址 = PTE & 0xFFFFFFFFFF000; 2. PT 由 PD 中某一项 PDE 指向,结合原VA的 I2 索引,找到 PDE; 3. PD_Base = PDE & 0xFFFFFFFFFF000。 举例2:已知 PDE,逆推 PDPT 基址 逻辑同上,利用上一级索引回溯,层层向上。 实战价值: 分析被反作弊隐藏的内存页:拿到一个异常PTE,向上溯源,找到它所属的整个页表,批量扫描同类隐藏页面。 ### 场景3:已知物理页基址,逆推对应的虚拟地址(反向映射) #### 适用场景 拿到一块物理内存(比如截图帧缓冲、外挂代码所在物理页),查找它被映射到哪些虚拟地址,常用于: 排查 DXGI 截屏黑屏(显存/物理页映射异常); 检测内存隐藏(物理页被映射到多个虚拟地址、内核隐藏区域)。 #### 核心思路 1. 遍历当前进程 整个PML4页表; 2. 逐层比对每一级页表项的物理基址; 3. 匹配到目标物理页后,反向拼接索引,还原出原始虚拟地址。 特点:计算量大,一般只在调试、漏洞分析时使用。 ## 四、内核驱动伪代码(可直接移植到游戏驱动) 基于 C 语言 + Windows x64 内核规范,实现 自映射读页表 + VA逆推CR3 两大功能。 ### 1. 基础宏定义 #include <ntddk.h> // Windows x64 固定自映射索引 #define SELF_MAP_IDX    0x1FF #define PAGE_MASK       0xFFFFFFFFFF000ULL #define ENTRY_SIZE      8ULL #define IDX_MASK        0x1FFULL // 从VA提取四级索引 VOID SplitVa(ULONG64 Va, ULONG64 *pI4, ULONG64 *pI3, ULONG64 *pI2, ULONG64 *pI1, ULONG64 *pOffset) {    *pI4 = (Va >> 39) & IDX_MASK;    *pI3 = (Va >> 30) & IDX_MASK;    *pI2 = (Va >> 21) & IDX_MASK;    *pI1 = (Va >> 12) & IDX_MASK;    *pOffset = Va & 0xFFF; } ### 2. 利用自映射读取任意 PTE // 输入:进程CR3 + 目标虚拟地址,输出PTE值 NTSTATUS GetPteBySelfMap(ULONG64 Cr3, ULONG64 TargetVa, ULONG64 *pPte) {    ULONG64 I4, I3, I2, I1, Off;    SplitVa(TargetVa, &I4, &I3, &I2, &I1, &Off);    // 构造PT页表的自映射虚拟地址    ULONG64 VaPt = (SELF_MAP_IDX << 39) | (I4 << 30) | (I3 << 21) | (I2 << 12) | (I1 * ENTRY_SIZE);    // 内核直接读取虚拟地址(自映射地址合法)    *pPte = *(PULONG64)VaPt;    // 校验Present位    if (!(*pPte & 1))        return STATUS_PAGE_FAULT_ERROR;    return STATUS_SUCCESS; } ### 3. 从虚拟地址逆推 CR3 // 输入:目标虚拟地址,输出各级页表基址 + CR3 NTSTATUS VaReverseToCr3(ULONG64 TargetVa,                        ULONG64 *pPtBase,                        ULONG64 *pPdBase,                        ULONG64 *pPdptBase,                        ULONG64 *pCr3) {    ULONG64 I4, I3, I2, I1, Off;    ULONG64 Pte, Pde, Pdte, Pml4e;    SplitVa(TargetVa, &I4, &I3, &I2, &I1, &Off);    // 1. 读PTE → PT基址    NTSTATUS status = GetPteBySelfMap(*pCr3, TargetVa, &Pte);    if (!NT_SUCCESS(status)) return status;    *pPtBase = Pte & PAGE_MASK;    // 2. 读PDE → PD基址(判断2MB大页)    ULONG64 VaPd = (SELF_MAP_IDX << 39) | (I4 << 30) | (I3 << 21) | (I2 * ENTRY_SIZE);    Pde = *(PULONG64)VaPd;    if (!Pde & 1) return STATUS_PAGE_FAULT_ERROR;    *pPdBase = Pde & PAGE_MASK;    if (Pde & (1ULL << 7)) return STATUS_SUCCESS; // 2MB大页,终止    // 3. 读PDPTE → PDPT基址    ULONG64 VaPdpt = (SELF_MAP_IDX << 39) | (I4 << 30) | (I3 * ENTRY_SIZE);    Pdte = *(PULONG64)VaPdpt;    if (!Pdte & 1) return STATUS_PAGE_FAULT_ERROR;    *pPdptBase = Pdte & PAGE_MASK;    if (Pdte & (1ULL << 7)) return STATUS_SUCCESS; // 1GB大页,终止    // 4. 读PML4E → PML4基址 = CR3    ULONG64 VaPml4 = (SELF_MAP_IDX << 39) | (I4 * ENTRY_SIZE);    Pml4e = *(PULONG64)VaPml4;    if (!Pml4e & 1) return STATUS_PAGE_FAULT_ERROR;    *pCr3 = Pml4e & PAGE_MASK;    return STATUS_SUCCESS; } ## 五、游戏逆向/驱动 典型落地场景 1. 内核内存读写优化(替代 ReadProcessMemory) 传统方式:每次读写都手动四级VA→PA; 优化后:用自映射直接读取PTE,拿到物理页基址,直读物理内存,速度更快、绕开用户态API拦截。 2. 突破页面权限保护 游戏把代码段设为 XD=1(不可执行)、RW=0(只读); 利用自映射定位 PTE,直接修改页表标志位,解除限制,实现DLL注入、代码补丁。 3. 对抗内存隐藏(反检测外挂) 外挂常用「页表断链、隐藏物理页」躲避扫描; 通过逆推CR3+遍历全页表,溯源所有物理页,找出被隐藏的外挂模块。 4. BYOVD 漏洞分析(你研究的方向) 漏洞驱动常出现「地址解析错误、页表篡改」; 通过逆推页表基址,定位漏洞破坏了哪一级页表、篡改了哪个表项,分析漏洞成因与利用方式。 5. DXGI 截屏排错 截屏黑屏/花屏:大概率是帧缓冲页表 Present=0 或权限异常; 用自映射读取PTE,检查页表状态,快速定位问题。 ## 六、常见踩坑点(必看) 1. 自映射仅内核可用 用户态尝试访问自映射高地址 → 直接崩溃,所有页表操作必须放在驱动中。 2. 忽略大页(PS位) Unity/UE 游戏大量使用2MB/1GB大页,代码不判断PS位,会继续向下遍历,导致内存越界、蓝屏。 3. 非Canonical虚拟地址 x64虚拟地址高16位必须和第47位符号扩展一致,非法地址无法通过页表转换,自映射也失效。 4. 页表项 Present=0 页面未映射/被交换到磁盘,读取PTE会失败,逆推流程中断。 5. 自定义内核修改自映射索引 极少数修改版系统会改动 0x1FF 自映射索引,原版 Windows 全部固定为 0x1FF。 ## 总结 1. 页表自映射:Windows x64 内核内置的快捷机制,用 0x1FF 固定索引形成闭环,虚拟地址直接访问页表,简化页表操作代码; 2. 页表逆推:正向转换的反向逻辑,从地址/表项逐层回溯,拿到各级页表基址与 CR3,用于溯源、排错、漏洞分析; 3. 两者结合,是 x64游戏内核驱动、反作弊、外挂、漏洞分析 的底层基石,也是你从“会调用API”进阶到“手写底层逻辑”的关键。


二、Windows内核系列:用户层代码实现 内核下申请内存

# Windows 内核知识点:用户层代码实现「内核态申请内存」

本节纯知识点讲解,不涉及代码,先把**底层原理、权限隔离、内存分类、API 体系、调用方案、约束规则、踩坑点**全部梳理清楚。结合你之前学的 `x64 四级分页、页表、Ring 权限` 知识做联动,同时区分**正规驱动开发**和**漏洞/BYOVD 提权**两条路线,适配你游戏驱动、逆向、内核对抗的技术方向。


---


## 一、整体概述

### 核心前提

Windows 采用 **CPU 特权级(Ring)** 隔离机制:

1. 应用程序、普通 DLL 运行在 **Ring3(用户态)**,权限受限;

2. 操作系统内核、驱动程序运行在 **Ring0(内核态)**,拥有最高硬件/内存/寄存器操作权限。


**关键结论**:

✅ **Ring3 用户层代码 无法直接调用内核内存申请函数、无法直接访问内核地址空间**。

想要让「用户层程序」完成「在内核态申请内存」,本质是实现 **Ring3 → Ring0 跨权限通信**,由内核层真正执行内存分配逻辑,再将结果回传给用户层。


本节围绕三件事展开:

1. 内核态本身有哪些内存类型、原生申请/释放 API;

2. 用户层通过哪些技术路径触发内核执行内存申请;

3. 配套规则、内存特性、上下文约束、常见错误。


---


## 二、前置核心基础概念

### 1. CPU 特权级(Ring0 / Ring3)

Windows x86/x64 仅使用两级特权环,是整个隔离机制的根基:

- **Ring0(内核态)**

  最高权限,可读写物理内存、操作 `CR3`/页表、执行特权指令、调用所有内核 API、访问**全量虚拟地址空间**。

  载体:Windows 内核 `ntoskrnl.exe`、各类驱动 `.sys`。

- **Ring3(用户态)**

  最低权限,被系统严格限制:

  1. 禁止执行特权指令、直接操作硬件/页表;

  2. 默认无法访问**内核虚拟地址空间**,强行访问直接触发蓝屏/内存访问异常;

  3. 只能调用系统封装的用户态 API,无法直接调用内核导出函数。

  载体:EXE 程序、普通 DLL。


跨环切换:由 CPU 硬件 + Windows 系统调用门、中断、IOCTL 完成,**用户层无法手动切换 Ring**。


### 2. Windows x64 虚拟地址空间划分(联动之前分页知识)

结合你学的 x64 64 位虚拟地址结构,Windows 将 64 位地址空间一分为二:

1. **低半地址空间(0 ~ 0x00007FFFFFFFFFFF):用户地址空间**

   - 每个进程**独立拥有**,进程切换时 `CR3` 切换,地址映射完全隔离;

   - Ring3 程序默认只能访问这部分内存;

   - 内存可被系统交换到页面文件(磁盘)。


2. **高半地址空间(0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF):内核地址空间**

   - **全局共享**:系统内所有进程、驱动共用同一份内核页表映射,所有进程看到的内核地址内容完全一致;

   - 只有 Ring0 代码可正常读写;

   - 我们所说的「内核申请内存」,全部分配在这片高地址区间。


> 联动分页知识:

> 进程切换时,`CR3` 切换只会刷新**用户空间页表**,内核空间对应的页表全局不变,这也是内核内存全进程共享的原因。


### 3. 内核内存核心分类(最重要知识点,决定用哪个 API)

内核内存分为两大类,**用途、特性、使用场景完全不同**,是内核开发第一必学区分点,所有内存申请都基于这两类划分。


#### (1)分页池内存 Paged Pool

- 内存特性:

  1. 允许 Windows 内存管理器**交换到磁盘页面文件**;

  2. 运行过程中可能触发**缺页异常 #PF**(联动之前页表 `Present=0`);

  3. 物理内存紧张时,会被临时换出物理内存。

- 使用限制:

  ❌ **禁止在中断、DPC、时钟回调、硬件中断上下文使用**(这类上下文不允许分页、不允许等待);

  ✅ 仅可在**普通线程上下文**使用。

- 适用场景:存放普通全局数据、日志、配置、临时缓冲区、非实时结构体。


#### (2)非分页池内存 NonPaged Pool

- 内存特性:

  1. **永久驻留在物理内存中**,永远不会被交换到磁盘;

  2. 页表项 `Present 位恒为 1`,不会触发缺页异常;

  3. 访问速度稳定,无页面交换开销。

- 使用限制:

  ✅ **全上下文通用**:普通线程、中断、DPC、硬件回调、钩子函数都能使用;

  ❌ 系统非分页池大小有限,不可无限制申请,滥用会耗尽系统资源。

- 细分变种(现代 Windows 强制推荐):

  - `NonPagedPoolNx`:**不可执行内存**(NX / XD 位开启,联动之前 PTE 标志位),禁止在该内存中执行代码,系统安全防护默认要求,现在主流驱动必须使用;

  - 传统 `NonPagedPool`:可执行内存,新版 Windows 已严格限制,恶意程序常用来存放 Shellcode,正规开发不再使用。

- 适用场景:驱动全局变量、钩子结构体、硬件交互缓冲区、游戏驱动实时数据、中断回调数据(你做游戏内核辅助、DX 驱动、BYOVD 提权时**绝大多数场景都用非分页池**)。


#### (3)拓展:连续物理内存(独立分类)

不属于池内存,是单独的内核内存类型:

- 特性:分配**物理地址连续**的内存(虚拟地址不一定连续);

- 用途:硬件 DMA 传输、显卡/采集卡、外设驱动(你做 DXGI 截屏、硬件采集会用到);

- 特点:分配成功率低、资源稀缺,仅硬件相关场景使用。


---


## 三、内核态原生内存申请/释放 API(Ring0 原生接口)

先搞懂「内核本身用什么函数申请内存」,用户层最终都是间接调用这些内核函数。基于 Windows 标准内核接口,区分**新旧版本、池类型、释放规则**。


### 1. 标准池内存 API(分页/非分页池通用)

Windows 分为**旧版兼容 API** 和 **Win10 及以上新版推荐 API**,企业/现代驱动优先用新版。


#### (1)新版推荐:`ExAllocatePool2`(Win10 1903+ 强制推荐)

- 作用:分配分页池 / 非分页池内存,原生支持 NX 不可执行属性,安全性更高;

- 核心参数:

  1. `PoolType`:内存类型(`PagedPool` / `NonPagedPoolNx` 等);

  2. `NumberOfBytes`:需要申请的字节大小;

  3. `Tag`:内存标签(4 字节自定义标识,用于 PoolMon、WinDbg 排查内存泄露,强制要求);

- 返回值:成功返回**内核虚拟地址**(高地址),失败返回 `NULL`。


#### (2)旧版兼容:`ExAllocatePool`(Win7/Win8 兼容,不推荐新开发)

- 早期内核内存分配函数,功能与新版一致,但默认不强制 NX,安全性差;

- 新系统会有安全告警,仅用于老驱动兼容。


#### (3)统一释放函数:`ExFreePool`

- 所有 `ExAllocatePool` / `ExAllocatePool2` 申请的内存,**必须用该函数释放**;

- 强制规则:**谁申请、谁释放**,跨驱动/跨上下文释放极易蓝屏;

- 典型问题:只申请不释放 → **内核池内存泄露**,最终导致系统内存耗尽、卡顿、蓝屏。


### 2. 连续物理内存 API(硬件/DMA 专用)

```

MmAllocateContiguousMemory / MmFreeContiguousMemory

```

专门分配物理连续内存,仅硬件驱动、视频采集、显卡相关场景使用,普通驱动/游戏辅助几乎不用。


### 3. 补充规则

1. 内核内存没有「堆/栈」概念,统一用**池(Pool)**管理;

2. 申请的内存默认无初始化,内存是脏数据,需要手动置零;

3. 内存标签(Tag)是内核调试标配,所有正规驱动必须填写。


---


## 四、核心:用户层(Ring3) 实现「内核态申请内存」的两大技术方案

这是本节**核心重点**。根据是否使用自定义驱动,分为**正规工业方案(驱动+IOCTL)** 和 **非正规提权方案(BYOVD/本地漏洞)**,两种路线原理、流程、用途完全分开。


### 方案一:正规方案 —— 自定义内核驱动(.sys) + IOCTL 通信(主流商用/驱动开发)

这是 Windows 官方支持、工业界、正规驱动、游戏驱动开发的**标准实现方式**,也是学习内核开发的主流路线。


#### 整体架构(三层链路)

```

用户层 EXE/DLL (Ring3) 

    ↓ 跨环通信(CreateFile + DeviceIoControl)

自定义驱动 .sys (Ring0)

    ↓ 调用内核原生 API(ExAllocatePool2 等)

内核内存管理器 → 分配内核内存

```


#### 分步流程详解

1. **驱动端(Ring0)提前部署**

   1. 驱动加载到系统,创建**设备对象 + 符号链接**(让用户层可以找到驱动);

   2. 驱动注册 `IRP_MJ_DEVICE_CONTROL` 回调函数(专门处理用户层下发的 IOCTL 指令);

   3. 预先定义一组 **IOCTL 控制码**:区分「申请内存」「释放内存」「读写内核内存」等不同指令。


2. **用户层(Ring3)执行逻辑**

   1. 使用 `CreateFileA/W` 打开驱动暴露的符号链接,获取驱动设备句柄;

   2. 构造输入缓冲区:把「申请大小、内存类型(分页/非分页)、内存标签」等参数传给驱动;

   3. 调用 `DeviceIoControl` 下发 IOCTL 指令 + 参数,触发驱动回调;

   4. 驱动收到指令后,在 Ring0 上下文调用 `ExAllocatePool2` 完成内存申请;

   5. 驱动将**内核虚拟地址、申请结果**写入输出缓冲区,回传给用户层;

   6. 用户层解析输出缓冲区,拿到内核内存地址;

   7. 不再使用时,用户层再次通过 IOCTL 通知驱动,调用 `ExFreePool` 释放内存。


#### 特点 & 适用场景

✅ 合法合规、稳定、系统兼容性好、可签名正常加载;

✅ 标准驱动开发流程,适用于:游戏正规辅助驱动、硬件驱动、系统工具、调试工具;

❌ 需要编写、编译、加载驱动,64 位系统默认开启驱动强制签名,测试需要关闭签名/申请数字证书。


---


### 方案二:非正规方案 —— Ring3 提权至 Ring0 + 直接调用内核 API(BYOVD/漏洞路线)

对应你之前学习的 **BYOVD(Bring Your Own Vulnerable Driver,利用漏洞驱动)**、本地提权漏洞,属于**安全研究、逆向、外挂、漏洞分析**路线,非正规开发。


#### 核心原理

1. Windows 存在大量第三方旧驱动、外设驱动、工具驱动,本身存在**越界读写、任意内存读写、提权漏洞**;

2. 用户层 Ring3 程序利用漏洞驱动,**临时将当前线程提升到 Ring0 内核权限**;

3. 提权成功后,当前代码运行在内核上下文,可以**直接调用内核导出函数**(`ExAllocatePool2` 等)申请内核内存;

4. 操作完成后恢复权限。


#### 细分两种实现形式

1. **BYOVD 利用(最常见)**

   加载系统中已存在的漏洞驱动 → 利用漏洞进入 Ring0 → 直接调用内核内存 API;

   特点:无需自己写驱动,依赖现有漏洞驱动,是目前游戏逆向、内核对抗最常用的提权方式。


2. **本地内核漏洞提权**

   利用 Windows 系统本身的内核漏洞,从 Ring3 直接提权到 Ring0,无第三方驱动依赖;

   特点:依赖系统版本,漏洞会被微软补丁修复,稳定性差。


#### 特点 & 适用场景

✅ 无需自行开发驱动,绕过驱动签名限制;

❌ 属于漏洞利用行为,存在法律与安全风险,系统补丁可能直接失效;

✅ 主要用于:漏洞研究、BYOVD 分析、游戏外挂内核功能、临时内核调试。


---


### 补充:无驱动简易方案(纯系统原生 API,仅限「用户/内核共享内存」)

如果不需要**纯内核池内存**,仅需要「用户层和内核层共享一块内存」,可以使用 Windows **内存映射文件(Section/内存节)**:

1. 用户层创建命名内存映射;

2. 内核驱动/系统组件打开同一份命名映射;

3. 双方均可读写同一块内存。

局限:不属于严格意义上的「内核态单独申请池内存」,多用于数据交互,不适合驱动全局数据、钩子等场景。


---


## 五、底层核心规则 & 联动过往知识点

结合你之前学的 **x64 分页、页表、上下文、页表标志位**,梳理内核内存的硬性约束,这是开发中蓝屏、崩溃的主要原因。


### 1. 内存类型与分页机制联动(重点)

1. **分页池内存**

   页面可被换出磁盘,页表项 `Present=0` 是常态,访问会触发缺页异常;

   中断/DPC 上下文**禁止使用**(中断不允许页面交换、等待)。


2. **非分页池内存**

   页面永久驻留物理内存,`Present=1` 恒成立,不会触发缺页;

   所有上下文通用,游戏驱动、钩子、中断回调**优先使用**。


3. **NX 不可执行属性**

   `NonPagedPoolNx` 会将页表项 `XD 位(不可执行位)` 置 1,防止在内存中运行恶意代码,现代 Windows 强制要求。


### 2. 线程上下文规则(内核开发第一大坑)

- **普通线程上下文**:可以使用分页池、非分页池,允许缺页、等待;

- **中断 / DPC / 定时器回调上下文**:**只能使用非分页池**,一旦使用分页池,100% 概率蓝屏;

- 补充:IOCTL 触发的驱动代码,默认运行在**发起调用的用户线程上下文**。


### 3. 内核地址空间共享规则

内核内存地址属于全局高地址,**系统所有进程都能看到同一份内容**:

- 进程 A 在内核申请的内存,进程 B、游戏进程、调试器都可以直接访问;

- 这也是内核钩子、全局数据可以作用于全进程的原因。


### 4. 内存生命周期规则

1. 内核内存**不会随用户层进程退出自动释放**;

   用户 EXE 关闭后,内核申请的内存依然驻留,必须手动调用释放函数,否则永久泄露;

2. 禁止重复释放、释放已释放的内核内存 → 立刻蓝屏;

3. 池内存有内存对齐规则,内核 API 会自动处理,无需手动对齐。


### 5. 64 位系统专属约束

1. x64 系统强制驱动签名,正规 `.sys` 驱动必须签名才能加载;

2. 强制默认开启 NX 防护,可执行内核内存(旧 NonPagedPool)被严格限制;

3. 内核地址全部为高地址 Canonical 格式(联动 x64 地址规范)。


---


## 六、典型应用场景(贴合你的技术方向)

结合你做的**游戏驱动、内核辅助、DXGI 截屏、BYOVD、逆向分析**,对应知识点落地:

1. **游戏内核 Aimbot / 内存读写驱动**

   驱动全局结构体、内存缓存、钩子上下文 → 使用 **NonPagedPoolNx 非分页不可执行内存**。

2. **DXGI 高速截屏 / 视频采集**

   帧缓冲区、硬件交互内存 → 部分场景使用 **连续物理内存**。

3. **BYOVD 漏洞分析 & 提权利用**

   提权到 Ring0 后,临时存放 Shellcode、漏洞数据、临时缓冲区 → 内核池内存。

4. **游戏页表遍历 / 内存隐藏检测**

   页表缓存、遍历状态结构体 → 非分页池内存(遍历逻辑常在高优先级上下文)。

5. **内核调试、逆向工具**

   调试断点、日志缓冲区、内存快照 → 分页池/非分页池根据上下文选择。


---


## 七、高频踩坑点(开发必避,90% 蓝屏源于此)

1. **内存类型误用**

   在中断/DPC 中使用分页池内存 → 直接蓝屏。

2. **内存泄露**

   只调用 `ExAllocatePool2` 申请,不调用 `ExFreePool` 释放 → 系统内存逐步耗尽。

3. **跨上下文/跨驱动释放内存**

   A 驱动申请的内存,由 B 驱动释放 → 内存链表损坏,蓝屏。

4. **忽略 NX 防护**

   强行使用可执行非分页池,新版 Windows 直接拒绝分配或触发安全拦截。

5. **IOCTL 缓冲区溢出**

   用户层传递的输入/输出缓冲区大小不匹配驱动预期 → 内核溢出、蓝屏、提权漏洞。

6. **驱动未处理空指针**

   内存申请失败返回 `NULL`,后续直接解引用 → 访问空指针蓝屏。

7. **64/32 位类型不兼容**

   指针、地址类型混用 32 位/64 位变量 → 地址截断、内存访问错误。


---


## 八、辅助调试工具(查看内核内存)

配合知识点做调试验证:

1. **PoolMon**:系统自带工具,查看系统分页池/非分页池占用、根据 Tag 定位自己申请的内核内存、排查内存泄露;

2. **WinDbg**:内核调试命令

   - `!pool`:查看内核池内存详情;

   - `dt`:查看内核内存结构体;

   - 结合之前的 `!pte`:查看内核内存对应的页表项、Present/XD 标志位;

3. **Process Explorer**:查看系统整体内核内存占用。


---


## 九、知识点总结

1. 权限隔离是根源:Ring3 用户层**无法直接调用内核内存 API**,必须跨环通信;

2. 内核内存分两大核心类型:**分页池(可换页)、非分页池(常驻物理内存,驱动主力)**;

3. 内核原生分配函数:`ExAllocatePool2`(新版推荐),释放统一用 `ExFreePool`;

4. 两大实现路线:

   - 正规:自定义 `.sys` 驱动 + IOCTL 通信(商用/标准驱动);

   - 非正规:BYOVD/漏洞提权到 Ring0,直接调用内核 API(安全研究/外挂);

5. 强约束:上下文决定内存类型、内核内存全局共享、必须手动释放、严格遵守 NX 安全规则;

6. 全链路联动:整套体系建立在 x64 四级分页、页表标志位、虚拟地址空间划分之上。


---


知识点部分讲解完毕,你接下来可以发送具体需求(比如:写驱动+IOCTL 完整代码、纯 Ring3 BYOVD 提权后申请内存代码、简易示例代码等),我按照你的要求编写对应代码。

代码:



结合前面讲解的知识点,下面分**两套主流实现方案**提供完整可编译代码,基于 **Windows x64 + Visual Studio + WDK(Windows 驱动工具包)**,包含详细注释、编译配置、运行步骤。 # 前置环境准备 ## 1. 编译环境 1. 安装 Visual Studio 2022 + **Windows 11 WDK**(驱动开发必备) 2. 项目统一编译目标:`x64`(64位系统,禁用32位兼容) 3. 测试系统:Windows 10/11 x64 ## 2. 系统前置配置(64位驱动强制签名限制) 测试环境需要关闭驱动强制签名,开启**测试模式**(管理员执行CMD): ```cmd bcdedit /set testsigning on ``` 重启电脑生效;恢复正常模式:`bcdedit /set testsigning off` ## 3. 通用约定 - 通信方式:`IOCTL`(用户层 ↔ 内核驱动 标准跨环通信) - 内存API:使用新版 `ExAllocatePool2`(Win10 1903+ 推荐) - 内存类型:默认使用 `NonPagedPoolNx`(非分页、不可执行,主流驱动标准) - 数据对齐:用户层/驱动层共用结构体强制对齐,避免跨环解析错误 --- # 方案一:标准正规方案(自定义SYS驱动 + IOCTL) 架构:`Ring3 控制台程序` ↔ `Ring0 内核驱动` 流程:用户层下发指令 → 驱动在内核态申请/释放池内存 → 结果回传给用户层。 ## 第一步:定义共用头文件(User & Driver 通用) 新建 `PoolComm.h`,**驱动项目、用户项目都引入此头文件**,保证结构体、IOCTL 码完全一致。 ```c #ifndef POOL_COMM_H #define POOL_COMM_H #include <windows.h> #include <ntdddisk.h> // ===================== 1. 定义IOCTL控制码 ===================== // CTL_CODE(设备类型, 功能码, 传输类型, 访问权限) #define IOCTL_ALLOC_KERNEL_POOL     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_FREE_KERNEL_POOL      CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) // ===================== 2. 内存类型枚举(对应内核PoolType) ===================== typedef enum _KERNEL_POOL_TYPE {    POOL_NON_PAGED_NX,      // 非分页 + 不可执行(推荐)    POOL_PAGED              // 分页池 } KERNEL_POOL_TYPE, *PKERNEL_POOL_TYPE; // ===================== 3. 跨环通信结构体(核心) ===================== // 用户层传入参数 + 驱动返回结果 typedef struct _POOL_IO_DATA {    ULONG64     ulAllocSize;    // 待申请内存大小(字节)    ULONG       PoolType;       // 内存类型 KERNEL_POOL_TYPE    ULONG64     KernelAddr;     // 输出:内核内存虚拟地址    NTSTATUS    Status;         // 输出:执行状态码 } POOL_IO_DATA, *PPOOL_IO_DATA; // 驱动设备符号链接(用户层通过这个名称打开驱动) #define DRIVER_SYMBOL_LINK  L"\\\\.\\KernelPoolDriver" #endif // POOL_COMM_H ``` --- ## 第二步:内核驱动代码(Ring0,.sys 驱动项目) 新建 **Windows Kernel Mode Driver** 项目,替换 `Driver.c` 完整代码。 ```c #include <ntddk.h> #include "PoolComm.h" // 内存标签(4字节,PoolMon工具可监控,自定义即可) #define POOL_TAG        '0meM'     // 倒序存储,实际显示为 Mem0 // 全局变量:设备对象 PDEVICE_OBJECT g_pDeviceObject = NULL; // 函数声明 VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject); NTSTATUS DefaultIrpHandler(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp); NTSTATUS IoControlHandler(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp); // ===================== 驱动入口函数(相当于main) ===================== NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {    NTSTATUS status = STATUS_SUCCESS;    UNICODE_STRING symLinkName;    UNICODE_STRING devName;    DbgPrint("KernelPoolDriver Loaded!\r\n");    // 1. 设置驱动卸载函数    DriverObject->DriverUnload = DriverUnload;    // 2. 初始化设备名、符号链接名    RtlInitUnicodeString(&devName, L"\\Device\\KernelPoolDevice");    RtlInitUnicodeString(&symLinkName, DRIVER_SYMBOL_LINK);    // 3. 创建设备对象    status = IoCreateDevice(        DriverObject,        0,        &devName,        FILE_DEVICE_UNKNOWN,        0,        FALSE,        &g_pDeviceObject    );    if (!NT_SUCCESS(status))    {        DbgPrint("IoCreateDevice Failed! Status: 0x%08X\r\n", status);        return status;    }    // 4. 创建符号链接(用户层可见)    status = IoCreateSymbolicLink(&symLinkName, &devName);    if (!NT_SUCCESS(status))    {        DbgPrint("IoCreateSymbolicLink Failed! Status: 0x%08X\r\n", status);        IoDeleteDevice(g_pDeviceObject);        g_pDeviceObject = NULL;        return status;    }    // 5. 注册默认IRP处理函数    for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)    {        DriverObject->MajorFunction[i] = DefaultIrpHandler;    }    // 专门注册 IOCTL 回调函数(处理用户层指令)    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControlHandler;    return STATUS_SUCCESS; } // ===================== 驱动卸载函数 ===================== VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {    UNICODE_STRING symLinkName;    RtlInitUnicodeString(&symLinkName, DRIVER_SYMBOL_LINK);    DbgPrint("KernelPoolDriver Unloaded!\r\n");    // 删除符号链接 + 设备对象    if (g_pDeviceObject != NULL)    {        IoDeleteSymbolicLink(&symLinkName);        IoDeleteDevice(g_pDeviceObject);        g_pDeviceObject = NULL;    } } // ===================== 默认IRP处理(创建/关闭设备) ===================== NTSTATUS DefaultIrpHandler(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp) {    Irp->IoStatus.Status = STATUS_SUCCESS;    Irp->IoStatus.Information = 0;    IoCompleteRequest(Irp, IO_NO_INCREMENT);    return STATUS_SUCCESS; } // ===================== 核心:IOCTL 指令处理函数(内存申请/释放) ===================== NTSTATUS IoControlHandler(_In_ PDEVICE_OBJECT DeviceObject, _Inout_ PIRP Irp) {    PIO_STACK_LOCATION pIoStack = IoGetCurrentIrpStackLocation(Irp);    PPOOL_IO_DATA pIoData = (PPOOL_IO_DATA)Irp->AssociatedIrp.SystemBuffer;    ULONG inBufSize = pIoStack->Parameters.DeviceIoControl.InputBufferLength;    ULONG outBufSize = pIoStack->Parameters.DeviceIoControl.OutputBufferLength;    NTSTATUS status = STATUS_SUCCESS;    // 校验缓冲区大小,防止溢出    if (inBufSize < sizeof(POOL_IO_DATA) || outBufSize < sizeof(POOL_IO_DATA))    {        status = STATUS_BUFFER_TOO_SMALL;        goto End;    }    // 根据不同IOCTL指令分支处理    switch (pIoStack->Parameters.DeviceIoControl.IoControlCode)    {        // 指令1:申请内核池内存        case IOCTL_ALLOC_KERNEL_POOL:        {            ULONG PoolType = 0;            ULONG64 allocSize = pIoData->ulAllocSize;            POOL_TYPE poolTypeFlag = NonPagedPoolNx;            // 解析用户层传入的内存类型            switch (pIoData->PoolType)            {                case POOL_NON_PAGED_NX:                    poolTypeFlag = NonPagedPoolNx;                    break;                case POOL_PAGED:                    poolTypeFlag = PagedPool;                    break;                default:                    status = STATUS_INVALID_PARAMETER;                    goto End;            }            // 调用新版内核内存分配API:ExAllocatePool2            pIoData->KernelAddr = (ULONG64)ExAllocatePool2(                poolTypeFlag,                allocSize,                POOL_TAG            );            // 判断是否分配成功            if (pIoData->KernelAddr == 0)            {                status = STATUS_INSUFFICIENT_RESOURCES;            }            break;        }        // 指令2:释放内核池内存        case IOCTL_FREE_KERNEL_POOL:        {            PVOID pKernelMem = (PVOID)pIoData->KernelAddr;            if (pKernelMem != NULL)            {                ExFreePool(pKernelMem); // 统一释放函数                pIoData->KernelAddr = 0;            }            break;        }        default:            status = STATUS_INVALID_DEVICE_REQUEST;            break;    } End:    // 回填状态,完成IRP请求    pIoData->Status = status;    Irp->IoStatus.Status = status;    Irp->IoStatus.Information = sizeof(POOL_IO_DATA);    IoCompleteRequest(Irp, IO_NO_INCREMENT);    return status; } ``` ### 驱动项目编译配置(关键) 1. 平台:`x64` 2. 目标类型:`Driver` 3. 目标系统:Windows 10/11 4. 禁用「增量链接」,开启「警告视为错误」可选 --- ## 第三步:用户层代码(Ring3,控制台EXE项目) 新建 **空控制台项目**(x64),引入上面的 `PoolComm.h`,编写 `main.c`。 功能:打开驱动 → 申请内核内存 → 打印内核地址 → 释放内存 → 关闭驱动。 ```c #include <windows.h> #include <stdio.h> #include "PoolComm.h" int main() {    HANDLE hDriver = INVALID_HANDLE_VALUE;    POOL_IO_DATA ioData = { 0 };    DWORD dwRet = 0;    BOOL bRet = FALSE;    printf("===== Ring3 调用内核申请内存 Demo =====\r\n");    // ===================== 1. 打开驱动设备 =====================    hDriver = CreateFileW(        DRIVER_SYMBOL_LINK,        GENERIC_READ | GENERIC_WRITE,        0,        NULL,        OPEN_EXISTING,        0,        NULL    );    if (hDriver == INVALID_HANDLE_VALUE)    {        printf("打开驱动失败! 错误码: %d\r\n", GetLastError());        goto Exit;    }    printf("成功打开驱动设备\r\n");    // ===================== 2. 准备参数:申请 4096 字节 非分页不可执行内存 =====================    ioData.ulAllocSize = 4096;    ioData.PoolType = POOL_NON_PAGED_NX;    ioData.KernelAddr = 0;    ioData.Status = 0;    // ===================== 3. 发送IOCTL:申请内核内存 =====================    bRet = DeviceIoControl(        hDriver,        IOCTL_ALLOC_KERNEL_POOL,        &ioData,        sizeof(POOL_IO_DATA),        &ioData,        sizeof(POOL_IO_DATA),        &dwRet,        NULL    );    if (!bRet || !NT_SUCCESS(ioData.Status))    {        printf("申请内核内存失败! Status: 0x%08X\r\n", ioData.Status);        goto CloseHandle;    }    printf("内核内存申请成功!\r\n");    printf("内核虚拟地址: 0x%016llX\r\n", ioData.KernelAddr);    // ===================== 4. 发送IOCTL:释放内核内存 =====================    bRet = DeviceIoControl(        hDriver,        IOCTL_FREE_KERNEL_POOL,        &ioData,        sizeof(POOL_IO_DATA),        &ioData,        sizeof(POOL_IO_DATA),        &dwRet,        NULL    );    if (!bRet || !NT_SUCCESS(ioData.Status))    {        printf("释放内核内存失败! Status: 0x%08X\r\n", ioData.Status);    }    else    {        printf("内核内存释放成功\r\n");    } CloseHandle:    // 关闭驱动句柄    CloseHandle(hDriver);    hDriver = INVALID_HANDLE_VALUE; Exit:    printf("程序结束\r\n");    system("pause");    return 0; } ``` ### 用户项目编译配置 1. 平台:`x64` 2. 字符集:`Unicode`(默认即可) --- ## 第四步:驱动加载 & 整体运行步骤(管理员CMD执行) 1. 编译驱动,得到 `KernelPoolDriver.sys` 2. 编译用户程序,得到 `Ring3Pool.exe` 3. 加载驱动(CMD 管理员):   ```cmd   :: 创建驱动服务   sc create KernelPoolDriver type= kernel binPath= "C:\你的路径\KernelPoolDriver.sys"   :: 启动驱动   sc start KernelPoolDriver   ``` 4. 运行用户程序 `Ring3Pool.exe`,正常输出:   ```   成功打开驱动设备   内核内存申请成功!   内核虚拟地址: 0xFFFFxxxxxxxxx000   内核内存释放成功   ``` 5. 卸载驱动(测试完成后):   ```cmd   sc stop KernelPoolDriver   sc delete KernelPoolDriver   ``` --- # 方案二:拓展方案(BYOVD 提权后直接调用内核API) 结合你研究的 **BYOVD(漏洞驱动提权)**,该方案**无需自定义驱动**,利用已有漏洞驱动提升至 Ring0,直接调用内核内存函数。 > 说明:BYOVD 依赖具体漏洞驱动,无法写出通用可运行代码,下面提供**标准代码框架 + 调用逻辑**,可对接任意Ring0提权壳。 ## 核心思路 1. Ring3 程序利用漏洞驱动 → 线程提权到 Ring0 2. 在 Ring0 上下文直接调用内核导出函数 `ExAllocatePool2` / `ExFreePool` 3. 操作完成后恢复 Ring3 权限 ## 代码框架(Ring3 主程序 + Ring0 回调) ```c #include <windows.h> #include <stdio.h> // 内核函数指针定义 typedef PVOID(__fastcall* pExAllocatePool2)(POOL_TYPE PoolType, ULONG64 Size, ULONG Tag); typedef VOID(__fastcall* pExFreePool)(PVOID Memory); // 全局函数指针 pExAllocatePool2 g_ExAllocatePool2 = NULL; pExFreePool      g_ExFreePool      = NULL; #define POOL_TAG '0meM' // ===================== Ring0 执行回调(提权后运行在内核态) ===================== VOID Ring0_Work() {    ULONG64 allocSize = 4096;    PVOID pKernelMem = NULL;    // 1. 在内核态申请非分页不可执行内存    pKernelMem = g_ExAllocatePool2(NonPagedPoolNx, allocSize, POOL_TAG);    if (pKernelMem != NULL)    {        printf("[Ring0] 申请内核内存成功, 地址: 0x%016llX\r\n", (ULONG64)pKernelMem);                // 2. 释放内存        g_ExFreePool(pKernelMem);        printf("[Ring0] 内核内存已释放\r\n");    }    else    {        printf("[Ring0] 申请内核内存失败\r\n");    } } int main() {    printf("===== BYOVD 提权调用内核内存API Demo =====\r\n");    // 1. 【关键步骤】通过漏洞驱动/BYOVD 提权到Ring0(第三方提权模块实现)    //    此处替换为你的 BYOVD 提权代码    BOOL bPrivEsc = TRUE;    if (!bPrivEsc)    {        printf("提权失败\r\n");        system("pause");        return 0;    }    // 2. 解析内核导出表,获取 ExAllocatePool2 / ExFreePool 地址    //    实现方式:遍历 ntoskrnl.exe 导出表(内核逆向常规操作)    // g_ExAllocatePool2 = GetKernelProcAddress(L"ExAllocatePool2");    // g_ExFreePool      = GetKernelProcAddress(L"ExFreePool");    // 3. 执行内核内存申请/释放逻辑    Ring0_Work();    // 4. 恢复Ring3权限(提权模块自带功能)    printf("执行完成\r\n");    system("pause");    return 0; } ``` ### 方案特点 - 无需编写、加载自定义驱动,绕过驱动签名; - 游戏外挂、漏洞分析场景常用; - 依赖外部漏洞驱动,系统补丁后可能失效。 --- # 关键补充说明 & 排错(结合之前知识点) ## 1. 内存类型选择(代码中可切换) - `POOL_NON_PAGED_NX`(默认):非分页、不可执行,**中断/DPC/钩子 全上下文可用**,游戏驱动首选; - `POOL_PAGED`:分页池,**禁止在中断上下文使用**,仅普通线程使用。 ## 2. 高频错误 & 解决 1. **CreateFile 打开驱动失败**   - 原因:驱动未启动、符号链接名不一致、未开启测试模式;   - 解决:检查 `sc start` 状态、核对 `DRIVER_SYMBOL_LINK`。 2. **内存申请返回NULL**   - 原因:系统非分页池耗尽、传入大小为0、参数错误;   - 解决:减小申请大小、检查传入参数。 3. **系统蓝屏**   - 原因1:缓冲区溢出(结构体大小不匹配)→ 保证用户/驱动结构体完全一致;   - 原因2:重复释放内存 → 释放后置空指针;   - 原因3:分页池用在非法上下文。 ## 3. 调试工具配合 1. `PoolMon`:输入标签 `Mem0`,监控我们申请的内核池内存,排查内存泄露; 2. WinDbg:内核调试,`!pool` 命令查看池状态,`DbgPrint` 日志可在调试器中查看。 ## 4. 安全与规范提醒 1. 正规商业驱动必须**代码签名**,测试模式仅用于开发调试; 2. 内核内存**必须手动释放**,进程退出不会自动回收,长期运行必然内存泄露; 3. 64位环境严禁混用32位指针,统一使用 `ULONG64` 保存内核地址。




[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 1天前 被BOSC叛忍编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (2)
雪    币: 32036
活跃值: (10418)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
格式太乱了,能否改一下
4小时前
0
雪    币: 261
活跃值: (146)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
huangyalei 格式太乱了,能否改一下
是的,目前只是初版,后面会重新大改的。最近在复习B站关注的逆向up的视频,把视频里面的东西提取一下用自己的话表达出来,目前在看“古典文学社”的视频
4小时前
0
游客
登录 | 注册 方可回帖
返回