28.实现MDL驱动读写-Windows驱动

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

本次游戏没法给

内容参考于:微尘网络安全

上一个内容:27.驱动自动编译和自动签名和自动vmp-Windows驱动

MDL是内存描述符列表,就是说我们在CE和x64dbg中看到的地址是虚拟地址,虚拟地址会对应一块物理内存地址(物理页),一个物理页可以对应多个虚拟地址,这个MDL的作用就是让一个物理页对应多个虚拟地址,如果对新的虚拟地址读或写,改变了物理页,之前的虚拟地址也会被改变

当要对一块内核内存进行修改的时候,需要先为这块内存创建MDL,那么就会建立一块新的虚拟内存空间,与将要修改内存对应的物理控件向映射,我们通过对虚拟地址对应物理内存进行操作,这是MDL修改内存的实现思路

流程

1.调用IoAllocateMdl函数分配一个足够大的MDL结构

2.调用MmBuildMdlForNonPagedPool函数更新MDL对物理内存的描述

3.调用MmMapLockedPages函数将MDL中描述的物理页映射到虚拟内存,并返回映射的虚拟内存地址,通过返回的虚拟内存地址来操作物理页

效果图:

三环代码

0环驱动代码

代码说明:

c++ 复制代码
// 【ReadProcessByMDL函数】- 核心!通过MDL(内存描述符列表)安全读取目标进程内存
// 入参:Irp - 设备控制请求的IRP结构体(用户层与内核层通信的核心载体)
// 作用:接收用户层传入的「目标进程PID、读取地址、读取长度」,通过MDL映射内存+进程附加,实现无权限检查的内存读取
// 核心优势:MDL是内核层安全操作跨进程内存的方式,绕过用户层ReadProcessMemory的权限校验
void ReadProcessByMDL(IRP* Irp) {
	//__debugbreak(); // 调试断点(调试时启用,发布时注释)

	// 步骤1:获取用户层传入的参数缓冲区(SystemBuffer是IRP中存储用户/内核交互数据的区域)
	// buf是64位数组,约定:
	// buf[0] = 目标进程PID(UINT64)
	// buf[1] = 目标进程中要读取的内存地址(UINT64,用户层虚拟地址)
	// buf[2] = 要读取的内存长度(UINT64,字节数)
	UINT64* buf = Irp->AssociatedIrp.SystemBuffer;

	// 调试打印:输出用户层传入的参数(方便WinDbg验证参数是否正确)
	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "IRP_MJ_DEVICE_CONTROL buf[0] = %llx\n", buf[0]); // 目标PID
	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "IRP_MJ_DEVICE_CONTROL buf[1] = %llx\n", buf[1]); // 目标地址
	DbgPrintEx(DPFLTR_IHVDRIVER_ID, 0, "IRP_MJ_DEVICE_CONTROL buf[2] = %llx\n", buf[2]); // 读取长度

	// 步骤2:定义目标进程的EPROCESS指针(内核中描述进程的核心对象)
	PEPROCESS pep = NULL;
	// 根据PID查找目标进程的EPROCESS(核心!后续附加进程必须用EPROCESS)
	// 注意:此处未检查返回值,失败时pep为NULL,会导致后续蓝屏
	PsLookupProcessByProcessId(buf[0], &pep);

	// 步骤3:分配MDL(内存描述符列表)- 内核操作跨进程内存的核心结构体
	// IoAllocateMdl参数说明:
	// 参数1:起始地址 → buf(用户层传入的缓冲区,用于存储读取结果)
	// 参数2:长度 → buf[2](要读取的内存长度)
	// 参数3:是否是分段缓冲区 → FALSE(连续缓冲区)
	// 参数4:是否是高层级MDL → FALSE(基础MDL)
	// 参数5:关联的IRP → NULL(无关联)
	PMDL g_mdl = IoAllocateMdl(buf, buf[2], FALSE, FALSE, NULL);

	// 步骤4:如果MDL分配成功,执行内存读取逻辑
	if (g_mdl) {
		// 构建MDL:将MDL与非分页池内存关联(内核规范,必须调用)
		// 作用:让MDL描述的内存区域被内核锁定,防止分页交换
		MmBuildMdlForNonPagedPool(g_mdl);

		// 将MDL锁定的内存映射到内核地址空间(返回映射后的内核虚拟地址)
		// 参数2:KernelMode(内核模式,映射到内核地址空间,用户层不可见)
		PVOID address = MmMapLockedPages(g_mdl, KernelMode);

		// 步骤5:附加到目标进程的地址空间(核心!跨进程读取内存的关键)
		// KAPC_STATE:存储进程上下文的结构体(附加/分离进程时保存当前上下文)
		KAPC_STATE apc;
		// KeStackAttachProcess:将当前内核线程附加到目标进程的地址空间
		// 效果:当前线程"假装"在目标进程中运行,能直接访问目标进程的虚拟地址
		KeStackAttachProcess(pep, &apc);

		// 步骤6:拷贝目标进程的内存到MDL映射的内核地址
		// RtlCopyMemory(目标地址, 源地址, 长度)
		// 此处:把目标进程buf[1]地址的内存,拷贝length字节到address(MDL映射地址)
		RtlCopyMemory(address, buf[1], buf[2]);

		// 步骤7:从目标进程地址空间分离(必须!否则内核线程一直挂在目标进程,导致进程退出时蓝屏)
		KeUnstackDetachProcess(&apc);

		// 步骤8:将MDL中的数据拷贝回用户层缓冲区(SystemBuffer)
		// 最终:用户层能从SystemBuffer拿到目标进程的内存数据
		RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, address, buf[2]);

		// 步骤9:释放MDL相关资源(内核规范,必须释放,否则内存泄漏)
		MmUnmapLockedPages(address, g_mdl); // 解除MDL内存映射
		IoFreeMdl(g_mdl);                  // 释放MDL结构体

		// 步骤10:设置IRP的完成状态(告诉用户层操作成功)
		Irp->IoStatus.Status = STATUS_SUCCESS; // 操作成功状态
		Irp->IoStatus.Information = buf[2];    // 返回给用户层的字节数(读取的长度)
	}

	// 步骤11:释放EPROCESS引用(内核规范,PsLookupProcessByProcessId会增加引用计数,必须释放)
	// 注意:此处未检查pep是否为NULL,pep=NULL时调用会蓝屏
	ObDereferenceObject(pep);
}

