1. 派遣函数


在Windows中,用户态进程的各类IO请求最后都会转化成对应的IRP请求并发送给底层驱动。其流程差不多是这样的,拿WriteFile来举例:

  1. 在用户态调用WriteFile函数
  2. WriteFile会调用NTDLL.dll中的NtWriteFile函数
  3. NtWriteFile会陷入内核调用系统服务NtWriteFile(注意只是相同的名字,但本质完全不同)
  4. NtWriteFile会生成相应的IRP,对应IRP类型为: IRP_MJ_WRITE并将其发送给底层对应驱动
  5. IRP会顺次自顶向下传递给设备栈中的各个设备,直到被处理完成,即调用了IoCompleteRequest内核函数
  6. 调用完后设备栈会向上回卷,如果碰到有完成函数就会调用完成函数(这是另外的内容与本博客无关)
  7. 直到最终会把处理完成的IRP携带的信息返回给用户态的WriteFile

来看一段测试几个通用IRP类型的测试程序:

#include <ntddk.h>typedef struct _DEVICE_EXTENSION {UNICODE_STRING ustrDevName;UNICODE_STRING ustrSymLinkName;PDEVICE_OBJECT pDevObj;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;VOID Unload(IN PDRIVER_OBJECT pDriverObject) {PDEVICE_OBJECT pDevObj = pDriverObject->DeviceObject;while (pDevObj) {PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;IoDeleteDevice(pDevExt->pDevObj);IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);pDevObj = pDevObj->NextDevice;}KdPrint(("卸载驱动!\n"));
}NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {const char *pIRPName[] = {"IRP_MJ_CREATE                     ","IRP_MJ_CREATE_NAMED_PIPE           ","IRP_MJ_CLOSE                       ","IRP_MJ_READ                        ","IRP_MJ_WRITE                       ","IRP_MJ_QUERY_INFORMATION           ","IRP_MJ_SET_INFORMATION             ","IRP_MJ_QUERY_EA                    ","IRP_MJ_SET_EA                      ","IRP_MJ_FLUSH_BUFFERS               ","IRP_MJ_QUERY_VOLUME_INFORMATION    ","IRP_MJ_SET_VOLUME_INFORMATION      ","IRP_MJ_DIRECTORY_CONTROL           ","IRP_MJ_FILE_SYSTEM_CONTROL         ","IRP_MJ_DEVICE_CONTROL              ","IRP_MJ_INTERNAL_DEVICE_CONTROL     ","IRP_MJ_SHUTDOWN                    ","IRP_MJ_LOCK_CONTROL                ","IRP_MJ_CLEANUP                     ","IRP_MJ_CREATE_MAILSLOT             ","IRP_MJ_QUERY_SECURITY              ","IRP_MJ_SET_SECURITY                ","IRP_MJ_POWER                       ","IRP_MJ_SYSTEM_CONTROL              ","IRP_MJ_DEVICE_CHANGE               ","IRP_MJ_QUERY_QUOTA                 ","IRP_MJ_SET_QUOTA                   ","IRP_MJ_PNP                         "};PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); // 获取收到该IRP的当前设备的IO堆栈KdPrint(("收到了%s类型的IRP\n",pIRPName[stack->MajorFunction])); // 打印当前IRP类型pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRPreturn(STATUS_SUCCESS);
}NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDrv) {UNICODE_STRING ustrDevName;RtlInitUnicodeString(&ustrDevName, L"\\Device\\HelloDDK");PDEVICE_OBJECT pDev;PDEVICE_EXTENSION pDevExt = NULL;NTSTATUS status = IoCreateDevice(pDrv, sizeof(DEVICE_EXTENSION), &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDev);if (!NT_SUCCESS(status)) {KdPrint(("创建设备失败%08X!\n", status));return(status);}pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension; // 获取设备对象内携带的设备拓展pDevExt->ustrDevName = ustrDevName; // 在设备拓展里携带设备的相关信息, 以便在对应派遣函数内使用pDevExt->pDevObj = pDev; UNICODE_STRING ustrSymLinkName;RtlInitUnicodeString(&ustrSymLinkName, L"\\??\\HelloDDK");status = IoCreateSymbolicLink(&ustrSymLinkName, &ustrDevName); // 创建符号链接以便用户态找到if (!NT_SUCCESS(status)) {IoDeleteDevice(pDev);KdPrint(("创建符号链接失败!\n"));return(status);}pDevExt->ustrSymLinkName = ustrSymLinkName; return(status);
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {pDriverObject->DriverUnload = Unload;KdPrint(("加载驱动!\n"));for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) pDriverObject->MajorFunction[i] = DispatchRoutine;NTSTATUS status = CreateDevice(pDriverObject);if (!NT_SUCCESS(status)) return(STATUS_UNSUCCESSFUL);return(STATUS_SUCCESS);
}

对应的用户态测试程序如下:

#include <windows.h>
#include <stdio.h>int main() {HANDLE hDl = CreateFile("\\\\.\\HelloDDK", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == hDl) {printf("打开失败!%08X\n", GetLastError());return(-1);}DWORD dwRet;ReadFile(hDl, NULL, 0, &dwRet, NULL);dwRet = GetFileSize(hDl, NULL);WriteFile(hDl, NULL, NULL, &dwRet, NULL);CloseHandle(hDl);return(0);
}

在这个用户层的程序内分别生成了IRP_MJ_READ, IRP_MJ_QUERY_INFORMATION,  IRP_MJ_WRITE和IRP_MJ_CLOSE等IRP类型,结果显示如下:

驱动程序把接受到的IRP类型全部打印了出来

2. 缓冲区方式读写内存


拿WriteFile举例,WriteFile会写入数据到设备上,那这些数据必然会传到其对应的驱动中并由派遣函数来处理,可是假设直接由处于内核态的驱动通过使用对应的数据地址来获取是不可靠的,原因在于,在保护模式下,每个进程都有独立的虚拟地址空间,而CPU处理进程中的线程会进行切换,同样的地址如果进行切换后虚拟地址映射得到的物理地址就是不同的了,这会导致很严重的问题,可能会直接蓝屏。

由于这种问题,所以衍生除了通过缓冲区的方式进行内存读写,其原理是,通过将用户态的用户数据全部复制到内核空间中,由于内核空间是所有用户进程共享同一个,没有虚拟地址空间的说法,所以即使进程切换了也不会有任何影响。其缺点是运行效率,即需要把用户态数据复制到内核态。

WriteFile通过生成IRP并将其送至对应的派遣处理函数,要写入的用户态数据地址会被存在IRP结构的AssociatedIrp.SystemBuffer子域中。通过当前设备对应的IO堆栈获取写入的字节数,即IO_STACK_LOCATION获取Parameters.Write.Length, 当WriteFile结束后,真正写入的字节数会通过一个指针带出WriteFile,来看一下原形:

BOOL WriteFile(HANDLE       hFile,LPCVOID      lpBuffer,DWORD        nNumberOfBytesToWrite,LPDWORD      lpNumberOfBytesWritten,LPOVERLAPPED lpOverlapped
);

可以发现其第二三个参数,就是我上述指代的通过IO堆栈和IRP传入底层驱动,第四个参数lpNumberOfBytesWritten是个输出参数,就是真正写入的字节,其会通过IRP结构中的IoStatus.Information带出。最后一个参数是控制同步异步的无关紧要。

接下去试着使用缓冲区的方式来进行内存读写,我会通过用户态的进程来写入一段话,然后内核态驱动接收到后打印出来,并返回给用户态写入了0字节,用户态进程会进行输出:

#include <ntddk.h>typedef struct _DEVICE_EXTENSION {UNICODE_STRING ustrDevName;UNICODE_STRING ustrSymLinkName;PDEVICE_OBJECT pDevObj;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;VOID Unload(IN PDRIVER_OBJECT pDriverObject) {PDEVICE_OBJECT pDevObj = pDriverObject->DeviceObject;while (pDevObj) {PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;IoDeleteDevice(pDevExt->pDevObj);IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);pDevObj = pDevObj->NextDevice;}KdPrint(("卸载驱动!\n"));
}NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {const char *pIRPName[] = {"IRP_MJ_CREATE                     ","IRP_MJ_CREATE_NAMED_PIPE           ","IRP_MJ_CLOSE                       ","IRP_MJ_READ                        ","IRP_MJ_WRITE                       ","IRP_MJ_QUERY_INFORMATION           ","IRP_MJ_SET_INFORMATION             ","IRP_MJ_QUERY_EA                    ","IRP_MJ_SET_EA                      ","IRP_MJ_FLUSH_BUFFERS               ","IRP_MJ_QUERY_VOLUME_INFORMATION    ","IRP_MJ_SET_VOLUME_INFORMATION      ","IRP_MJ_DIRECTORY_CONTROL           ","IRP_MJ_FILE_SYSTEM_CONTROL         ","IRP_MJ_DEVICE_CONTROL              ","IRP_MJ_INTERNAL_DEVICE_CONTROL     ","IRP_MJ_SHUTDOWN                    ","IRP_MJ_LOCK_CONTROL                ","IRP_MJ_CLEANUP                     ","IRP_MJ_CREATE_MAILSLOT             ","IRP_MJ_QUERY_SECURITY              ","IRP_MJ_SET_SECURITY                ","IRP_MJ_POWER                       ","IRP_MJ_SYSTEM_CONTROL              ","IRP_MJ_DEVICE_CHANGE               ","IRP_MJ_QUERY_QUOTA                 ","IRP_MJ_SET_QUOTA                   ","IRP_MJ_PNP                         "};PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); // 获取收到该IRP的当前设备的IO堆栈KdPrint(("收到了%s类型的IRP\n",pIRPName[stack->MajorFunction])); // 打印当前IRP类型pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRPreturn(STATUS_SUCCESS);
}NTSTATUS WriteRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);ULONG totalLen = stack->Parameters.Write.Length; // 获取用户态写入的字节数PCHAR pAddr = pIrp->AssociatedIrp.SystemBuffer; // 获取用户态复制内核态的地址PCHAR pNewMem = (PWCHAR)ExAllocatePool(PagedPool, totalLen + 1);if (NULL == pNewMem) {KdPrint(("分配内存失败!\n"));return(STATUS_INSUFFICIENT_RESOURCES);}strcpy(pNewMem, pAddr);pNewMem[totalLen] = 0;KdPrint(("%s\n", pNewMem));ExFreePool(pNewMem);pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0; // 让WriteFile的真正写入字节数变0IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP
}NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDrv) {UNICODE_STRING ustrDevName;RtlInitUnicodeString(&ustrDevName, L"\\Device\\HelloDDK");PDEVICE_OBJECT pDev;PDEVICE_EXTENSION pDevExt = NULL;NTSTATUS status = IoCreateDevice(pDrv, sizeof(DEVICE_EXTENSION), &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDev);if (!NT_SUCCESS(status)) {KdPrint(("创建设备失败%08X!\n", status));return(status);}pDev->Flags |= DO_BUFFERED_IO; // 重要!!必须加上该标志位以声明是缓冲区方式读写pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension; // 获取设备对象内携带的设备拓展pDevExt->ustrDevName = ustrDevName; // 在设备拓展里携带设备的相关信息, 以便在对应派遣函数内使用pDevExt->pDevObj = pDev; UNICODE_STRING ustrSymLinkName;RtlInitUnicodeString(&ustrSymLinkName, L"\\??\\HelloDDK");status = IoCreateSymbolicLink(&ustrSymLinkName, &ustrDevName); // 创建符号链接以便用户态找到if (!NT_SUCCESS(status)) {IoDeleteDevice(pDev);KdPrint(("创建符号链接失败!\n"));return(status);}pDevExt->ustrSymLinkName = ustrSymLinkName; return(status);
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {pDriverObject->DriverUnload = Unload;KdPrint(("加载驱动!\n"));for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) pDriverObject->MajorFunction[i] = DispatchRoutine;pDriverObject->MajorFunction[IRP_MJ_WRITE] = WriteRoutine;NTSTATUS status = CreateDevice(pDriverObject);if (!NT_SUCCESS(status)) return(STATUS_UNSUCCESSFUL);return(STATUS_SUCCESS);
}

以上是完整程序,其实只需要看WriteRoutine和CreateDevice中的Flag标志位变动即可。下面给出运行结果:

可以看到明明内核态接收到了用户态传来的Hello, world可用户态只显示写入了0字节。接下去再添加读取的部分,我依旧会写入Hello world, 但我会读取到Fuck world来看看吧!

NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);ULONG totalLen = stack->Parameters.Read.Length; // 获取用户态要读取的字节数// 获取用户态读取的内核态地址PCHAR pAddr = pIrp->AssociatedIrp.SystemBuffer;strcpy(pAddr, "Fuck, world");KdPrint(("%s\n", pIrp->AssociatedIrp.SystemBuffer));pIrp->IoStatus.Status = STATUS_SUCCESS;// 注意!!这里读取的结果字节数就是你用户态最终读取到的字节数量,如果你写0这里就一个字节读不到了pIrp->IoStatus.Information = totalLen; IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP
}

其结果如下:

再来看一种相对复杂一点的方式,可以通过在设备拓展中分配一段缓冲区,来记录所有用户态写入内核的数据,先来看一下代码:

// 这是新的设备拓展结构
typedef struct _DEVICE_EXTENSION {UNICODE_STRING ustrDevName;UNICODE_STRING ustrSymLinkName;PDEVICE_OBJECT pDevObj;PCHAR pBuffer;ULONG bufLen;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;
#define MAX_BUFFER_SIZE 4096NTSTATUS WriteRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension;NTSTATUS status;ULONG totalLen = stack->Parameters.Write.Length;// 通过SetFilePointer会导致该偏移移动ULONG WriteOfst = stack->Parameters.Write.ByteOffset.QuadPart; KdPrint(("stack->Parameters.Write.ByteOffset.QuadPart: %d\nstack->Parameters.Write.Length: %d\n", WriteOfst, totalLen));// 如果内核缓冲区的偏移开始处+用户态新写入的内容超过了内核缓冲区总大小则代表出现错误// 否则代表可以写入进入if语句if (WriteOfst + totalLen < MAX_BUFFER_SIZE) {// 判断内核缓冲区已存字符串长度是否需要更新// 如果内核缓冲区已有字符串长度超过了记录的长度,则更新if (WriteOfst + totalLen > pDevExt->bufLen) // 更新现在缓冲区长度pDevExt->bufLen = WriteOfst + totalLen;// 复制操作RtlCopyMemory(pDevExt->pBuffer + WriteOfst, (PCHAR)pIrp->AssociatedIrp.SystemBuffer, totalLen);KdPrint(("总共写入了%d字节\n", pDevExt->bufLen));KdPrint(("写入了%s\n", pDevExt->pBuffer));}else {status = STATUS_BUFFER_TOO_SMALL;totalLen = 0;KdPrint(("写操作出现了问题\n"));}pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0; // 让WriteFile的真正写入字节数变0IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP
}

这种方式可以节省IO次数。

3. 直接方式进行内存读写


直接读写方式是通过锁定位于用户态进程的数据内存块,并通过内存描述符表将其映射到内核态,然后直接操纵MDL来达到IO读写的目的,用户态的读写操作发出的IRP中一般带有MdlAddress字段,通过该字段可以进行MDL操作,首先来看一下读取的代码:

#include <ntddk.h>typedef struct _DEVICE_EXTENSION {UNICODE_STRING ustrDevName;UNICODE_STRING ustrSymLinkName;PDEVICE_OBJECT pDevObj;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;VOID Unload(IN PDRIVER_OBJECT pDriverObject) {PDEVICE_OBJECT pDevObj = pDriverObject->DeviceObject;while (pDevObj) {PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;IoDeleteDevice(pDevExt->pDevObj);IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);pDevObj = pDevObj->NextDevice;}KdPrint(("卸载驱动!\n"));
}NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {const char *pIRPName[] = {"IRP_MJ_CREATE                     ","IRP_MJ_CREATE_NAMED_PIPE           ","IRP_MJ_CLOSE                       ","IRP_MJ_READ                        ","IRP_MJ_WRITE                       ","IRP_MJ_QUERY_INFORMATION           ","IRP_MJ_SET_INFORMATION             ","IRP_MJ_QUERY_EA                    ","IRP_MJ_SET_EA                      ","IRP_MJ_FLUSH_BUFFERS               ","IRP_MJ_QUERY_VOLUME_INFORMATION    ","IRP_MJ_SET_VOLUME_INFORMATION      ","IRP_MJ_DIRECTORY_CONTROL           ","IRP_MJ_FILE_SYSTEM_CONTROL         ","IRP_MJ_DEVICE_CONTROL              ","IRP_MJ_INTERNAL_DEVICE_CONTROL     ","IRP_MJ_SHUTDOWN                    ","IRP_MJ_LOCK_CONTROL                ","IRP_MJ_CLEANUP                     ","IRP_MJ_CREATE_MAILSLOT             ","IRP_MJ_QUERY_SECURITY              ","IRP_MJ_SET_SECURITY                ","IRP_MJ_POWER                       ","IRP_MJ_SYSTEM_CONTROL              ","IRP_MJ_DEVICE_CHANGE               ","IRP_MJ_QUERY_QUOTA                 ","IRP_MJ_SET_QUOTA                   ","IRP_MJ_PNP                         "};PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); // 获取收到该IRP的当前设备的IO堆栈KdPrint(("收到了%s类型的IRP\n",pIRPName[stack->MajorFunction])); // 打印当前IRP类型pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRPreturn(STATUS_SUCCESS);
}NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);ULONG totalLen = stack->Parameters.Read.Length; // 获取用户态要读取的字节数KdPrint(("用户态读取缓冲区大小: %d字节\n", totalLen));ULONG mdlLen = MmGetMdlByteCount(pIrp->MdlAddress); // 获取MDL缓冲区长度//PVOID mdlAddr = MmGetMdlVirtualAddress(pIrp->MdlAddress); // 获取MDL缓冲区的首地址//ULONG mdlOfst = MmGetMdlByteOffset(pIrp->MdlAddress); // 获取MDL缓冲区偏移NTSTATUS status = STATUS_SUCCESS;if (mdlLen != totalLen) {pIrp->IoStatus.Information = 0;status = STATUS_UNSUCCESSFUL;}else {PVOID kernelAddr = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); // 获取在内核态的映射KdPrint(("mdl内核态地址: %08X\n", kernelAddr));strcpy(kernelAddr, "Fuck, world");pIrp->IoStatus.Information = mdlLen;}pIrp->IoStatus.Status = status;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP
}NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDrv) {UNICODE_STRING ustrDevName;RtlInitUnicodeString(&ustrDevName, L"\\Device\\HelloDDK");PDEVICE_OBJECT pDev;PDEVICE_EXTENSION pDevExt = NULL;NTSTATUS status = IoCreateDevice(pDrv, sizeof(DEVICE_EXTENSION), &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDev);if (!NT_SUCCESS(status)) {KdPrint(("创建设备失败%08X!\n", status));return(status);}pDev->Flags |= DO_DIRECT_IO; // 重要!!必须加上该标志位以声明是直接读写方式pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension; // 获取设备对象内携带的设备拓展pDevExt->ustrDevName = ustrDevName; // 在设备拓展里携带设备的相关信息, 以便在对应派遣函数内使用pDevExt->pDevObj = pDev; UNICODE_STRING ustrSymLinkName;RtlInitUnicodeString(&ustrSymLinkName, L"\\??\\HelloDDK");status = IoCreateSymbolicLink(&ustrSymLinkName, &ustrDevName); // 创建符号链接以便用户态找到if (!NT_SUCCESS(status)) {IoDeleteDevice(pDev);KdPrint(("创建符号链接失败!\n"));return(status);}pDevExt->ustrSymLinkName = ustrSymLinkName; return(status);
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {pDriverObject->DriverUnload = Unload;KdPrint(("加载驱动!\n"));for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) pDriverObject->MajorFunction[i] = DispatchRoutine;pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;NTSTATUS status = CreateDevice(pDriverObject);if (!NT_SUCCESS(status)) return(STATUS_UNSUCCESSFUL);return(STATUS_SUCCESS);
}
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>int main() {HANDLE hDl = CreateFile("\\\\.\\HelloDDK", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (INVALID_HANDLE_VALUE == hDl) {printf("打开失败!%08X\n", GetLastError());return(-1);}DWORD dwRet;CHAR psz[] = "Hello, world";CHAR *pRsz = NULL;SIZE_T sz = strlen(psz);pRsz = (CHAR *)malloc(sz + 1);if (NULL == pRsz) {printf("分配内存失败!\n");return(-1);}memset(pRsz, 0, strlen(psz) + 1);ReadFile(hDl, pRsz, sz + 1, &dwRet, NULL);printf("读取了内容%s, 获得读取的字节数: %d\n", pRsz, dwRet);free(pRsz);CloseHandle(hDl);return(0);
}

最重要的是设置设备的Flag标志位成DO_IO_DIRECT以表示是直接内存读写。接着要通过MmGetMdlByteCount来获取MDL分配的内存大小并且与用户态传入的读取缓冲区大小相比较,如果不相等,那就肯定是错误的。如果相等就代表映射成功就可以通过MmGetSystemAddressForMdlSafe获取MDL在内核态的虚拟地址,向这个地址写入内容即是向用户态缓冲区写内容。通过MmGetMdlVirtualAddress获取的是用户态虚拟空间地址,而MmGetMdlByteOffset则获取的是用户空间偏移。

3. 其他方式进行读写操作


该方式不推荐,是直接操作用户空间地址,使用的前提是驱动程序与应用程序处于线程上下文才可使用。首先进程切换其实说法并不准确,因为主流操作系统的操作粒度一般都是线程,而进程只是提供了地址空间和各类资源。这些都是位于进程中的线程共享的。windows甚至发展出了纤程级别的粒度,当然这不重要。也就是说进程一般都至少有一个主线程,可能有一些子线程也可能没有,所谓的进程切换就是不同进程的线程间的切换操作。线程上下文发生变化了意思就是其地址空间与资源都不一样了,因为切换到的线程是属于另一个进程内。

所以驱动程序与应用程序处于线程上下文才可使用,意思就是在驱动与应用层进程进行IO时不能发生进程切换。否则一切失去了意义会导致蓝屏。

其主要的特征是首先设备对象的Flag标志位置0,而用户空间的数据缓冲区首地址用irp的UserBuffer字段指明。其他依旧保持不变,可以使用ProbeForWrite和ProbeForRead来探测后在进行使用。

来看一下实现代码:

#include <ntddk.h>typedef struct _DEVICE_EXTENSION {UNICODE_STRING ustrDevName;UNICODE_STRING ustrSymLinkName;PDEVICE_OBJECT pDevObj;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;VOID Unload(IN PDRIVER_OBJECT pDriverObject) {PDEVICE_OBJECT pDevObj = pDriverObject->DeviceObject;while (pDevObj) {PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;IoDeleteDevice(pDevExt->pDevObj);IoDeleteSymbolicLink(&pDevExt->ustrSymLinkName);pDevObj = pDevObj->NextDevice;}KdPrint(("卸载驱动!\n"));
}NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {const char *pIRPName[] = {"IRP_MJ_CREATE                     ","IRP_MJ_CREATE_NAMED_PIPE           ","IRP_MJ_CLOSE                       ","IRP_MJ_READ                        ","IRP_MJ_WRITE                       ","IRP_MJ_QUERY_INFORMATION           ","IRP_MJ_SET_INFORMATION             ","IRP_MJ_QUERY_EA                    ","IRP_MJ_SET_EA                      ","IRP_MJ_FLUSH_BUFFERS               ","IRP_MJ_QUERY_VOLUME_INFORMATION    ","IRP_MJ_SET_VOLUME_INFORMATION      ","IRP_MJ_DIRECTORY_CONTROL           ","IRP_MJ_FILE_SYSTEM_CONTROL         ","IRP_MJ_DEVICE_CONTROL              ","IRP_MJ_INTERNAL_DEVICE_CONTROL     ","IRP_MJ_SHUTDOWN                    ","IRP_MJ_LOCK_CONTROL                ","IRP_MJ_CLEANUP                     ","IRP_MJ_CREATE_MAILSLOT             ","IRP_MJ_QUERY_SECURITY              ","IRP_MJ_SET_SECURITY                ","IRP_MJ_POWER                       ","IRP_MJ_SYSTEM_CONTROL              ","IRP_MJ_DEVICE_CHANGE               ","IRP_MJ_QUERY_QUOTA                 ","IRP_MJ_SET_QUOTA                   ","IRP_MJ_PNP                         "};PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); // 获取收到该IRP的当前设备的IO堆栈KdPrint(("收到了%s类型的IRP\n",pIRPName[stack->MajorFunction])); // 打印当前IRP类型pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRPreturn(STATUS_SUCCESS);
}NTSTATUS ReadRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);ULONG totalLen = stack->Parameters.Read.Length; // 获取用户态要读取的字节数NTSTATUS status;__try {// 尝试写入ReadFile的缓冲区ProbeForWrite(pIrp->UserBuffer, totalLen, 4); strcpy((PCHAR)pIrp->UserBuffer, "Hello?");status = STATUS_SUCCESS;}__except (EXCEPTION_EXECUTE_HANDLER) {pIrp->IoStatus.Information = 0;pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;KdPrint(("异常发生!\n"));IoCompleteRequest(pIrp, IO_NO_INCREMENT);}pIrp->IoStatus.Information = strlen("Hello?");pIrp->IoStatus.Status = status;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP
}NTSTATUS WriteRoutine(IN PDEVICE_OBJECT pDev, IN PIRP pIrp) {PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension;NTSTATUS status = STATUS_UNSUCCESSFUL;ULONG totalLen = stack->Parameters.Write.Length;PCHAR pBuf = pIrp->UserBuffer;PCHAR *nBuf = (PCHAR)ExAllocatePool(PagedPool, totalLen);if (NULL == nBuf) {KdPrint(("内存分配失败\n"));status = STATUS_INSUFFICIENT_RESOURCES;goto End_Place;}RtlZeroMemory(nBuf, totalLen);__try {// 由于是单线程其实这一步没必要,但是还是写入ProbeForRead(pBuf, totalLen, 4); // 尝试读取从用户态写入到内核态的内容RtlCopyMemory(nBuf, pBuf, totalLen - 1);nBuf[totalLen - 1] = 0;KdPrint(("%s\n", nBuf));status = STATUS_SUCCESS;} __except (EXCEPTION_EXECUTE_HANDLER) {pIrp->IoStatus.Information = 0; // 让WriteFile的真正写入字节数变0pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;IoCompleteRequest(pIrp, IO_NO_INCREMENT); KdPrint(("异常发生!\n"));}ExFreePool(nBuf);
End_Place:pIrp->IoStatus.Information = totalLen; pIrp->IoStatus.Status = status;IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRPreturn(status);
}NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDrv) {UNICODE_STRING ustrDevName;RtlInitUnicodeString(&ustrDevName, L"\\Device\\HelloDDK");PDEVICE_OBJECT pDev;PDEVICE_EXTENSION pDevExt = NULL;NTSTATUS status = IoCreateDevice(pDrv, sizeof(DEVICE_EXTENSION), &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDev);if (!NT_SUCCESS(status)) {KdPrint(("创建设备失败%08X!\n", status));return(status);}pDev->Flags |= 0; // 其实这句话不写也没关系,为了完整性,因为这除了改变EFLAGS标志位本来就什么也没做pDevExt = (PDEVICE_EXTENSION)pDev->DeviceExtension; // 获取设备对象内携带的设备拓展pDevExt->ustrDevName = ustrDevName; // 在设备拓展里携带设备的相关信息, 以便在对应派遣函数内使用pDevExt->pDevObj = pDev; UNICODE_STRING ustrSymLinkName;RtlInitUnicodeString(&ustrSymLinkName, L"\\??\\HelloDDK");status = IoCreateSymbolicLink(&ustrSymLinkName, &ustrDevName); // 创建符号链接以便用户态找到if (!NT_SUCCESS(status)) {IoDeleteDevice(pDev);KdPrint(("创建符号链接失败!\n"));return(status);}pDevExt->ustrSymLinkName = ustrSymLinkName; return(status);
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) {pDriverObject->DriverUnload = Unload;KdPrint(("加载驱动!\n"));for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i) pDriverObject->MajorFunction[i] = DispatchRoutine;pDriverObject->MajorFunction[IRP_MJ_WRITE] = WriteRoutine;pDriverObject->MajorFunction[IRP_MJ_READ] = ReadRoutine;NTSTATUS status = CreateDevice(pDriverObject);if (!NT_SUCCESS(status)) return(STATUS_UNSUCCESSFUL);return(STATUS_SUCCESS);
}

