在Windows中,用户态进程的各类IO请求最后都会转化成对应的IRP请求并发送给底层驱动。其流程差不多是这样的,拿WriteFile来举例:
在用户态调用WriteFile函数WriteFile会调用NTDLL.dll中的NtWriteFile函数NtWriteFile会陷入内核调用系统服务NtWriteFile(注意只是相同的名字,但本质完全不同)NtWriteFile会生成相应的IRP,对应IRP类型为: IRP_MJ_WRITE并将其发送给底层对应驱动IRP会顺次自顶向下传递给设备栈中的各个设备,直到被处理完成,即调用了IoCompleteRequest内核函数调用完后设备栈会向上回卷,如果碰到有完成函数就会调用完成函数(这是另外的内容与本博客无关)直到最终会把处理完成的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); // 完成该IRP return(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类型全部打印了出来
拿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); // 完成该IRP return(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的真正写入字节数变0 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_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 4096 NTSTATUS 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的真正写入字节数变0 IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 完成该IRP }这种方式可以节省IO次数。
直接读写方式是通过锁定位于用户态进程的数据内存块,并通过内存描述符表将其映射到内核态,然后直接操纵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); // 完成该IRP return(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则获取的是用户空间偏移。
该方式不推荐,是直接操作用户空间地址,使用的前提是驱动程序与应用程序处于线程上下文才可使用。首先进程切换其实说法并不准确,因为主流操作系统的操作粒度一般都是线程,而进程只是提供了地址空间和各类资源。这些都是位于进程中的线程共享的。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); // 完成该IRP return(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的真正写入字节数变0 pIrp->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); // 完成该IRP return(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); }(完)