Windows驱动中通过MDL实现用户态与核心态共享内存
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Windows驱动中通过MDL实现⽤户态与核⼼态共享内存Windows驱动跑在核⼼态(Kernel mode),驱动的调⽤者跑在⽤户态。
如何使⽤户态进程与核⼼态驱动共享内存呢 ?
我们知道32位Windows中,默认状态下虚拟空间有4G,前2G是每个进程私有的,也就是说在进程切换的时候会变化,后2G是操作系统的,所以是固定的。
既然⽤户态进程和核⼼态驱动在同⼀个进程空间⾥,是不是只要直接传个内存地址过来,就可以访问了?理论上可以但实际上不⾏,因为⽤户态的进程在不断地切换,使驱动运⾏时没法保证前⾯的⽤户态进程是哪个,也就不确定前2G虚拟地址空间的映射情况,那么⽤户态进程传来的地址也许不是合法的。
⽐较常⽤的做法是通过MDL进⾏内存的重映射。
简单地说就是将同⼀块物理内存同时映射到⽤户态空间和核⼼态空间。
具体来说,可以有两种做法:⽤户态进程分配空间,内核态去映射。
另⼀种是内核态分配空间,⽤户态进程去映射。
前者伪码:
// assume uva is a virtual address in user space, uva_size is its size
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID kva = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
// use kva
// …
MmUnlockPages(mdl);
IoFreeMdl(mdl);
*记得在driver unl oad之前把mdl unlock和free掉,否则会BSoD。
后者伪码:
PVOID kva = ExAllocatePoolWithTag(NonPagedPool, 1024, (ULONG)'PMET');
MDL * mdl = IoAllocateMdl(uva, uva_size, FALSE, FALSE, NULL);
ASSERT(mdl);
__try {
MmBuildMdlForNonPagedPool(mdl);
} __except(EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("error code = %d", GetExceptionCode);
}
PVOID uva = MmMapLockedPagesSpecifyCache(mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
*如果kva是分配在nonpagedpool,那这些物理页本⾝就是被lock住的,因此⽤的是MmBuildMdlForNonPagedPool,如果是分配在paged pool⾥的
⽤MmProbeAndLockPages。
除了这种最原始的⽅式,Windows还提供了两种称为DO_BUFFERED_IO 和DO_DIRECT_IO的⽅式,前者中系统⾃动将⽤户态空间内存拷贝到了到核⼼态空间(Associated-Irp.SystemBuffer),后者由系统⾃动⽣成MDL(Irp->MdlAddress)。
其实这两种⽅法本质都是系统帮忙做了上⾯的部分流程,从⽽可以让程序员省了那些操作。
前⾯提到了⼀个关键数据结构MDL(memorydescriptor list ),系统⽤它来描述虚拟空间对应物理内存的layout。
MDL分为两部分:固定长部分和变长部分,固定长部分结构如下:
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
Next:指向下⼀个MDL结构,从⽽构成链表,有时⼀个IRP会包含多个MDL
Size:MDL本⾝的⼤⼩,注意包含了定长部分和变长两部分的size
MdlFlags:属性标记,如所描述的物理页有没有被lock住等
Process:顾名思义,指向该包含该虚拟地址的地址空间的对应进程结构
MappedSystemVa:内核态空间中的对应地址
StartVa:⽤户或者内核地址空间中的虚拟地址,取决于在哪allocate的,该值是页对齐的
ByteCount:MDL所描述的虚拟地址段的⼤⼩,byte为单位
ByteOffset:起始地址的页内偏移,因为MDL所描述的地址段不⼀定是页对齐的
如allocate出来的虚拟地址为0xac004010,则StartVa为0xac004000,ByteOffset为0x10,MmGetMdlVirtualAddress给出StartVa + ByteOffset。
变长部分包含了物理页编号数组,可以⽤
PPFN_NUMBER pfn = MmGetMdlPfnArray(mdl)
来得到,注意⾥⾯只包含了pfn,不包含页内偏移量。
数组的元素个数可以由ADDRESS_AND_SIZE_TO_SPAN_PAGES得到。
jpg 改 rar。