介绍
MDL本来是很单纯的一个东西,就是描述物理地址页面的一个结构。而MDL映射,通常见得最多的就是内核通信中,内核访问用户态地址空间或者是给用户态空间返回一个地址供其使用。
然而简单的技术依然可以不平凡的使用,MDL应该算是非常典型的一个例子了,在某些领域被利用的如火如荼。
尽管如此,许多入内核这个“大坑”的新人们,依然对其不甚理解,照猫画虎的居多,深入理解的颇少。而我作为刚才这个“大坑”中爬出来的一员,秉着能拉队友一把是一把的原则,希望三言两语能把这事给说清楚了。若是没说清楚,或说错了,希望大神们跳出来斧正。感谢。
一个结构与三个函数
MDL
MDL是用来Windows用来描述物理地址状态的一个数据结构,其结构如下:
+0x000 Next : Ptr64 _MDL
+0x008 Size : Int2B
+0x00a MdlFlags : Int2B
+0x00c AllocationProcessorNumber : Uint2B
+0x00e Reserved : Uint2B
+0x010 Process : Ptr64 _EPROCESS
+0x018 MappedSystemVa : Ptr64 Void
+0x020 StartVa : Ptr64 Void
+0x028 ByteCount : Uint4B
+0x02c ByteOffset : Uint4B
需要关注的几个成员:
- MappedSystemVa:表示最终映射的系统地址
- StartVA:指定的映射的虚拟地址的页基址
- ByteOffset:指定的虚拟地址相对于所属页的偏移
- ByteCount:指定虚拟地址的Buffer的长度
MDL描述的所有物理页的地址,最终保存在PFN Array中,它位于MDL结构的结尾,即PMDL + 1
处,基本上是这样一个结构MDL|PFN0,PFN1,...PFN N
。
IoAllocateMdl
该函数用来申请一个相应的MDL结构,其函数定义如下:
PMDL IoAllocateMdl(
__drv_aliasesMem PVOID VirtualAddress,
ULONG Length,
BOOLEAN SecondaryBuffer,
BOOLEAN ChargeQuota,
PIRP Irp
);
该函数的作用是申请一个MDL用以映射参数VirtualAddress和Length所描述的Buffer。如果指定了IRP,则会将该MDL插入到该Irp的Mdl chain中,IRP不是本文的关注点,可以略过。
核心:其实就做一件事,就是根据传入的参数VirtualAddress和Length,计算出所需要的页面数量,然后申请对应数量的PFN Array + MDL头部结构,初始化成员。
MmProbeAndLockPages 和 MmProbeAndLockProcessPages
函数MmProbeAndLockProcessPages用于MDL描述的虚拟地址位于其它进程的地址空间的情况,实现和MmProbeAndLockPages一样,只不过是在调用MmProbeAndLockPages前,先KeStackAttachProcess发生地址空间切换。这两个函数定义如下:
void MmProbeAndLockPages(
PMDL MemoryDescriptorList,
KPROCESSOR_MODE AccessMode,
LOCK_OPERATION Operation
);
VOID
MmProbeAndLockProcessPages(
__inout PMDL MemoryDescriptorList,
__in PEPROCESS Process,
__in KPROCESSOR_MODE AccessMode,
__in LOCK_OPERATION Operation
);
- MemoryDescriptorList:目标MDL
- AccessMode:指明你要Probe时的访问模式,
KernelMode或者UserMode
- Operation:指明要操作的类型,
IoReadAccess,IoWriteAccess,IoModifyAccess
等。
一定要注意,这两函数虽然没有返回值,但是其内部在处理错误时,会抛出异常,因此需要加入到__try{}__except
中。
核心:该函数内部逻辑复杂,然而其本质就是在做一件事,将目标VA触发换页动作,发生页面错误处理,使得其PTE内容有效,然后获取对应的PFN值,填入到该MDL的PFN Array中。
该过程会处理到的情况:
- 会对用户态的地址页面进行Probe的动作,若是IoWriteAccess,则还会触发一次写。
- 获取目标地址的PTE地址,然后利用MmAccessFault来进行页面错误处理,更新将PTE状态为valid。
- 若是IoWriteAccess,则判断其PTE是否可写,若不可写,则检测是否有COW标志,若是则重新执行COW的动作。
- 获取PFN值,遍历当前进程的PhysicalVadRoot树来检测该PFN否在当前进程的物理页面中。
- 更新PFN Array。
MmMapLockedPagesSpecifyCache
该函数用来将一个MDL描述的物理页到系统地址或者是用户虚拟地址空间。函数原型如下:
PVOID MmMapLockedPagesSpecifyCache(
PMDL MemoryDescriptorList,
KPROCESSOR_MODE AccessMode,
MEMORY_CACHING_TYPE CacheType,
PVOID RequestedAddress,
ULONG BugCheckOnFailure,
ULONG Priority
);
- MemoryDescriptorList:目标MDL。
- AccessMode:指示这些页映射到哪里,是用户态地址空间还是内核态地址空间。
- CacheType:指明该MDL要使用的Cache类型。默认为MmCached。
- RequestedAddress:只有当AccessMode为UserMode时才有效,指明你想分配到的虚拟地址。
- BugCheckOnFailure:不言而喻,失败否是BugCheck。
核心:该函数的对于内核地址的映射就是从SystemPTE区域维护的空闲PTE链表上摘下对应的PTE项来,将该MDL描述的PFN值填入,返回对应的SystemPTE Adresss。
核心:而要映射到用户态,则会构建一个VadDevicePhysicalMemory类型的VAD,若没有指定RequestedAddress,则从VAD树中动态获取一个有效的地址范围返回,将该MDL描述的PFN值填入到这些虚拟地址的PTE中。最后将该VAD插入到当前进程的VAD树中。其间还会创建一个结构为PMI_PHYSICAL_VIEW的Address Node,用以更新当前进程的Process->PhysicalVadRoot。
代码
见得太多了吧,这还想要?
交流群
QQ群:414792053
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-7-23 15:35
被小丶小编辑
,原因: