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);
}

相关推荐
我叫Double3 分钟前
blogx添加接口
驱动开发
MyBFuture28 分钟前
C#表格与定时器实战技巧
开发语言·windows·c#·visual studio
小尧嵌入式1 小时前
c++红黑树及B树B+树
开发语言·数据结构·c++·windows·b树·算法·排序算法
爱敲点代码的小哥2 小时前
【无标题】
linux·windows·microsoft
bst@微胖子2 小时前
CrewAI+FastAPI的Pipelines功能实现多CrewAI工作流以及Flows功能实现复杂工作流
服务器·windows·fastapi
wuk9983 小时前
C# Winform实现拼图游戏
windows·microsoft·c#
十五年专注C++开发12 小时前
CMake进阶:vcpkg中OpenSSLConfig.cmake详解
c++·windows·cmake·openssl·跨平台编译
多多*14 小时前
2026年1月3日八股记录
java·开发语言·windows·tcp/ip·mybatis
nnsix15 小时前
Windows 将桌面 路径 从C盘 移动到D盘
windows
何中应18 小时前
windows安装python环境
开发语言·windows·python