Windows内核安全

4、驱动内存管理

1、课程目标

  1. 理解内核内存池的类型和特性
  2. 掌握内核内存分配和释放
  3. 学会使用MDL描述用户内存
  4. 理解内存映射和共享机制

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,
        &sectionSize,
        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、课后作业

  1. 实现一个内存池管理器,统计分配释放
  2. 使用MDL实现驱动与用户程序的数据交换
  3. 编写跨进程内存读写的完整示例
  4. 使用PoolMon监控内存池使用情况

7、扩展阅读

  • Windows内存管理机制
  • MDL详解
  • 内核池溢出利用