• 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

  • 相关阅读:
    基于Nodejs生态圈的TypeScript+React开发入门教程
    防止 IE 自动跳兼容模式
    C# DateTime 转 JavaScript Date
    自制 Chrome Custom.css 设置网页字体为微软雅黑扩展
    比較C struct 與 C# unsafe struct内存分佈
    C/C++编程GUI库比较
    WIN32控件使用系统样式
    【转载】Layered Window(分层窗体,透明窗体)
    WM_COMMAND和WM_NOTIFY区别[转]
    MFC RichText 滚动条问题
  • 原文地址:https://www.cnblogs.com/sideny/p/3295831.html
Copyright © 2020-2023  润新知