MDL数据结构
微软的文档里对MDL的描述感觉语焉不详,这两天在找工作的间隙逆向+黑盒测试了一下MmBuildMdlForNonPagedPool,把得到的一些理解描述下来。
一.MDL数据结构
MDL是用来建立一块虚拟地址空间与物理页面之间的映射,结构定义如下:
- typedef struct _MDL {
- struct _MDL *Next;
- CSHORT Size;
- CSHORT MdlFlags;
- struct _EPROCESS *Process;
- PVOID MappedSystemVa;
- PVOID StartVa;
- ULONG ByteCount;
- ULONG ByteOffset;
- } MDL, *PMDL;
各field的解释:
Next:MDL可以连接成一个单链表,这在IRP的结构里能找到。具体是做什么用的参考对IRP的描述
Size:一个MDL并不单单包含结构里这些东西,在内存中紧接着一个MDL结构,存着这个MDL对应的各个物理页面编号,由于一个物理页面一定是4KB 对齐的,所以这个编号相当于一个物理页面起始地址的高20位。Size的值减去sizeof(MDL),等于存放编号的区域的大小。比如该MDL需要三个 物理页面来映射虚拟地址空间,则Size-sizeof(MDL)==4*3==12;
MdlFlags:与这个MDL相关的一些标记
Process:如果虚拟地址是某一进程的用户地址空间,那么MDL代表的这块虚拟地址必须是从属于某一个进程,这个成员指向从属进程的结构
MappedSystemVa:该MDL结构对应的物理页面可能被映射到内核地址空间,这个成员代表这个内核地址空间下的虚拟地址。对 MmBuildMdlForNonPagedPool的逆向表明,MappedSystemVa=StartVa+ByteOffset。这是因为这个函 数的输入MDL,其StartVa是由ExAllocatePoolWithTag决定的,所以已经从内核空间到物理页面建立了映 射,MappedSystemVa自然就可以这样算。 可以猜测,如果是调用MmProbeAndLockPages返回,则MappedSystemVa不会与StartVa有这样的对应关系,因为此时对应的物理页面还没有被映射到内核空间。(此处未定,MmProbeAndLockPages是否会到PDE与PTE中建立映射,未知。)
StartVa:虚拟地址空间的首地址,当这块虚拟地址描述的是一个用户进程地址空间的一块时,这个地址从属于某一个进程。
ByteCount:虚拟地址块的大小,字节数
ByteOffset:StartVa+ByteCount等于缓冲区的开始地址
二.对MmBuildMdlForNonPagedPool的黑盒测试
测试的程序主要执行如下步骤:
1.用ExAllocatePoolWithTag在内核地址空间的NonpagedPool分配一块10000自己的区域
2.用上述得到的地址和大小调用IoAllocateMdl,返回一个MDL
3.打印该MDL个成员的值
4.调用MmBuildMdlForNonPagedPool
5.打印MDL各成员的值,比较与步骤3中的不同
代码如下:
- #include "ntddk.h"
- #include "wdm.h"
- #include "ntdef.h"
- #define BUF_LENGTH 10000
- static void OutputMDL(PMDL pMDL);
- static void Unload( IN PDRIVER_OBJECT pDriverObject);
- NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING RegistryPath )
- {
- PVOID pBuf = NULL;
- PMDL pMDL;
- //set up unload routing
- pDriverObject->DriverUnload = Unload;
- //allocate memory from non-paged pool
- pBuf = ExAllocatePoolWithTag(NonPagedPool,BUF_LENGTH,(ULONG)DriverEntry);
- if(!pBuf) {
- DbgPrint("ExAllocatePoolWithTag failed./n");
- return STATUS_SUCCESS;
- }
- DbgPrint("MDL_TEST: pBuf=0x%08x/n",(ULONG)pBuf);
- //allocate a MDL
- pMDL = IoAllocateMdl(pBuf,BUF_LENGTH,FALSE,FALSE,NULL);
- if(!pMDL) {
- DbgPrint("IoAllocateMdl failed./n");
- ExFreePoolWithTag(pBuf,(ULONG)DriverEntry);
- return STATUS_SUCCESS;
- }
- //print MDL right after IoAllocateMdl
- OutputMDL(pMDL);
- //
- DbgPrint("****************************************/n");
- //call MmBuildMdlForNonPagedPool
- MmBuildMdlForNonPagedPool(pMDL);
- //print MDL after MmBuildMdlForNonPagedPool is called
- OutputMDL(pMDL);
- //return
- IoFreeMdl(pMDL);
- ExFreePoolWithTag(pBuf,(ULONG)DriverEntry);
- return STATUS_SUCCESS;
- }
- void Unload( IN PDRIVER_OBJECT pDriverObject)
- {
- DbgPrint("MDL_TEST: Unloading. 88/n");
- }
- void OutputMDL(PMDL pMDL)
- {
- int i;
- ULONG * p = (ULONG*)(pMDL+1);
- DbgPrint("MDL_TEST: Size=%d/n",pMDL->Size);
- DbgPrint("MDL_TEST: MdlFlags=0x%04x/n",pMDL->MdlFlags);
- DbgPrint("MDL_TEST: Process=0x%08x/n",(ULONG)pMDL->Process);
- DbgPrint("MDL_TEST: MappedSystemVa=0x%08x/n",(ULONG)pMDL->MappedSystemVa);
- DbgPrint("MDL_TEST: StartVa=0x%08x/n",(ULONG)pMDL->StartVa);
- DbgPrint("MDL_TEST: ByteCount=%u/n",pMDL->ByteCount);
- DbgPrint("MDL_TEST: ByteOffset=%u/n",pMDL->ByteOffset);
- //print a few 4-bytes after the MDL structure
- for(i=0;i<5;i++)
- DbgPrint("MDL_TEST: p[%d]=0x%08x/n",i,p[i]);
- }
执行的结果如下:
- MDL_TEST: pBuf=0xadc92000
- MDL_TEST: Size=40
- MDL_TEST: MdlFlags=0x0008
- MDL_TEST: Process=0x87e85c88
- MDL_TEST: MappedSystemVa=0x95fb1cc4
- MDL_TEST: StartVa=0xadc92000
- MDL_TEST: ByteCount=10000
- MDL_TEST: ByteOffset=0
- MDL_TEST: p[0]=0x0002d72f
- MDL_TEST: p[1]=0x0002e2b0
- MDL_TEST: p[2]=0x0007e15a
- MDL_TEST: p[3]=0x0007e15b
- MDL_TEST: p[4]=0x0007e15c
- ****************************************
- MDL_TEST: Size=40
- MDL_TEST: MdlFlags=0x000c
- MDL_TEST: Process=0x00000000
- MDL_TEST: MappedSystemVa=0xadc92000
- MDL_TEST: StartVa=0xadc92000
- MDL_TEST: ByteCount=10000
- MDL_TEST: ByteOffset=0
- MDL_TEST: p[0]=0x0005bd23
- MDL_TEST: p[1]=0x0005bea2
- MDL_TEST: p[2]=0x0005bb21
- MDL_TEST: p[3]=0x0007e15b
- MDL_TEST: p[4]=0x0007e15c
对驱动程序采用Direct I/O方式进行数据读的测试
采用这种方式进行读数据时,I/O
Manager调用MmProbeAndLockPages将ReadFile参数提供的用户空间缓冲区对应的物理页面锁定为不可换出,然后将得到的
MDL放在Irp->MdlAddress里,将IRP传递给相应驱动程序的DispatchRead。根据Walter
Oney在书中的描述,此时I/O Manager的行为可以用下面的代码来描述:
- KPROCESSOR_MODE mode; // <== either KernelMode or UserMode
- PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp);
- MmProbeAndLockPages(mdl, mode,
- reading ? IoWriteAccess : IoReadAccess);
- <code to send and await IRP>
- MmUnlockPages(mdl);
- IoFreeMdl(mdl);
这里主要关注的地方是MmProbeAndLockPages有没有进行实际的虚拟地址的映射,即将物理页面映射到内核地址空间中。我们用下面的驱动代码来测试这一行为。
- NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
- {
- PVOID pSysAddr;
- PMDL pMDL = pIrp->MdlAddress;
- DbgPrint("******************DispatchRead******************/n");
- DbgPrint("Before MmGetSystemAddressForMdlSafe/n");
- OutputMDL(pMDL);
- pSysAddr = MmGetSystemAddressForMdlSafe(pMDL,LowPagePriority);
- if(!pSysAddr) {
- DbgPrint("MmGetSystemAddressForMdlSafe failed./n");
- return STATUS_SUCCESS;
- }
- DbgPrint("After MmGetSystemAddressForMdlSafe/n");
- OutputMDL(pMDL);
- pIrp->IoStatus.Status = STATUS_SUCCESS;
- pIrp->IoStatus.Information = MmGetMdlByteCount(pMDL);
- IoCompleteRequest(pIrp,IO_NO_INCREMENT);
- return STATUS_SUCCESS;
- }
再写一个应用程序来发起一个读操作:
- void TestMDLDriver()
- {
- HANDLE hDevice;
- BOOL bRet;
- DWORD dwRead;
- BYTE buf[10000] = {'S','Q','U','I'};
- hDevice = CreateFile(_T("////.//MDLTest"),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
- if(hDevice==INVALID_HANDLE_VALUE)
- {
- fprintf(stderr,"CreateFile error : %d/n",GetLastError());
- return;
- }
- //issue a read request
- bRet = ReadFile(hDevice,buf,sizeof(buf),&dwRead,NULL);
- if(!bRet)
- {
- fprintf(stderr,"ReadFile error : %d/n",GetLastError());
- return;
- }
- printf("Read bytes:%d/n",dwRead);
- //
- CloseHandle(hDevice);
- }
导致的内核输出如下:
- 00000009 4.27463436 ******************DispatchRead******************
- 00000010 4.27464771 Before MmGetSystemAddressForMdlSafe
- 00000011 4.27465439 MDL_TEST: Size=40
- 00000012 4.27466011 MDL_TEST: MdlFlags=0x008a
- 00000013 4.27466583 MDL_TEST: Process=0x86ca7b58
- 00000014 4.27467155 MDL_TEST: MappedSystemVa=0x92b1f000
- 00000015 4.27467775 MDL_TEST: StartVa=0x001ad000
- 00000016 4.27468348 MDL_TEST: ByteCount=10000
- 00000017 4.27468824 MDL_TEST: ByteOffset=1148
- 00000018 4.27469397 MDL_TEST: p[0]=0x00064429
- 00000019 4.27469969 MDL_TEST: p[1]=0x000619fc
- 00000020 4.27470541 MDL_TEST: p[2]=0x000618ee
- 00000021 4.27471066 MDL_TEST: p[3]=0x00060749
- 00000022 4.27471685 MDL_TEST: p[4]=0x86abca24
- 00000023 4.27472448 After MmGetSystemAddressForMdlSafe
- 00000024 4.27472973 MDL_TEST: Size=40
- 00000025 4.27473545 MDL_TEST: MdlFlags=0x008b
- 00000026 4.27474070 MDL_TEST: Process=0x86ca7b58
- 00000027 4.27474689 MDL_TEST: MappedSystemVa=0xb01e747c
- 00000028 4.27475214 MDL_TEST: StartVa=0x001ad000
- 00000029 4.27475786 MDL_TEST: ByteCount=10000
- 00000030 4.27476311 MDL_TEST: ByteOffset=1148
- 00000031 4.27476835 MDL_TEST: p[0]=0x00064429
- 00000032 4.27477455 MDL_TEST: p[1]=0x000619fc
- 00000033 4.27477980 MDL_TEST: p[2]=0x000618ee
- 00000034 4.27478504 MDL_TEST: p[3]=0x00060749
- 00000035 4.27479029 MDL_TEST: p[4]=0x86abca24
此时从VS的调试器中看到,应用程序中buf[10000]的地址为0x001ad47c
从输出可以得到如下结论:
1.MmProbeAndLockPages并不将物理页面映射到内核地址空间,而仅锁定物理页面;MappedSystemVa的变化可以显示这一点
2.buf的地址=StartVa+ByteOffset;
3.MmGetSystemAddressForMdlSafe进行实际的映射操作,并设置MdlFlags的MDL_MAPPED_TO_SYSTEM_VA标志。
4.MmProbeAndLockPages将MdlFlags=MDL_WRITE_OPERATION | MDL_ALLOCATED_FIXED_SIZE | MDL_PAGES_LOCKED
MDL数据结构的更多相关文章
- MDL
1 先是mdl的数据结构. 2 下面根据用法逐步的讲解mdl数据结构的含义:一般用法,先是 IoAllocateMdl :原型为: 最常用的是VirtualAddress和Length.把自己的Non ...
- 【windwos 操作系统】关键的Windows内核数据结构一览(下)
I/O管理器 nt!_IRP IRP表示一个I/O请求包结构体,它用来封装执行一个特定I/O操作所需要的所有参数以及I/O操作的状态.IRP的表现也类似于一个线程独立调用栈因此它可以从一个线程传递到另 ...
- IRP 与 派遣函数
什么是派遣函数: 派遣函数是 WIndows 驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.也就是说,派遣函数是用来处理驱动程序提交过来的 I ...
- ring0与ring3通信方式
修改自: https://blog.csdn.net/wzsy/article/details/54929726 控制码方式详解: https://www.cnblogs.com/lsh123/p/7 ...
- 派遣函数IRP
派遣函数是Windows驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的. 用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IR ...
- Windows驱动开发-设备读写方式
设备读写方式共三种: 方式 Flag 特点 缓冲区方式读写 DO_BUFFERED_IO I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区.而你的驱动程序将使用这个系统缓冲区工作.I/O ...
- Windows内核 基本数据结构
驱动对象: 每个驱动程序都会有唯一的驱动对象与之对应,并且这个驱动对象是在驱动加载时被内核中的对象管理程序所创建的.驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动的一个实例被内核加载,并 ...
- MySQL · 特性分析 · MDL 实现分析
http://mysql.taobao.org/monthly/2015/11/04/ 前言 在MySQL中,DDL是不属于事务范畴的,如果事务和DDL并行执行,操作相关联的表的话,会出现各种意想不到 ...
- 初步认知MySQL metadata lock(MDL)
http://blog.itpub.net/26515977/viewspace-1208250/ 概述 随着5.5.3引入MDL,更多的Query被“Waiting for table metada ...
随机推荐
- 一图看懂hadoop分布式文件存储系统HDFS工作原理
一图看懂hadoop分布式文件存储系统HDFS工作原理
- Linux系统下连接校园网Drcom客户端教程(广东工业大学)
这篇教程写给想要学习Linux系统或者在Linux系统下有需要使用Drcom上网的同学,在我疯狂踩坑,经过n多次的刷机装机实验,体验不同发行版本的linux系统后,终于懂得怎么连接上drcom,想想连 ...
- USB-Blaster驱动安装失败——文件哈希值不在指定目录中
右击此电脑,选择管理,选择设备管理器,更新USB-Blaster驱动出现问题 问题: 文件的哈希值不在指定的目录文件中,如图: 解决办法: Windows键+R→shutdown.exe /r /o ...
- typedef和define一些问题
1. 四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针 ...
- 20155204 2016-2017-2 《Java程序设计》第3周学习总结
20155204 2016-2017-2 <Java程序设计>第3周学习总结 教材学习内容总结 一个原始码中可以有多个类定义,但只能有一个公开类. 留心Scanner对于每一种类型的nex ...
- 20155206 2016-2017-2 《Java程序设计》第十周学习总结
20155206 2016-2017-2 <Java程序设计>第十周学习总结. 教材学习内容总结 教材学习内容总结 Java的网络编程 •网络编程是指编写运行在多个设备(计算机)的程序,这 ...
- 20155215宣言 实验四 Andoid开发基础实验报告
20155215宣言 实验四 Andoid开发基础实验报告 实验要求 1.没有Linux基础的同学建议先学习<Linux基础入门(新版)><Vim编辑器> 课程: 2.完成实验 ...
- 20155231 cho3 课下作业
20155231 cho3 课下作业 4 1 通过输入gcc -S -o main.s main.c 将下面c程序"week0603学号.c"编译成汇编代码 int g(int x ...
- 20155233 《Java程序设计》 第十一周课堂练习总结
20155233 <Java程序设计> 第十一周课堂练习总结 测试题目 1.修改教材P74 一行代码 NineNineTable.java, 让执行结果是个三角形: 提交在IDEA或命令行 ...
- ruby学习笔记(2)-chomp,chop的区别
还没开始系统性的学习Ruby,最近在看metasploit框架的exploit会涉及到Ruby脚本,也就硬着头皮一遍查阅资料一遍做些笔记吧. Ruby字符串中存在chop和chomp的内置函数.我在h ...