微软的文档里对MDL的描述感觉语焉不详,这两天在找工作的间隙逆向+黑盒测试了一下MmBuildMdlForNonPagedPool,把得到的一些理解描述下来。

一.MDL数据结构

MDL是用来建立一块虚拟地址空间与物理页面之间的映射,结构定义如下:

  1. typedef struct _MDL {
  2. struct _MDL *Next;
  3. CSHORT Size;
  4. CSHORT MdlFlags;
  5. struct _EPROCESS *Process;
  6. PVOID MappedSystemVa;
  7. PVOID StartVa;
  8. ULONG ByteCount;
  9. ULONG ByteOffset;
  10. } 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中的不同

代码如下:

  1. #include "ntddk.h"
  2. #include "wdm.h"
  3. #include "ntdef.h"
  4. #define BUF_LENGTH 10000
  5. static void OutputMDL(PMDL pMDL);
  6. static void Unload( IN PDRIVER_OBJECT pDriverObject);
  7. NTSTATUS DriverEntry( IN PDRIVER_OBJECT  pDriverObject, IN PUNICODE_STRING RegistryPath )
  8. {
  9. PVOID pBuf = NULL;
  10. PMDL pMDL;
  11. //set up unload routing
  12. pDriverObject->DriverUnload = Unload;
  13. //allocate memory from non-paged pool
  14. pBuf = ExAllocatePoolWithTag(NonPagedPool,BUF_LENGTH,(ULONG)DriverEntry);
  15. if(!pBuf) {
  16. DbgPrint("ExAllocatePoolWithTag failed./n");
  17. return STATUS_SUCCESS;
  18. }
  19. DbgPrint("MDL_TEST: pBuf=0x%08x/n",(ULONG)pBuf);
  20. //allocate a MDL
  21. pMDL = IoAllocateMdl(pBuf,BUF_LENGTH,FALSE,FALSE,NULL);
  22. if(!pMDL) {
  23. DbgPrint("IoAllocateMdl failed./n");
  24. ExFreePoolWithTag(pBuf,(ULONG)DriverEntry);
  25. return STATUS_SUCCESS;
  26. }
  27. //print MDL right after IoAllocateMdl
  28. OutputMDL(pMDL);
  29. //
  30. DbgPrint("****************************************/n");
  31. //call MmBuildMdlForNonPagedPool
  32. MmBuildMdlForNonPagedPool(pMDL);
  33. //print MDL after MmBuildMdlForNonPagedPool is called
  34. OutputMDL(pMDL);
  35. //return
  36. IoFreeMdl(pMDL);
  37. ExFreePoolWithTag(pBuf,(ULONG)DriverEntry);
  38. return STATUS_SUCCESS;
  39. }
  40. void Unload( IN PDRIVER_OBJECT pDriverObject)
  41. {
  42. DbgPrint("MDL_TEST: Unloading. 88/n");
  43. }
  44. void OutputMDL(PMDL pMDL)
  45. {
  46. int i;
  47. ULONG * p = (ULONG*)(pMDL+1);
  48. DbgPrint("MDL_TEST: Size=%d/n",pMDL->Size);
  49. DbgPrint("MDL_TEST: MdlFlags=0x%04x/n",pMDL->MdlFlags);
  50. DbgPrint("MDL_TEST: Process=0x%08x/n",(ULONG)pMDL->Process);
  51. DbgPrint("MDL_TEST: MappedSystemVa=0x%08x/n",(ULONG)pMDL->MappedSystemVa);
  52. DbgPrint("MDL_TEST: StartVa=0x%08x/n",(ULONG)pMDL->StartVa);
  53. DbgPrint("MDL_TEST: ByteCount=%u/n",pMDL->ByteCount);
  54. DbgPrint("MDL_TEST: ByteOffset=%u/n",pMDL->ByteOffset);
  55. //print a few 4-bytes after the MDL structure
  56. for(i=0;i<5;i++)
  57. DbgPrint("MDL_TEST: p[%d]=0x%08x/n",i,p[i]);
  58. }

执行的结果如下:

  1. MDL_TEST: pBuf=0xadc92000
  2. MDL_TEST: Size=40
  3. MDL_TEST: MdlFlags=0x0008
  4. MDL_TEST: Process=0x87e85c88
  5. MDL_TEST: MappedSystemVa=0x95fb1cc4
  6. MDL_TEST: StartVa=0xadc92000
  7. MDL_TEST: ByteCount=10000
  8. MDL_TEST: ByteOffset=0
  9. MDL_TEST: p[0]=0x0002d72f
  10. MDL_TEST: p[1]=0x0002e2b0
  11. MDL_TEST: p[2]=0x0007e15a
  12. MDL_TEST: p[3]=0x0007e15b
  13. MDL_TEST: p[4]=0x0007e15c
  14. ****************************************
  15. MDL_TEST: Size=40
  16. MDL_TEST: MdlFlags=0x000c
  17. MDL_TEST: Process=0x00000000
  18. MDL_TEST: MappedSystemVa=0xadc92000
  19. MDL_TEST: StartVa=0xadc92000
  20. MDL_TEST: ByteCount=10000
  21. MDL_TEST: ByteOffset=0
  22. MDL_TEST: p[0]=0x0005bd23
  23. MDL_TEST: p[1]=0x0005bea2
  24. MDL_TEST: p[2]=0x0005bb21
  25. MDL_TEST: p[3]=0x0007e15b
  26. MDL_TEST: p[4]=0x0007e15c

