MDL数据结构

微软的文档里对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.       
  49.     DbgPrint("MDL_TEST: Size=%d/n",pMDL->Size);  
  50.     DbgPrint("MDL_TEST: MdlFlags=0x%04x/n",pMDL->MdlFlags);  
  51.     DbgPrint("MDL_TEST: Process=0x%08x/n",(ULONG)pMDL->Process);  
  52.     DbgPrint("MDL_TEST: MappedSystemVa=0x%08x/n",(ULONG)pMDL->MappedSystemVa);  
  53.     DbgPrint("MDL_TEST: StartVa=0x%08x/n",(ULONG)pMDL->StartVa);  
  54.     DbgPrint("MDL_TEST: ByteCount=%u/n",pMDL->ByteCount);  
  55.     DbgPrint("MDL_TEST: ByteOffset=%u/n",pMDL->ByteOffset);  
  56. //print a few 4-bytes after the MDL structure  
  57.     for(i=0;i<5;i++)  
  58.         DbgPrint("MDL_TEST: p[%d]=0x%08x/n",i,p[i]);  
  59.       
  60. }  

 

执行的结果如下:

  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的行为可以用下面的代码来描述:

  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有没有进行实际的虚拟地址的映射,即将物理页面映射到内核地址空间中。我们用下面的驱动代码来测试这一行为。

  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.       
  14.     DbgPrint("After MmGetSystemAddressForMdlSafe/n");  
  15.     OutputMDL(pMDL);  
  16.     pIrp->IoStatus.Status = STATUS_SUCCESS;  
  17.     pIrp->IoStatus.Information = MmGetMdlByteCount(pMDL);  
  18.     IoCompleteRequest(pIrp,IO_NO_INCREMENT);  
  19.       
  20.     return STATUS_SUCCESS;  
  21. }  

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

  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

posted @ 2013-09-02 07:18  问笑  阅读(1095)  评论(0编辑  收藏  举报