1、课程目标
- 理解内核内存池的类型和特性
- 掌握内核内存分配和释放
- 学会使用MDL描述用户内存
- 理解内存映射和共享机制
2、名词解释
| 术语 |
解释 |
| PagedPool |
可分页内存池 |
| NonPagedPool |
非分页内存池 |
| MDL |
Memory Descriptor List,内存描述符列表 |
| Section |
内存区对象,用于共享内存 |
| Lookaside List |
快速内存分配缓存 |
| ProbeForRead/Write |
验证用户地址有效性 |
3、使用工具
| 工具 |
用途 |
| WinDbg |
!pool, !poolused命令 |
| Driver Verifier |
内存检测 |
| PoolMon |
池标签监控 |
4、技术原理
4.1、内核内存架构
┌─────────────────────────────────────────────────────────────┐
│ Windows内核内存架构 │
│ │
│ 地址空间 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 0xFFFF8000'00000000 - 0xFFFFFFFF'FFFFFFFF │ │
│ │ 内核空间 (高地址) │ │
│ │ ├── 系统缓存 │ │
│ │ ├── 非分页池 (NonPagedPool) - 常驻内存 │ │
│ │ ├── 分页池 (PagedPool) - 可换出到磁盘 │ │
│ │ ├── 系统PTE │ │
│ │ └── 内核代码和数据 │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 0x00000000'00000000 - 0x00007FFF'FFFFFFFF │ │
│ │ 用户空间 (低地址) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.2、内存池对比
| 特性 |
PagedPool |
NonPagedPool |
| 可换页 |
是 |
否 |
| IRQL要求 |
PASSIVE_LEVEL |
任意 |
| 资源消耗 |
低 |
高 |
| 适用场景 |
普通数据 |
中断/DPC访问 |
5、代码实现
5.1、示例1:基础内存分配
// MemoryBasics.c - 基础内存分配
#include <ntddk.h>
// 分页池分配(适用于PASSIVE_LEVEL)
PVOID AllocatePagedMemory(SIZE_T size) {
PVOID buffer;
// 使用带标签的分配(便于调试和追踪)
buffer = ExAllocatePoolWithTag(PagedPool, size, 'gaPM'); // 'MPag'
if (buffer) {
RtlZeroMemory(buffer, size);
}
return buffer;
}
// 非分页池分配(可在任意IRQL使用)
PVOID AllocateNonPagedMemory(SIZE_T size) {
PVOID buffer;
// Windows 10 2004+推荐使用NonPagedPoolNx
buffer = ExAllocatePoolWithTag(NonPagedPoolNx, size, 'NPMM');
if (buffer) {
RtlZeroMemory(buffer, size);
}
return buffer;
}
// 释放内存
VOID FreeMemory(PVOID buffer, ULONG tag) {
if (buffer) {
ExFreePoolWithTag(buffer, tag);
}
}
// 使用ExAllocatePool2(Windows 10 2004+)
PVOID AllocatePool2(SIZE_T size, POOL_FLAGS flags) {
// 新的Pool分配API,更安全
// POOL_FLAG_NON_PAGED - 非分页
// POOL_FLAG_PAGED - 分页
// POOL_FLAG_UNINITIALIZED - 不初始化
return ExAllocatePool2(flags, size, 'lo2P');
}
// 测试代码
VOID TestMemoryAllocation() {
PVOID pagedMem = NULL;
PVOID nonPagedMem = NULL;
// 分配分页内存
pagedMem = AllocatePagedMemory(4096);
if (pagedMem) {
DbgPrint("[Memory] Paged memory allocated at: 0x%p\n", pagedMem);
strcpy((char*)pagedMem, "Hello from paged pool!");
FreeMemory(pagedMem, 'gaPM');
}
// 分配非分页内存
nonPagedMem = AllocateNonPagedMemory(4096);
if (nonPagedMem) {
DbgPrint("[Memory] NonPaged memory allocated at: 0x%p\n", nonPagedMem);
strcpy((char*)nonPagedMem, "Hello from non-paged pool!");
FreeMemory(nonPagedMem, 'NPMM');
}
}
5.2、示例2:Lookaside List快速分配
// LookasideList.c - 快速内存分配
#include <ntddk.h>
typedef struct _MY_OBJECT {
LIST_ENTRY ListEntry;
ULONG Id;
CHAR Data[256];
} MY_OBJECT, *PMY_OBJECT;
// 非分页Lookaside List
NPAGED_LOOKASIDE_LIST g_ObjectLookaside;
// 分页Lookaside List
PAGED_LOOKASIDE_LIST g_PagedLookaside;
VOID InitializeLookasideLists() {
// 初始化非分页Lookaside
ExInitializeNPagedLookasideList(
&g_ObjectLookaside,
NULL, // 默认分配函数
NULL, // 默认释放函数
0, // 标志
sizeof(MY_OBJECT), // 对象大小
'jbOL', // 池标签
0 // 深度(0=系统决定)
);
// 初始化分页Lookaside
ExInitializePagedLookasideList(
&g_PagedLookaside,
NULL,
NULL,
0,
sizeof(MY_OBJECT),
'gPaL',
0
);
}
PMY_OBJECT AllocateObject() {
PMY_OBJECT obj;
// 从Lookaside快速分配
obj = (PMY_OBJECT)ExAllocateFromNPagedLookasideList(&g_ObjectLookaside);
if (obj) {
RtlZeroMemory(obj, sizeof(MY_OBJECT));
}
return obj;
}
VOID FreeObject(PMY_OBJECT obj) {
if (obj) {
// 返回到Lookaside
ExFreeToNPagedLookasideList(&g_ObjectLookaside, obj);
}
}
VOID CleanupLookasideLists() {
ExDeleteNPagedLookasideList(&g_ObjectLookaside);
ExDeletePagedLookasideList(&g_PagedLookaside);
}
5.3、示例3:MDL内存操作
// MDLOperations.c - MDL内存描述符
#include <ntddk.h>
// 创建MDL描述用户内存
PMDL CreateMdlForUserBuffer(PVOID userBuffer, ULONG length, BOOLEAN write) {
PMDL mdl = NULL;
__try {
// 验证用户地址
if (write) {
ProbeForWrite(userBuffer, length, sizeof(UCHAR));
} else {
ProbeForRead(userBuffer, length, sizeof(UCHAR));
}
// 分配MDL
mdl = IoAllocateMdl(userBuffer, length, FALSE, FALSE, NULL);
if (!mdl) {
return NULL;
}
// 锁定页面到物理内存
MmProbeAndLockPages(mdl, UserMode,
write ? IoWriteAccess : IoReadAccess);
} __except (EXCEPTION_EXECUTE_HANDLER) {
if (mdl) {
IoFreeMdl(mdl);
mdl = NULL;
}
}
return mdl;
}
// 获取系统地址
PVOID GetSystemAddressFromMdl(PMDL mdl) {
PVOID systemAddress;
// 映射到系统地址空间
systemAddress = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
return systemAddress;
}
// 释放MDL
VOID FreeMdl(PMDL mdl) {
if (mdl) {
// 解锁页面
MmUnlockPages(mdl);
// 释放MDL
IoFreeMdl(mdl);
}
}
// 示例:在驱动中访问用户缓冲区
NTSTATUS ProcessUserBuffer(PVOID userBuffer, ULONG length) {
PMDL mdl = NULL;
PVOID kernelBuffer = NULL;
NTSTATUS status = STATUS_SUCCESS;
// 创建MDL
mdl = CreateMdlForUserBuffer(userBuffer, length, FALSE);
if (!mdl) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// 获取内核地址
kernelBuffer = GetSystemAddressFromMdl(mdl);
if (!kernelBuffer) {
FreeMdl(mdl);
return STATUS_INSUFFICIENT_RESOURCES;
}
// 现在可以安全地访问kernelBuffer
DbgPrint("[MDL] User data: %.*s\n", length, (char*)kernelBuffer);
// 清理
FreeMdl(mdl);
return status;
}
5.4、示例4:内核与用户空间内存映射
// MemoryMapping.c - 内存映射
#include <ntddk.h>
typedef struct _SHARED_MEMORY {
HANDLE SectionHandle;
PVOID KernelAddress;
PVOID UserAddress;
SIZE_T Size;
PMDL Mdl;
} SHARED_MEMORY, *PSHARED_MEMORY;
// 创建共享内存
NTSTATUS CreateSharedMemory(PSHARED_MEMORY pShared, SIZE_T size) {
NTSTATUS status;
LARGE_INTEGER sectionSize;
OBJECT_ATTRIBUTES objAttr;
SIZE_T viewSize = 0;
RtlZeroMemory(pShared, sizeof(SHARED_MEMORY));
pShared->Size = size;
// 创建Section对象
sectionSize.QuadPart = size;
InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwCreateSection(
&pShared->SectionHandle,
SECTION_ALL_ACCESS,
&objAttr,
§ionSize,
PAGE_READWRITE,
SEC_COMMIT,
NULL
);
if (!NT_SUCCESS(status)) {
return status;
}
// 映射到内核空间
status = ZwMapViewOfSection(
pShared->SectionHandle,
ZwCurrentProcess(),
&pShared->KernelAddress,
0,
size,
NULL,
&viewSize,
ViewUnmap,
0,
PAGE_READWRITE
);
if (!NT_SUCCESS(status)) {
ZwClose(pShared->SectionHandle);
return status;
}
return STATUS_SUCCESS;
}
// 映射到用户进程
NTSTATUS MapToUserSpace(PSHARED_MEMORY pShared, PEPROCESS Process) {
NTSTATUS status;
KAPC_STATE apcState;
SIZE_T viewSize = 0;
// 附加到目标进程
KeStackAttachProcess(Process, &apcState);
status = ZwMapViewOfSection(
pShared->SectionHandle,
ZwCurrentProcess(),
&pShared->UserAddress,
0,
pShared->Size,
NULL,
&viewSize,
ViewUnmap,
0,
PAGE_READWRITE
);
KeUnstackDetachProcess(&apcState);
return status;
}
// 清理共享内存
VOID FreeSharedMemory(PSHARED_MEMORY pShared) {
if (pShared->KernelAddress) {
ZwUnmapViewOfSection(ZwCurrentProcess(), pShared->KernelAddress);
}
if (pShared->SectionHandle) {
ZwClose(pShared->SectionHandle);
}
}
5.5、示例5:跨进程内存读写
// CrossProcessMemory.c - 跨进程内存操作
#include <ntddk.h>
// 读取目标进程内存
NTSTATUS ReadProcessMemory(
PEPROCESS Process,
PVOID SourceAddress,
PVOID DestBuffer,
SIZE_T Size,
PSIZE_T BytesRead
) {
SIZE_T bytes = 0;
NTSTATUS status;
status = MmCopyVirtualMemory(
Process, // 源进程
SourceAddress, // 源地址
PsGetCurrentProcess(), // 目标进程(当前)
DestBuffer, // 目标地址
Size, // 大小
KernelMode, // 前一模式
&bytes // 实际复制字节数
);
if (BytesRead) {
*BytesRead = bytes;
}
return status;
}
// 写入目标进程内存
NTSTATUS WriteProcessMemory(
PEPROCESS Process,
PVOID DestAddress,
PVOID SourceBuffer,
SIZE_T Size,
PSIZE_T BytesWritten
) {
SIZE_T bytes = 0;
NTSTATUS status;
status = MmCopyVirtualMemory(
PsGetCurrentProcess(), // 源进程(当前)
SourceBuffer, // 源地址
Process, // 目标进程
DestAddress, // 目标地址
Size, // 大小
KernelMode,
&bytes
);
if (BytesWritten) {
*BytesWritten = bytes;
}
return status;
}
// 通过MDL方式访问用户进程内存
NTSTATUS AccessUserMemoryByMdl(
PEPROCESS Process,
PVOID UserAddress,
SIZE_T Size,
BOOLEAN Write
) {
KAPC_STATE apcState;
PMDL mdl = NULL;
PVOID mappedAddress = NULL;
NTSTATUS status = STATUS_SUCCESS;
// 附加到目标进程
KeStackAttachProcess(Process, &apcState);
__try {
// 验证用户地址
if (Write) {
ProbeForWrite(UserAddress, Size, 1);
} else {
ProbeForRead(UserAddress, Size, 1);
}
// 分配MDL
mdl = IoAllocateMdl(UserAddress, (ULONG)Size, FALSE, FALSE, NULL);
if (!mdl) {
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}
// 锁定页面
MmProbeAndLockPages(mdl, UserMode,
Write ? IoModifyAccess : IoReadAccess);
// 映射到系统空间
mappedAddress = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
if (!mappedAddress) {
MmUnlockPages(mdl);
IoFreeMdl(mdl);
status = STATUS_INSUFFICIENT_RESOURCES;
__leave;
}
// 现在可以在任意上下文访问mappedAddress
DbgPrint("[Memory] Mapped user memory to: 0x%p\n", mappedAddress);
// 清理
MmUnlockPages(mdl);
IoFreeMdl(mdl);
} __except (EXCEPTION_EXECUTE_HANDLER) {
status = GetExceptionCode();
}
KeUnstackDetachProcess(&apcState);
return status;
}
6、课后作业
- 实现一个内存池管理器,统计分配释放
- 使用MDL实现驱动与用户程序的数据交换
- 编写跨进程内存读写的完整示例
- 使用PoolMon监控内存池使用情况
7、扩展阅读
- Windows内存管理机制
- MDL详解
- 内核池溢出利用