对驱动程序采用Direct I/O方式进行数据读的测试

采用这种方式进行读数据时,I/O
Manager调用MmProbeAndLockPages将ReadFile参数提供的用户空间缓冲区对应的物理页面锁定为不可换出,然后将得到的
MDL放在Irp->MdlAddress里,将IRP传递给相应驱动程序的DispatchRead。根据Walter
Oney在书中的描述,此时I/O Manager的行为可以用下面的代码来描述:

[c-sharp] view plaincopy
  1. KPROCESSOR_MODE mode;   // <== either KernelMode or UserMode
  2. PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp);
  3. MmProbeAndLockPages(mdl, mode,
  4. reading ? IoWriteAccess : IoReadAccess);
  5. <code to send and await IRP>
  6. MmUnlockPages(mdl);
  7. IoFreeMdl(mdl);

这里主要关注的地方是MmProbeAndLockPages有没有进行实际的虚拟地址的映射,即将物理页面映射到内核地址空间中。我们用下面的驱动代码来测试这一行为。

[c-sharp] view plaincopy
  1. NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
  2. {
  3. PVOID pSysAddr;
  4. PMDL pMDL = pIrp->MdlAddress;
  5. DbgPrint("******************DispatchRead******************/n");
  6. DbgPrint("Before MmGetSystemAddressForMdlSafe/n");
  7. OutputMDL(pMDL);
  8. pSysAddr = MmGetSystemAddressForMdlSafe(pMDL,LowPagePriority);
  9. if(!pSysAddr) {
  10. DbgPrint("MmGetSystemAddressForMdlSafe failed./n");
  11. return STATUS_SUCCESS;
  12. }
  13. DbgPrint("After MmGetSystemAddressForMdlSafe/n");
  14. OutputMDL(pMDL);
  15. pIrp->IoStatus.Status = STATUS_SUCCESS;
  16. pIrp->IoStatus.Information = MmGetMdlByteCount(pMDL);
  17. IoCompleteRequest(pIrp,IO_NO_INCREMENT);
  18. return STATUS_SUCCESS;
  19. }

再写一个应用程序来发起一个读操作:

[c-sharp] view plaincopy
  1. void TestMDLDriver()
  2. {
  3. HANDLE hDevice;
  4. BOOL bRet;
  5. DWORD dwRead;
  6. BYTE buf[10000] = {'S','Q','U','I'};
  7. hDevice = CreateFile(_T("////.//MDLTest"),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  8. if(hDevice==INVALID_HANDLE_VALUE)
  9. {
  10. fprintf(stderr,"CreateFile error : %d/n",GetLastError());
  11. return;
  12. }
  13. //issue a read request
  14. bRet = ReadFile(hDevice,buf,sizeof(buf),&dwRead,NULL);
  15. if(!bRet)
  16. {
  17. fprintf(stderr,"ReadFile error : %d/n",GetLastError());
  18. return;
  19. }
  20. printf("Read bytes:%d/n",dwRead);
  21. //
  22. CloseHandle(hDevice);
  23. }

导致的内核输出如下:

  1. 00000009    4.27463436  ******************DispatchRead******************
  2. 00000010    4.27464771  Before MmGetSystemAddressForMdlSafe
  3. 00000011    4.27465439  MDL_TEST: Size=40
  4. 00000012    4.27466011  MDL_TEST: MdlFlags=0x008a
  5. 00000013    4.27466583  MDL_TEST: Process=0x86ca7b58
  6. 00000014    4.27467155  MDL_TEST: MappedSystemVa=0x92b1f000
  7. 00000015    4.27467775  MDL_TEST: StartVa=0x001ad000
  8. 00000016    4.27468348  MDL_TEST: ByteCount=10000
  9. 00000017    4.27468824  MDL_TEST: ByteOffset=1148
  10. 00000018    4.27469397  MDL_TEST: p[0]=0x00064429
  11. 00000019    4.27469969  MDL_TEST: p[1]=0x000619fc
  12. 00000020    4.27470541  MDL_TEST: p[2]=0x000618ee
  13. 00000021    4.27471066  MDL_TEST: p[3]=0x00060749
  14. 00000022    4.27471685  MDL_TEST: p[4]=0x86abca24
  15. 00000023    4.27472448  After MmGetSystemAddressForMdlSafe
  16. 00000024    4.27472973  MDL_TEST: Size=40
  17. 00000025    4.27473545  MDL_TEST: MdlFlags=0x008b
  18. 00000026    4.27474070  MDL_TEST: Process=0x86ca7b58
  19. 00000027    4.27474689  MDL_TEST: MappedSystemVa=0xb01e747c
  20. 00000028    4.27475214  MDL_TEST: StartVa=0x001ad000
  21. 00000029    4.27475786  MDL_TEST: ByteCount=10000
  22. 00000030    4.27476311  MDL_TEST: ByteOffset=1148
  23. 00000031    4.27476835  MDL_TEST: p[0]=0x00064429
  24. 00000032    4.27477455  MDL_TEST: p[1]=0x000619fc
  25. 00000033    4.27477980  MDL_TEST: p[2]=0x000618ee
  26. 00000034    4.27478504  MDL_TEST: p[3]=0x00060749
  27. 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数据结构的更多相关文章

  1. MDL

    1 先是mdl的数据结构. 2 下面根据用法逐步的讲解mdl数据结构的含义:一般用法,先是 IoAllocateMdl :原型为: 最常用的是VirtualAddress和Length.把自己的Non ...

  2. 【windwos 操作系统】关键的Windows内核数据结构一览(下)

    I/O管理器 nt!_IRP IRP表示一个I/O请求包结构体,它用来封装执行一个特定I/O操作所需要的所有参数以及I/O操作的状态.IRP的表现也类似于一个线程独立调用栈因此它可以从一个线程传递到另 ...

  3. IRP 与 派遣函数

    什么是派遣函数: 派遣函数是 WIndows 驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.也就是说,派遣函数是用来处理驱动程序提交过来的 I ...

  4. ring0与ring3通信方式

    修改自: https://blog.csdn.net/wzsy/article/details/54929726 控制码方式详解: https://www.cnblogs.com/lsh123/p/7 ...

  5. 派遣函数IRP

    派遣函数是Windows驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的. 用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IR ...

  6. Windows驱动开发-设备读写方式

    设备读写方式共三种: 方式 Flag 特点 缓冲区方式读写 DO_BUFFERED_IO I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区.而你的驱动程序将使用这个系统缓冲区工作.I/O ...

  7. Windows内核 基本数据结构

    驱动对象: 每个驱动程序都会有唯一的驱动对象与之对应,并且这个驱动对象是在驱动加载时被内核中的对象管理程序所创建的.驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动的一个实例被内核加载,并 ...

  8. MySQL · 特性分析 · MDL 实现分析

    http://mysql.taobao.org/monthly/2015/11/04/ 前言 在MySQL中,DDL是不属于事务范畴的,如果事务和DDL并行执行,操作相关联的表的话,会出现各种意想不到 ...

  9. 初步认知MySQL metadata lock(MDL)

    http://blog.itpub.net/26515977/viewspace-1208250/ 概述 随着5.5.3引入MDL,更多的Query被“Waiting for table metada ...

随机推荐

  1. Delphi东京版FireDAC连接MSSQL2000提示对象名 'SYS.DATABASES' 无效

    在Delphi 10.2.1 东京 版中,FireDAC默认不兼容MSSQL2000,会提示“[FireDAC][Phys][ODBC][Microsoft][ODBC SQL Server Driv ...

  2. 100-Days-Of-ML-Code 评注版(Day 1)

    Day 1_Data PreProcessing(数据预处理) 本文引用自 Day 1_Data PreProcessing, 对其中内容进行了评注与补充说明. 导入数据 dataset = pd.r ...

  3. 每天一个Linux命令之mkdir

    Linux mkdir命令 mkdir [-p] filename 用于创建一个空目录 如果该目录下有相同名称的目录那么会报错 apple@apple-Pro  ~/Documents/java_d ...

  4. Codeforces #123D: 后缀数组+单调栈

    D. String     You are given a string s. Each pair of numbers l and r that fulfill the condition 1 ≤  ...

  5. 用GO把你想说的话写到比特币链上

    比特币除了币转账还有不少好玩的地方,比如把你想说的话写上去,可以当做留念.记录.或者装逼.今天用GO语言写了一个比特币交易构建.交易签名的程序. 生成的交易信息可以在比特币控制台用sendrawtra ...

  6. Mycp补交作业

    Mycp补交作业 代码 import java.io.; import java.lang.; import java.util.Scanner; public class MyCP { public ...

  7. XMAPP 的安装与配置

    1.XMAPP简介 1.1.XAMPP(Apache+MySQL/MariaDB+PHP+Perl)   开头的X代表X-OS,代表可以在任何常见操作系统下使用,包括Windows.Mac.Linux ...

  8. 20155304田宜楠-第三次作业:虚拟机的安装与Linux学习

    安装VirtualBox虚拟机 安装VirtualBox虚拟机 这一步很简单,参考老师给的教程一步步安装,很快就完成了. 2.安装Ubuntu 这一步可是让我吃尽了苦头,我按照老师给的下载地址成功下载 ...

  9. 20155327 2016-2017-3 《Java程序设计》第4周学习总结

    20155327 2016-2017-3 <Java程序设计>第4周学习总结 教材学习内容总结 一. 理解封装.继承.多态的关系 封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方 ...

  10. 假期C语言学习笔记4

    函数 经过三个星期的慕课的学习大致上学会了C的一些基本知识,在经过三个星期的C语言课本实践将书上的例题,课后练习实践挨个做了一遍,终于到了函数这一章.时间过的好快呀. 函数分为库函数和自定义函数:有返 ...