通过MDL读写进程内存
通过MDL读写进程内存
本文总结于lyshark的《Windows内核安全编程技术实践》。
MDL
Windows采用了分页机制来管理内存。考虑一个虚拟地址,要经过逐级查表,才可以得到物理地址。
逻辑上连续的虚拟页,很大概率会被映射到不连续的物理页中。
假设现在要求对一块内存进行高效的操作(比如IO),频繁地进行查表转换操作效率太低,所以可以把需要用到的页表的物理地址,以链表的形式进行存储,这样就避免了反复查表的开销。
MDL的具体结构Windows并未公开,这里只能参考其他人逆向得到的布局:
typedefstruct_MDL{struct_MDL*Next;// 指向下一个 MDL,形成 MDL 链CSHORT Size;// 本 MDL 结构的总大小(字节)CSHORT MdlFlags;// 标志位(见下表)struct_EPROCESS*Process;// 所属进程(若为用户模式缓冲区)PVOID MappedSystemVa;// 映射后的系统空间虚拟地址(若已映射)PVOID StartVa;// 缓冲区起始页的虚拟地址(页对齐)ULONG ByteCount;// 缓冲区总字节数ULONG ByteOffset;// 缓冲区在起始页内的字节偏移}MDL,*PMDL;引用自Microsoft官方文档链接:
MDL 结构是半不透明的。 驱动程序应仅直接访问此结构的 Next 和 MdlFlags 成员。
通过MDL读取进程内存
主要步骤
1. KeStackAttachProcess() // 附加到目标进程 2. IoAllocateMdl() // 创建 MDL 3. MmProbeAndLockPages() // 探测并锁定页面 4. KeUnstackDetachProcess() // 脱离目标进程 5. MmGetSystemAddressForMdlSafe() // 获取系统空间地址 6. RtlCopyMemory() // 读取数据 7. MmUnlockPages() + IoFreeMdl() // 清理资源附加到目标进程
首先我们需要切换当前线程的地址空间到目标进程中,因为IoAllocateMdl需要知道目标虚拟地址在哪个进程上下文中,并且我们还要在目标进程的地址空间中验证地址有效性,确保后续的MmProbeAndLockPages能正确访问目标进程的页表。
创建MDL
PMDL pMdl=IoAllocateMdl((PVOID)data->pTargetAddr,// 目标虚拟地址data->dwSize,// 要读取的大小false,// SecondaryBuffer(通常为false)false,// ChargeQuota(通常为false)nullptr// Irp(可选));创建一个 MDL 来描述目标内存区域。
关键:探测并锁定页面
MmProbeAndLockPages(pMdl,UserMode,IoReadAccess);这是最关键的一步,包含两个操作:
探测(Probe)
- 验证地址有效性:确保地址在用户模式地址空间(0x00000000-0x7FFFFFFF)
- 检查访问权限:验证当前是否有读取权限
- 对齐检查:确保地址和大小符合对齐要求
- 异常处理:如果地址无效,会抛出异常(代码中缺少 try/except)
锁定(Lock)
- 确保页面驻留:将相关页面锁定在物理内存中,防止被换出到磁盘
- 建立物理映射:在 MDL 中记录物理页面信息
- 增加引用计数:防止页面被释放
参数说明:
UserMode:表示地址是用户模式地址
IoReadAccess:指定只读访问权限
脱离目标进程
MDL 已经捕获了物理页面信息,物理页面被锁定在内存中,不再需要目标进程的地址空间上下文。这样一来,我们就可以减少对目标进程的干扰、允许在脱离状态下长时间访问内存、避免保持附加状态可能导致的死锁问题。
获取系统空间地址
PVOID mappedAddress=MmGetSystemAddressForMdlSafe(pMdl,NormalPagePriority);目的:将 MDL 描述的物理页面映射到系统空间。
工作原理:
根据 MDL 中的物理页面信息
创建系统空间的虚拟地址映射
返回一个在内核中可以直接访问的指针
该函数返回的地址在内核空间中,我们可以直接使用RtlCopyMemory等进行访问。
示例代码
#include<ntifs.h>#include<windef.h>structData{DWORD dwPID;PVOID pTargetAddr;PBYTE data;DWORD dwSize;};VOIDDriverUnload(PDRIVER_OBJECT pDriverObj){UNREFERENCED_PARAMETER(pDriverObj);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"Uninstall Driver Successfully!\n");}BOOLMDL_ReadMemory(Data*data){PEPROCESS pEp=nullptr;NTSTATUS status=PsLookupProcessByProcessId((HANDLE)data->dwPID,&pEp);if(!NT_SUCCESS(status)||pEp==nullptr){DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"find process failed!\n");returnFALSE;}// 附加到进程KAPC_STATE stack={0};KeStackAttachProcess(pEp,&stack);// 创建MDLPMDL pMdl=IoAllocateMdl((PVOID)data->pTargetAddr,data->dwSize,false,false,nullptr);if(!pMdl){DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"find process failed!\n");KeUnstackDetachProcess(&stack);ObDereferenceObject(pEp);returnfalse;}// 探测并锁定页面MmProbeAndLockPages(pMdl,UserMode,IoReadAccess);// 取消附加KeUnstackDetachProcess(&stack);// 获取系统空间映射地址PVOID mappedAddress=MmGetSystemAddressForMdlSafe(pMdl,NormalPagePriority);if(!mappedAddress){MmUnlockPages(pMdl);IoFreeMdl(pMdl);ObDereferenceObject(pEp);returnfalse;}// 读取数据RtlCopyMemory(data->data,mappedAddress,data->dwSize);// 清理资源MmUnlockPages(pMdl);IoFreeMdl(pMdl);ObDereferenceObject(pEp);returntrue;}EXTERN_C NTSTATUSDriverEntry(PDRIVER_OBJECT pDriverObj,PUNICODE_STRING pRegPath){UNREFERENCED_PARAMETER(pRegPath);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"Installing Driver...\n");pDriverObj->DriverUnload=DriverUnload;Data data;data.dwPID=6776;data.pTargetAddr=(PVOID)0x00000056ac965000;data.dwSize=20;if(MDL_ReadMemory(&data)){for(size_t i=0;i<data.dwSize/sizeof(DWORD);i++){DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"Readed: %x\n",((PDWORD)data.data)[i]);}}else{DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"Installing Driver...\n");}returnSTATUS_SUCCESS;}