相关推荐
公子小六17 分钟前
基于.NET的Windows窗体编程之WinForms图像控件
windows·microsoft·c#·.net·winforms
狂热开发者1 小时前
用 Typeoff 把会后口述整理成 Markdown 决策记录
人工智能·windows·macos·ios·安卓
何中应1 小时前
Windows7 虚拟机如何传文件
windows·vmware·虚拟机
ofoxcoding1 小时前
Codex Computer Use 完整指南:AI 自动操控 Mac 与 Windows 桌面实战详解
人工智能·windows·macos·ai
阿拉斯攀登3 小时前
SPI 设备驱动开发
linux·驱动开发·嵌入式硬件·linux内核·嵌入式·linux驱动·spi
XUHUOJUN3 小时前
Windows Server 2025 Stretch Cluster 架构详解
windows·microsoft·azure local
zhou135284822673 小时前
Windows 配置 WSL (Ubuntu) 环境完整指南
linux·windows·ubuntu
XUHUOJUN4 小时前
Windows Server 2025 RAC vs Stretch Cluster 技术定位
windows·microsoft·azure local
sg_knight5 小时前
Claude Code 安装指南(macOS / Linux / Windows WSL)
linux·windows·macos·claude·code·anthropic·claude-code
阿拉斯攀登5 小时前
I2C 设备驱动开发
linux·驱动开发·嵌入式硬件·嵌入式·linux驱动·i2c·字符设备驱动