(完)

[Win32驱动10] 派遣函数与读写方式相关推荐

  1. Windows驱动开发 - 派遣函数

    一 派遣函数 驱动程序的主要功能是负责处理I/O请求. 其中大部分I/O请求是在派遣函数中处理的. 用户模式下所有对驱动程序的I/O请求,全部由操作系统转化为一个叫做IRP的数据结构,不同的IRP数据 ...

  2. 驱动开发笔记5—驱动对象、设备对象、IRP和派遣函数

    文章目录 驱动对象 设备对象 IRP和派遣函数 IRP IRP类型 设置派遣函数 处理IRP 举例说明 设备读写方式 缓冲区方式读写 直接方式读写 其他方式读写 驱动对象 每个驱动程序都会有唯一的驱动 ...

  3. 驱动开发之 设备读写方式:缓冲区方式

    1. 设备对象一共同拥有三种读写方式:缓冲区方式读写(Buffered方式):直接方式读写(Direct方式).Neither方式.这三种方式的Flags分别相应DO_BUFFERED_IO,DO_D ...

  4. C语言中文件读写方式r 的作用,C语言文件读写操作主要函数及其用例

    C语言把磁盘文件看成是字符(或字节)的序列,按照存储信息的形式来说,文件主要是有文本文件和二进制文件.文本文件由一个个字符组成,每个字节存放一个ASCII码制,代表一个字符.二进制文件把内存中的数据按 ...

  5. Epoll在LT和ET模式下的读写方式

    在一个非阻塞的socket上调用read/write函数,返回EAGAIN 或者 EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 从字面上看, 意思是:EAGAIN: 再试一次 ...

  6. 咚咚咚————【封装驱动】DS3231时钟芯片读写程序,分享交流自己编写的程序。

    咚咚咚----[封装驱动]DS3231时钟芯片读写程序,分享交流自己编写的程序. /******************************************** 主控芯片:STM32 Co ...

  7. jS四种函数的调用方式

    6- js 函数的四种调用方式 2016年11月04日 13:41:54 阅读数:7559 函数的四种调用方式 函数有下列调用模式 函数调用模式 方法调用模式 构造器模式 上下文模式 函数调用 模式 ...

  8. ② ESP8266 开发学习笔记_By_GYC 【ESP8266 驱动 ws2812 三原色灯(spi方式 稳定灯光)】

    目录 ② ESP8266 开发学习笔记_By_GYC [ESP8266 驱动 ws2812 三原色灯(spi方式 稳定灯光)] 一.驱动ws2812遇到的问题 二.可能的方案 三.具体实现 四.测试程 ...

  9. PCIe驱动开发接口函数

    Realtek8168网卡时pci接口的网卡,其驱动程序就是一个PCI设备的驱动程序实例,我们一起看看其流程. 1.  首先,初始化模块调用static inline int pci_register ...

最新文章

  1. LeetCode 206 Reverse Linked List--反转链表--迭代与递归解法--递归使用一个临时变量,迭代使用3个
  2. Android多线程断点下载
  3. Sublime和Webstorm新建代码块
  4. 【错误记录】Android Studio 4.2.1 编译报错 ( 设置支持的 Java 和 Kotlin 版本 | java.lang.BootstrapMethodError )
  5. MySQL Connector/ODBC 5.2.2 发布
  6. 漫画TCP——一个悲伤的故事
  7. ABAP和Java的单元测试Unit Test
  8. HDU5971【瞎搞】
  9. 视觉SLAM笔记(23) 图像
  10. hdu 2089 不要62--数位dp入门
  11. table td 纵向求和
  12. 高频量化交易之王--李庆在华尔街
  13. excel怎么设置打印区域_学会Excel分页符设置,打印区域自由选择
  14. 这些中国扶贫路上的“组合拳”,你见过吗?
  15. 中文乱码——编码问题
  16. vs2017下libcef配置
  17. 执法文书打印的实现(二):基于freemaker技术生成可打印的word文档
  18. Firebird数据库的安装配置与使用
  19. 数据结构基础知识点,看完保证期末不挂科!
  20. 一文读懂区块链技术,史上最全,最通俗

热门文章

  1. 去除Google key及TEE key
  2. 英语中代替 I think 的Debate回答
  3. python编的著名游戏制作人是谁_老贼是哪个游戏制作人
  4. 爱心发射代码带名字升级版
  5. 文字与图片渐变效果(图层CALayer与属性蒙版mask )
  6. 适合计算机社团活动的游戏,大学课外社团活动游戏大全
  7. 小学计算机课教案,小学信息技术教案(全套).docx
  8. python subprocess使用_Python subprocess模块用法详解
  9. 一文带你详细了解【类和对象】
  10. 获取手机上已安装应用,游戏的安装包