Windows应用与内核内存共享
我们知道,进程之间如果要进行数据,是可以通过共享内存来实现的。
其原理就是将同一片物理内存,分别进行不同的线性映射,这样就可以得到2个线性地址,而这两个线地址址对应的空间就是物理内存的空间。
这样,如果我们在各自的进程分别读写内存,另一个进程中对应的线性地址空间的数据同时也会变化。
原理如下图:
相同的进程是由于地址空间的隔离,所以进程之间传递数据使用共享内存的方式。
在驱动中,我们进行应用与驱动层是通过函数ReadFile,WriteFile,DeviceIoControl进行应用与驱动层的传递的。但数据之间的拷贝或地址映射对于我们来说是不可见的,由系统自动完成,而我们只需要在创建设备或者设置IOCTL_CODE码时指定相应的方式即可,如BUFFER,DIRECT_IO或NEITHER方式。
但由于每次进行数据传递都需要进行一次系统调用,那么有没有像共享内存的方式不进行系统调用呢?
答案是肯定的。
原理
原理上和共享内存一样,我们只需要对同一片物理地址空间进行不同的线性地址映射即可,即分别进行该进程的应用层地址映射和内核层映射即可。
所以进程的内核层是共享的。
方法如下:
第一步就是先在内存中创建一片地址空间,这个我们可以通过内存分配函数ExAllocatePoolWithTag或ExAllocatePool来实现。这样就有了内核层地址空间。
第二步是对刚创建的内存分配MDL,我们可以通过函数IoAllocateMdl来实现,并使用函数MmBuildMdlForNonPagedPool内存关联。
在Windows内核中,内存的管理是通过MDL来实现的。
第三步使用函数MmMapLockedPagesSpecifyCache对指定的MDL进行线性映射,这里我们可以指定为应用层。
返回的这个地址是应用层的地址,在内核层是不可用的,如果不在进程空间中,会出现蓝屏的。
如果指定为内核,即对同一步物理地址空间进行了不同的内核映射,这样可以对一些只读的内存空间进行读写,这如果说开了就是内核的IAT或者PATCH。
最后,我们可以通过一个DeviceIoControl将创建的地址返回给应用层。这样应用层就可以使用这个地址进行数据读写了。
假如我们创建一个700k的共享内存空间,代码如下:
deviceExtension->nShareMemoryLen = 1024 * 700;
deviceExtension->pKernelShareMemory = ExAllocatePoolWithTag(NonPagedPool, deviceExtension->nShareMemoryLen, 'byte');
...
deviceExtension->pShareMdl = IoAllocateMdl(deviceExtension->pKernelShareMemory, deviceExtension->nShareMemoryLen, FALSE, FALSE, NULL);
...
MmBuildMdlForNonPagedPool(deviceExtension->pShareMdl);
deviceExtension->pUserShareMemory = MmMapLockedPagesSpecifyCache(deviceExtension->pShareMdl, UserMode, MmNonCached, NULL, FALSE, NormalPagePriority);
...
释放
当不再需要进行共享内存时,就需要释放。
释放时只需要将新创建的MDL和内存空间释放即可。
IoFreeMdl(deviceExtension->pShareMdl);
ExFreePoolWithTag(deviceExtension->pKernelShareMemory,'byte')