在前几篇文章中LyShark通过多种方式实现了驱动程序与应用层之间的通信,这其中就包括了通过运用SystemBuf缓冲区通信,运用ReadFile读写通信,运用PIPE管道通信,以及运用ASYNC反向通信,这些通信方式在应对一收一发模式的时候效率极高,但往往我们需要实现一次性吐出多种数据,例如ARK工具中当我们枚举内核模块时,往往应用层例程中可以返回几条甚至是几十条结果,如下案例所示,这对于开发一款ARK反内核工具是必须要有的功能。

其实,实现这类功能可以从两个方面入手,但不论使用哪一种方式本质上都是预留一段缓冲区以此来给内核与应用层共享的区域,该区域内可用于交换数据,实现方式有两种要么在应用层分配空间,要么在内核中分配,LyShark先带大家在内核层实现,通过巧妙地运用MDL映射机制来实现通信需求。
MDL内存读写是最常用的一种读写模式,是用于描述物理地址页面的一个结构,简单的官方解释;内存描述符列表 (MDL) 是一个系统定义的结构,通过一系列物理地址描述缓冲区。执行直接I/O的驱动程序从I/O管理器接收一个MDL的指针,并通过MDL读写数据。一些驱动程序在执行直接I/O来满足设备I/O控制请求时也使用MDL。
通过运用MDL的方式对同一块物理内存同时映射到R0和R3,这样我们只需要使用DeviceIoControl向驱动发送一个指针,通过对指针进行读写就可以实现数据的交换,本人在网络上找到了如下两段被转载的烂大街的片段,这两段代码明显是存在缺陷的如果你也在寻找映射方法那么不要被这两段代码坑了,多数人也根本没有能力将其变为可用的,也就只能转载,不知道哪个大哥挖的坑。
用户态进程分配空间,内核态去映射。- // 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);
复制代码 内核态分配空间,用户态进程去映射。- 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);
复制代码 如上的代码看看就好摘出来只是要提醒大家这个是无法使用的,如下将进入本篇文章的正题。
以内核中开辟空间为例,首先在代码中要做的就是定义一段非分页内存#define FILE_DEVICE_EXTENSION 4096这段区域用于给全局变量使用,其次我们需要传输结构体那么结构体中的成员就要事先定义好,例如此处使用StructAll来定义结构结构体成员变量如下所示,通过使用static将结构体定义为静态,预先空出1024的内存空间并初始化为0,当然了这种方式是存在弊端的,例如最大只支持1024个结构如果超过了则可能会溢出,当然最好的办法是用户空间开辟,在下次章节中再介绍。- // -------------------------------------------------
- // MDL数据传递变量
- // -------------------------------------------------
- // 保存一段非分页内存,用于给全局变量使用
- #define FILE_DEVICE_EXTENSION 4096
- // 定义重复结构(循环传递)
- typedef struct
- {
- char username[256];
- char password[256];
- int count;
- }StructAll;
- static StructAll ptr[1024] = { 0 };
复制代码 为了能够达到输出结构体的效果这里我定义一个ShowProcess用于模拟当前系统内进程数,并自动填充为特定的数据,此处结构体内部count成员则用于标注当前共有多少个结构体,用于在用户层读取判断,当然了这种方式的另一个弊端就是浪费空间,因为每一个结构体中都存在一个被填充为0的整数类型。但如果只是实现功能的话其实也不是那么重要。- // 模拟进程列表赋值测试
- int ShowProcess(int process_count)
- {
- memset(ptr, 0, sizeof(StructAll) * process_count);
- int x = 0;
- for (; x < process_count + 1; x++)
- {
- strcpy_s(ptr[x].username, 256, "lyshark");
- strcpy_s(ptr[x].password, 256, "123456");
- }
- // 设置总共有多少个结构体,并返回结构体个数
- ptr[0].count = x;
- return x;
- }
复制代码 内核态映射: 当定义好如上这些方法时,接下来就是最重要的驱动映射部分了,如下代码所示,首先当用户调用派遣时第一个执行的函数是ShowProcess()它用于获取到当前系统中有多少个进程,接着通过sizeof(MyData) * count计算出当前MyData需要分配的内存池大小并返回给pool_size,调用ExAllocatePool分配一块非分页内核空间,创建IoAllocateMdlMDL映射,将数据MmMapLockedPagesSpecifyCache映射到用户空间,最后将指针pShareMM_User返回给用户态。
- ShowProcess(715) 获取当前进程数,并返回数量
- sizeof(MyData) * count 计算得到结构体长度
- ExAllocatePool(NonPagedPool, pool_size) 分配非分页内存,长度是pool_size
- IoAllocateMdl() 分配MDL空间,并放入内核态
- MmMapLockedPagesSpecifyCache() 将内核态指针映射到用户态
- RtlCopyMemory(pShareMM_SYS, &ptr, sizeof(ptr[0]) * count) 将总进程数放入到count计数变量内
- *(PVOID *)pIrp->AssociatedIrp.SystemBuffer = pShareMM_User 直接将指针传递给用户态
- // 署名权
- // right to sign one's name on a piece of work
- // PowerBy: LyShark
- // Email: me@lyshark.com
- // 获取到当前列表数据
- int count = ShowProcess(715);
- long pool_size = sizeof(MyData) * count;
- DbgPrint("总进程数: %d 分配内存池大小: %d \n", count, pool_size);
- __try
- {
- // 分配内核空间
- PVOID pShareMM_SYS = ExAllocatePool(NonPagedPool, pool_size);
- RtlZeroMemory(pShareMM_SYS, pool_size);
- // 创建MDL
- PMDL pShareMM_MDL = IoAllocateMdl(pShareMM_SYS, pool_size, FALSE, FALSE, NULL);
- MmBuildMdlForNonPagedPool(pShareMM_MDL);
- // 将内核空间映射到用户空间
- PVOID pShareMM_User = MmMapLockedPagesSpecifyCache(pShareMM_MDL, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
- // 拷贝发送数据
- RtlCopyMemory(pShareMM_SYS, &ptr, sizeof(ptr[0]) * count);
- DbgPrint("[lyshark] 用户地址空间: 0x%x \n", pShareMM_User);
- DbgPrint("[lyshark] 内核地址空间: 0x%p \n", pShareMM_SYS);
- // 将字符串指针发送给应用层
- *(PVOID *)pIrp->AssociatedIrp.SystemBuffer = pShareMM_User;
- // ExFreePool(pShareMM_SYS);
- }
- __except (EXCEPTION_EXECUTE_HANDLER)
- {
- break;
- }
- status = STATUS_SUCCESS;
- break;
复制代码 用户态读取数据: 与内核层一致,用户层同样需要定义StructAll结构体用于接收内核中返回过来的结构,而重要的代码则是接收部分,通过IoControl发送控制码,并得到ptr内存指针,此处区域就是内核态分配过的指针,用户只需要通过循环的方式依次读出里面的数据即可。
[code]// 署名权// right to sign one's name on a piece of work// PowerBy: LyShark// Email: me@lyshark.com// -------------------------------------------------// MDL数据传递变量// -------------------------------------------------// 定义重复结构(循环传递)typedef struct{ char username[256]; char password[256]; int count;}StructAll;// 直接输出循环结构体StructAll *ptr;// 派遣命令DriveControl.IoControl(IOCTL_IO_MDLStructAll, 0, 0, &ptr, sizeof(PVOID), 0);printf("共享内存地址: %x \n", ptr);long size = ptr[0].count;std::cout = 0; i--) { if (szCurFile == '\\') { szCurFile[i + 1] = '\0'; break; } }}// -------------------------------------------------// MDL数据传递变量// -------------------------------------------------// 定义重复结构(循环传递)typedef struct{ char username[256]; char password[256]; int count;}StructAll;int main(int argc, char *argv[]){ cDrvCtrl DriveControl; // 设置驱动名称 char szSysFile[MAX_PATH] = { 0 }; char szSvcLnkName[] = "WinDDK";; GetAppPath(szSysFile); strcat(szSysFile, "WinDDK.sys"); // 安装并启动驱动 DriveControl.Install(szSysFile, szSvcLnkName, szSvcLnkName); DriveControl.Start(); // 打开驱动的符号链接 DriveControl.Open("\\\\.\\WinDDK"); // 直接输出循环结构体 StructAll *ptr; // 派遣命令 DriveControl.IoControl(IOCTL_IO_MDLStructAll, 0, 0, &ptr, sizeof(PVOID), 0); printf("[LyShark.com] 共享内存地址: %x \n", ptr); long size = ptr[0].count; std::cout |