微软的wdk开发包里自带了一些sample,这是些质量不错并且权威的学习资料,最好的学习驱动的方法就是阅读和修改这些代码。其中Ramdisk实现了一个虚拟磁盘,可以作为WDF编程的经典代码材料,《寒江独钓-Windows内核安全编程》第5章“磁盘的虚拟”便以此为例,这篇博客是一篇学习总结。
驱动的入口函数很简洁:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { WDF_DRIVER_CONFIG config; KdPrint(("Windows Ramdisk Driver - Driver Framework Edition.\n")); WDF_DRIVER_CONFIG_INIT( &config, RamDiskEvtDeviceAdd ); return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE); }
上面代码最主要的任务就是创建一个“驱动对象”并马上返回了,我们看WDF_DRIVER_CONFIG这个玩意:
typedef struct _WDF_DRIVER_CONFIG { ULONG Size; //这个结构体的大小,以自己为单位; PFN_WDF_DRIVER_DEVICE_ADD EvtDriverDeviceAdd; //Windows驱动的EvtDriverDeviceAdd回调函数指针; PFN_WDF_DRIVER_UNLOAD EvtDriverUnload; //Windows驱动的EvtDriverUnload回调函数指针; ULONG DriverInitFlags; //驱动初始化标志位,由一个或者多个WDF_DRIVER_INIT_FLAGS类型值构成; ULONG DriverPoolTag; //驱动定义的内存池标签,是Framework分配给驱动的内存标签。调试时可以显示这个标签。 } WDF_DRIVER_CONFIG, *PWDF_DRIVER_CONFIG;
上面的WDF_DRIVER_CONFIG_INIT( &config, RamDiskEvtDeviceAdd );其实就是把 RamDiskEvtDeviceAdd 赋给结构体的第2项。上面有注释,它是一个回调函数指针,注意这是一个非常重要的参数,因为创建完驱动对象后主函数就返回了,而这个回调函数是后面驱动和系统联系起来的唯一纽带,在今后系统运行过程中,一旦发现了此类设备, RamDiskEvtDeviceAdd就会被 Windows 的PnP manager调用,这个驱动的处理流程也将在此后上演。
接下来看 RamDiskEvtDeviceAdd 这个函数:
NTSTATUS RamDiskEvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) { WDF_OBJECT_ATTRIBUTES deviceAttributes; //将建立的设备对象的属性描述变量 NTSTATUS status; WDFDEVICE device; //将建立的设备 WDF_OBJECT_ATTRIBUTES queueAttributes; //将要建立的队列对象的属性描述变量 WDF_IO_QUEUE_CONFIG ioQueueConfig; //将要建立的队列配置变量 PDEVICE_EXTENSION pDeviceExtension; //此设备所对应的设备扩展域的指针 PQUEUE_EXTENSION pQueueContext = NULL; //将要建立的队列扩展域的指针 WDFQUEUE queue; //将要建立的队列 DECLARE_CONST_UNICODE_STRING(ntDeviceName, NT_DEVICE_NAME); PAGED_CODE(); UNREFERENCED_PARAMETER(Driver); //此函数不使用Driver参数,加这句为避免编译告警 status = WdfDeviceInitAssignName(DeviceInit, &ntDeviceName); //1.为设备指定名字 if (!NT_SUCCESS(status)) { return status; } //2.设置设备的一些属性 WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_DISK); WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect); WdfDeviceInitSetExclusive(DeviceInit, FALSE); //指定设备的设备对象扩展 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_EXTENSION); deviceAttributes.EvtCleanupCallback = RamDiskEvtDeviceContextCleanup; //建立设备的清除回调函数 //3.创建设备 status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); if (!NT_SUCCESS(status)) { return status; } //声明一个指向设备扩展的指针 pDeviceExtension = DeviceGetExtension(device); //将队列的配置变量初始化为默认值 WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE ( &ioQueueConfig, WdfIoQueueDispatchSequential ); //设置我们感兴趣的请求的处理函数,这里处理了Read/Write/DeviceIoControl请求 ioQueueConfig.EvtIoDeviceControl = RamDiskEvtIoDeviceControl; ioQueueConfig.EvtIoRead = RamDiskEvtIoRead; ioQueueConfig.EvtIoWrite = RamDiskEvtIoWrite; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&queueAttributes, QUEUE_EXTENSION); //指定一下队列的队列对象扩展 __analysis_assume(ioQueueConfig.EvtIoStop != 0); status = WdfIoQueueCreate( device, //4.用初始化好的参数创建队列 &ioQueueConfig, &queueAttributes, &queue ); __analysis_assume(ioQueueConfig.EvtIoStop == 0); if (!NT_SUCCESS(status)) { return status; } pQueueContext = QueueGetExtension(queue); //指向队列设备扩展的指针 pQueueContext->DeviceExtension = pDeviceExtension; //队列扩展与设备扩展关联起来 //设备扩展中几个值的初始化 pDeviceExtension->DiskRegInfo.DriveLetter.Buffer = (PWSTR) &pDeviceExtension->DriveLetterBuffer; pDeviceExtension->DiskRegInfo.DriveLetter.MaximumLength = sizeof(pDeviceExtension->DriveLetterBuffer); //从本驱动提供的注册表键中取信息 RamDiskQueryDiskRegParameters( WdfDriverGetRegistryPath(WdfDeviceGetDriver(device)), &pDeviceExtension->DiskRegInfo ); //分配指定大小的非分页内存 pDeviceExtension->DiskImage = ExAllocatePoolWithTag( NonPagedPool, pDeviceExtension->DiskRegInfo.DiskSize, RAMDISK_TAG ); if (pDeviceExtension->DiskImage) { UNICODE_STRING deviceName; UNICODE_STRING win32Name; //初始化磁盘的函数,属于业务逻辑部分,这里不同的功能代码替换之 RamDiskFormatDisk(pDeviceExtension); status = STATUS_SUCCESS; RtlInitUnicodeString(&win32Name, DOS_DEVICE_NAME); RtlInitUnicodeString(&deviceName, NT_DEVICE_NAME); pDeviceExtension->SymbolicLink.Buffer = (PWSTR) &pDeviceExtension->DosDeviceNameBuffer; pDeviceExtension->SymbolicLink.MaximumLength = sizeof(pDeviceExtension->DosDeviceNameBuffer); pDeviceExtension->SymbolicLink.Length = win32Name.Length; RtlCopyUnicodeString(&pDeviceExtension->SymbolicLink, &win32Name); RtlAppendUnicodeStringToString(&pDeviceExtension->SymbolicLink, &pDeviceExtension->DiskRegInfo.DriveLetter); //5.为设备建立符号链接,即DOS_DEVICE_NAME+注册表中读取的盘符 status = WdfDeviceCreateSymbolicLink(device, &pDeviceExtension->SymbolicLink); } return status; }
上面代码注释的已经比较清晰,再简要描述一下我们做了什么。
首先创建一个“驱动对象”,并设置一个 RamDiskEvtDeviceAdd 回调函数,这个回调函数很重要,它是驱动和系统相联系的纽带。在 RamDiskEvtDeviceAdd 函数里面,最重要的任务便是建立一个“设备对象”,在这之后各种请求就会纷至踏来。而如何处理这些请求呢,一种常用的方式就是使用队列,把接收到的请求放入一个或多个队列中,另一个线程去处理这些队列,这是一个典型的生产者——消费者模型。为了方便我们编程,微软实现了这个轮子,这显然比我们自己处理方便许多,这里用到了“队列对象”。
目前为止我们用到了3个内核对象,分别是“驱动对象”,“设备对象”,“队列对象”,现在来总结一下他们有没有共性。
1.“对象”都有可配置的属性,如“驱动对象”通过WDF_DRIVER_CONFIG变量,“设备对象”和“队列对象”的WDF_OBJECT_ATTRIBUTES变量,“队列对象”的WDF_IO_QUEUE_CONFIG变量。
2.正确处理这上述“对象”结构的各个成员,那么驱动就实现它的功能了,这是使用框架开发带来的便捷。我们的编程工作最终就变成了:创建一个个“对象”,并妥善处理好它们。
现在我们还有几个问题没有处理,一是初始化磁盘的业务逻辑部分,二是请求的处理部分,三是设备的清除回调函数部分,下面分别说明。
初始化磁盘属于业务逻辑,与我们想要说明的WDF编程框架并没有联系,所以不作代码注释,如下:
NTSTATUS RamDiskFormatDisk( IN PDEVICE_EXTENSION devExt ) { PBOOT_SECTOR bootSector = (PBOOT_SECTOR) devExt->DiskImage; PUCHAR firstFatSector; ULONG rootDirEntries; ULONG sectorsPerCluster; USHORT fatType; // Type FAT 12 or 16 USHORT fatEntries; // Number of cluster entries in FAT USHORT fatSectorCnt; // Number of sectors for FAT PDIR_ENTRY rootDir; // Pointer to first entry in root dir PAGED_CODE(); ASSERT(sizeof(BOOT_SECTOR) == 512); ASSERT(devExt->DiskImage != NULL); RtlZeroMemory(devExt->DiskImage, devExt->DiskRegInfo.DiskSize); devExt->DiskGeometry.BytesPerSector = 512; devExt->DiskGeometry.SectorsPerTrack = 32; // Using Ramdisk value devExt->DiskGeometry.TracksPerCylinder = 2; // Using Ramdisk value devExt->DiskGeometry.Cylinders.QuadPart = devExt->DiskRegInfo.DiskSize / 512 / 32 / 2; devExt->DiskGeometry.MediaType = RAMDISK_MEDIA_TYPE; KdPrint(( "Cylinders: %I64d\n TracksPerCylinder: %lu\n SectorsPerTrack: %lu\n BytesPerSector: %lu\n", devExt->DiskGeometry.Cylinders.QuadPart, devExt->DiskGeometry.TracksPerCylinder, devExt->DiskGeometry.SectorsPerTrack, devExt->DiskGeometry.BytesPerSector )); rootDirEntries = devExt->DiskRegInfo.RootDirEntries; sectorsPerCluster = devExt->DiskRegInfo.SectorsPerCluster; if (rootDirEntries & (DIR_ENTRIES_PER_SECTOR - 1)) { rootDirEntries = (rootDirEntries + (DIR_ENTRIES_PER_SECTOR - 1)) & ~ (DIR_ENTRIES_PER_SECTOR - 1); } KdPrint(( "Root dir entries: %lu\n Sectors/cluster: %lu\n", rootDirEntries, sectorsPerCluster )); bootSector->bsJump[0] = 0xeb; bootSector->bsJump[1] = 0x3c; bootSector->bsJump[2] = 0x90; bootSector->bsOemName[0] = ‘R‘; bootSector->bsOemName[1] = ‘a‘; bootSector->bsOemName[2] = ‘j‘; bootSector->bsOemName[3] = ‘u‘; bootSector->bsOemName[4] = ‘R‘; bootSector->bsOemName[5] = ‘a‘; bootSector->bsOemName[6] = ‘m‘; bootSector->bsOemName[7] = ‘ ‘; bootSector->bsBytesPerSec = (SHORT)devExt->DiskGeometry.BytesPerSector; bootSector->bsResSectors = 1; bootSector->bsFATs = 1; bootSector->bsRootDirEnts = (USHORT)rootDirEntries; bootSector->bsSectors = (USHORT)(devExt->DiskRegInfo.DiskSize / devExt->DiskGeometry.BytesPerSector); bootSector->bsMedia = (UCHAR)devExt->DiskGeometry.MediaType; bootSector->bsSecPerClus = (UCHAR)sectorsPerCluster; fatEntries = (bootSector->bsSectors - bootSector->bsResSectors - bootSector->bsRootDirEnts / DIR_ENTRIES_PER_SECTOR) / bootSector->bsSecPerClus + 2; if (fatEntries > 4087) { fatType = 16; fatSectorCnt = (fatEntries * 2 + 511) / 512; fatEntries = fatEntries + fatSectorCnt; fatSectorCnt = (fatEntries * 2 + 511) / 512; } else { fatType = 12; fatSectorCnt = (((fatEntries * 3 + 1) / 2) + 511) / 512; fatEntries = fatEntries + fatSectorCnt; fatSectorCnt = (((fatEntries * 3 + 1) / 2) + 511) / 512; } bootSector->bsFATsecs = fatSectorCnt; bootSector->bsSecPerTrack = (USHORT)devExt->DiskGeometry.SectorsPerTrack; bootSector->bsHeads = (USHORT)devExt->DiskGeometry.TracksPerCylinder; bootSector->bsBootSignature = 0x29; bootSector->bsVolumeID = 0x12345678; bootSector->bsLabel[0] = ‘R‘; bootSector->bsLabel[1] = ‘a‘; bootSector->bsLabel[2] = ‘m‘; bootSector->bsLabel[3] = ‘D‘; bootSector->bsLabel[4] = ‘i‘; bootSector->bsLabel[5] = ‘s‘; bootSector->bsLabel[6] = ‘k‘; bootSector->bsLabel[7] = ‘ ‘; bootSector->bsLabel[8] = ‘ ‘; bootSector->bsLabel[9] = ‘ ‘; bootSector->bsLabel[10] = ‘ ‘; bootSector->bsFileSystemType[0] = ‘F‘; bootSector->bsFileSystemType[1] = ‘A‘; bootSector->bsFileSystemType[2] = ‘T‘; bootSector->bsFileSystemType[3] = ‘1‘; bootSector->bsFileSystemType[4] = ‘?‘; bootSector->bsFileSystemType[5] = ‘ ‘; bootSector->bsFileSystemType[6] = ‘ ‘; bootSector->bsFileSystemType[7] = ‘ ‘; bootSector->bsFileSystemType[4] = ( fatType == 16 ) ? ‘6‘ : ‘2‘; bootSector->bsSig2[0] = 0x55; bootSector->bsSig2[1] = 0xAA; firstFatSector = (PUCHAR)(bootSector + 1); firstFatSector[0] = (UCHAR)devExt->DiskGeometry.MediaType; firstFatSector[1] = 0xFF; firstFatSector[2] = 0xFF; if (fatType == 16) { firstFatSector[3] = 0xFF; } rootDir = (PDIR_ENTRY)(bootSector + 1 + fatSectorCnt); rootDir->deName[0] = ‘M‘; rootDir->deName[1] = ‘S‘; rootDir->deName[2] = ‘-‘; rootDir->deName[3] = ‘R‘; rootDir->deName[4] = ‘A‘; rootDir->deName[5] = ‘M‘; rootDir->deName[6] = ‘D‘; rootDir->deName[7] = ‘R‘; rootDir->deExtension[0] = ‘I‘; rootDir->deExtension[1] = ‘V‘; rootDir->deExtension[2] = ‘E‘; rootDir->deAttributes = DIR_ATTR_VOLUME; return STATUS_SUCCESS; }
这里我们使用的内存来虚拟磁盘,所以对磁盘的读写等操作最后都映射到内存,以下是3种请求的处理函数部分:
VOID RamDiskEvtIoRead( IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_t Length ) { PDEVICE_EXTENSION devExt = QueueGetExtension(Queue)->DeviceExtension; NTSTATUS Status = STATUS_INVALID_PARAMETER; WDF_REQUEST_PARAMETERS Parameters; LARGE_INTEGER ByteOffset; WDFMEMORY hMemory; _Analysis_assume_(Length > 0); WDF_REQUEST_PARAMETERS_INIT(&Parameters); WdfRequestGetParameters(Request, &Parameters); ByteOffset.QuadPart = Parameters.Parameters.Read.DeviceOffset; if (RamDiskCheckParameters(devExt, ByteOffset, Length)) { Status = WdfRequestRetrieveOutputMemory(Request, &hMemory); if(NT_SUCCESS(Status)){ Status = WdfMemoryCopyFromBuffer(hMemory, // Destination 0, // Offset into the destination devExt->DiskImage + ByteOffset.LowPart, // source Length); } } WdfRequestCompleteWithInformation(Request, Status, (ULONG_PTR)Length); } VOID RamDiskEvtIoWrite( IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_t Length ) { PDEVICE_EXTENSION devExt = QueueGetExtension(Queue)->DeviceExtension; NTSTATUS Status = STATUS_INVALID_PARAMETER; WDF_REQUEST_PARAMETERS Parameters; LARGE_INTEGER ByteOffset; WDFMEMORY hMemory; _Analysis_assume_(Length > 0); WDF_REQUEST_PARAMETERS_INIT(&Parameters); WdfRequestGetParameters(Request, &Parameters); ByteOffset.QuadPart = Parameters.Parameters.Write.DeviceOffset; if (RamDiskCheckParameters(devExt, ByteOffset, Length)) { Status = WdfRequestRetrieveInputMemory(Request, &hMemory); if(NT_SUCCESS(Status)){ Status = WdfMemoryCopyToBuffer(hMemory, // Source 0, // offset in Source memory where the copy has to start devExt->DiskImage + ByteOffset.LowPart, // destination Length); } } WdfRequestCompleteWithInformation(Request, Status, (ULONG_PTR)Length); } VOID RamDiskEvtIoDeviceControl( IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_t OutputBufferLength, IN size_t InputBufferLength, IN ULONG IoControlCode ) { NTSTATUS Status = STATUS_INVALID_DEVICE_REQUEST; ULONG_PTR information = 0; size_t bufSize; PDEVICE_EXTENSION devExt = QueueGetExtension(Queue)->DeviceExtension; UNREFERENCED_PARAMETER(OutputBufferLength); UNREFERENCED_PARAMETER(InputBufferLength); switch (IoControlCode) { case IOCTL_DISK_GET_PARTITION_INFO: { PPARTITION_INFORMATION outputBuffer; PBOOT_SECTOR bootSector = (PBOOT_SECTOR) devExt->DiskImage; information = sizeof(PARTITION_INFORMATION); Status = WdfRequestRetrieveOutputBuffer(Request, sizeof(PARTITION_INFORMATION), &outputBuffer, &bufSize); if(NT_SUCCESS(Status) ) { outputBuffer->PartitionType = (bootSector->bsFileSystemType[4] == ‘6‘) ? PARTITION_FAT_16 : PARTITION_FAT_12; outputBuffer->BootIndicator = FALSE; outputBuffer->RecognizedPartition = TRUE; outputBuffer->RewritePartition = FALSE; outputBuffer->StartingOffset.QuadPart = 0; outputBuffer->PartitionLength.QuadPart = devExt->DiskRegInfo.DiskSize; outputBuffer->HiddenSectors = (ULONG) (1L); outputBuffer->PartitionNumber = (ULONG) (-1L); Status = STATUS_SUCCESS; } } break; case IOCTL_DISK_GET_DRIVE_GEOMETRY: { PDISK_GEOMETRY outputBuffer; information = sizeof(DISK_GEOMETRY); Status = WdfRequestRetrieveOutputBuffer(Request, sizeof(DISK_GEOMETRY), &outputBuffer, &bufSize); if(NT_SUCCESS(Status) ) { RtlCopyMemory(outputBuffer, &(devExt->DiskGeometry), sizeof(DISK_GEOMETRY)); Status = STATUS_SUCCESS; } } break; case IOCTL_DISK_CHECK_VERIFY: case IOCTL_DISK_IS_WRITABLE: Status = STATUS_SUCCESS; break; } WdfRequestCompleteWithInformation(Request, Status, information); }
设备的清除回调函数比较简单,主要进行释放分配的内存操作:
VOID RamDiskEvtDeviceContextCleanup( IN WDFOBJECT Device ) { PDEVICE_EXTENSION pDeviceExtension = DeviceGetExtension(Device); PAGED_CODE(); if(pDeviceExtension->DiskImage) { ExFreePool(pDeviceExtension->DiskImage); } }
从注册表中读取配置参数的代码如下:
VOID RamDiskQueryDiskRegParameters( _In_ PWSTR RegistryPath, _In_ PDISK_INFO DiskRegInfo ) { RTL_QUERY_REGISTRY_TABLE rtlQueryRegTbl[5 + 1]; // Need 1 for NULL NTSTATUS Status; DISK_INFO defDiskRegInfo; PAGED_CODE(); ASSERT(RegistryPath != NULL); defDiskRegInfo.DiskSize = DEFAULT_DISK_SIZE; defDiskRegInfo.RootDirEntries = DEFAULT_ROOT_DIR_ENTRIES; defDiskRegInfo.SectorsPerCluster = DEFAULT_SECTORS_PER_CLUSTER; RtlInitUnicodeString(&defDiskRegInfo.DriveLetter, DEFAULT_DRIVE_LETTER); RtlZeroMemory(rtlQueryRegTbl, sizeof(rtlQueryRegTbl)); rtlQueryRegTbl[0].Flags = RTL_QUERY_REGISTRY_SUBKEY; rtlQueryRegTbl[0].Name = L"Parameters"; rtlQueryRegTbl[0].EntryContext = NULL; rtlQueryRegTbl[0].DefaultType = (ULONG_PTR)NULL; rtlQueryRegTbl[0].DefaultData = NULL; rtlQueryRegTbl[0].DefaultLength = (ULONG_PTR)NULL; rtlQueryRegTbl[1].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[1].Name = L"DiskSize"; rtlQueryRegTbl[1].EntryContext = &DiskRegInfo->DiskSize; rtlQueryRegTbl[1].DefaultType = REG_DWORD; rtlQueryRegTbl[1].DefaultData = &defDiskRegInfo.DiskSize; rtlQueryRegTbl[1].DefaultLength = sizeof(ULONG); rtlQueryRegTbl[2].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[2].Name = L"RootDirEntries"; rtlQueryRegTbl[2].EntryContext = &DiskRegInfo->RootDirEntries; rtlQueryRegTbl[2].DefaultType = REG_DWORD; rtlQueryRegTbl[2].DefaultData = &defDiskRegInfo.RootDirEntries; rtlQueryRegTbl[2].DefaultLength = sizeof(ULONG); rtlQueryRegTbl[3].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[3].Name = L"SectorsPerCluster"; rtlQueryRegTbl[3].EntryContext = &DiskRegInfo->SectorsPerCluster; rtlQueryRegTbl[3].DefaultType = REG_DWORD; rtlQueryRegTbl[3].DefaultData = &defDiskRegInfo.SectorsPerCluster; rtlQueryRegTbl[3].DefaultLength = sizeof(ULONG); rtlQueryRegTbl[4].Flags = RTL_QUERY_REGISTRY_DIRECT; rtlQueryRegTbl[4].Name = L"DriveLetter"; rtlQueryRegTbl[4].EntryContext = &DiskRegInfo->DriveLetter; rtlQueryRegTbl[4].DefaultType = REG_SZ; rtlQueryRegTbl[4].DefaultData = defDiskRegInfo.DriveLetter.Buffer; rtlQueryRegTbl[4].DefaultLength = 0; Status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, RegistryPath, rtlQueryRegTbl, NULL, NULL ); if (NT_SUCCESS(Status) == FALSE) { DiskRegInfo->DiskSize = defDiskRegInfo.DiskSize; DiskRegInfo->RootDirEntries = defDiskRegInfo.RootDirEntries; DiskRegInfo->SectorsPerCluster = defDiskRegInfo.SectorsPerCluster; RtlCopyUnicodeString(&DiskRegInfo->DriveLetter, &defDiskRegInfo.DriveLetter); } KdPrint(("DiskSize = 0x%lx\n", DiskRegInfo->DiskSize)); KdPrint(("RootDirEntries = 0x%lx\n", DiskRegInfo->RootDirEntries)); KdPrint(("SectorsPerCluster = 0x%lx\n", DiskRegInfo->SectorsPerCluster)); KdPrint(("DriveLetter = %wZ\n", &(DiskRegInfo->DriveLetter))); return; }
磁盘虚拟的驱动代码在这里作为WDF编程方式的总结案例,它的应用场景也是非常诱人的,实现数据隐藏、Sandbox、网吧无盘系统...这个代码使用内存作为磁盘存储介质,其实这个介质还可以是文件、注册表、网络文件等,实现功能而定了。
最后分享几个网站
收录一些Undocumented Kernel Data Structure的网站 http://www.nirsoft.net/kernel_struct/vista/index.html
张银奎先生关于软件调试内容的网站 http://advdbg.org/default.aspx