{"id":19081048,"url":"https://github.com/apriorit/virtual-disk-driver-for-windows","last_synced_at":"2025-04-14T00:36:41.511Z","repository":{"id":261781588,"uuid":"885318696","full_name":"apriorit/virtual-disk-driver-for-windows","owner":"apriorit","description":null,"archived":false,"fork":false,"pushed_at":"2024-11-08T11:19:44.000Z","size":801,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-27T14:52:02.452Z","etag":null,"topics":["article","driver","virtual-disk","windows"],"latest_commit_sha":null,"homepage":"https://www.apriorit.com/dev-blog/766-driver-development-virtual-disk-driver","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apriorit.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-08T11:03:54.000Z","updated_at":"2025-02-06T10:14:20.000Z","dependencies_parsed_at":"2024-11-08T12:26:19.393Z","dependency_job_id":"bd653d8e-d32d-4345-b997-d02bff2bee1b","html_url":"https://github.com/apriorit/virtual-disk-driver-for-windows","commit_stats":null,"previous_names":["apriorit/virtual-disk-driver-for-windows"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apriorit%2Fvirtual-disk-driver-for-windows","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apriorit%2Fvirtual-disk-driver-for-windows/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apriorit%2Fvirtual-disk-driver-for-windows/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apriorit%2Fvirtual-disk-driver-for-windows/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apriorit","download_url":"https://codeload.github.com/apriorit/virtual-disk-driver-for-windows/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248803188,"owners_count":21164004,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["article","driver","virtual-disk","windows"],"created_at":"2024-11-09T02:32:43.219Z","updated_at":"2025-04-14T00:36:41.482Z","avatar_url":"https://github.com/apriorit.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# How to develop a Virtual Disk Driver for Windows\n\n- [How to develop a Virtual Disk Driver for Windows](#how-to-develop-a-virtual-disk-driver-for-windows)\n  - [Introduction](#introduction)\n    - [What is a driver?](#what-is-a-driver)\n    - [What is a device?](#what-is-a-device)\n    - [What is a virtual disk?](#what-is-a-virtual-disk)\n    - [What is WDF?](#what-is-wdf)\n    - [C++ in drivers](#c-in-drivers)\n  - [Prerequisites](#prerequisites)\n  - [Developing](#developing)\n    - [Driver](#driver)\n      - [Pch](#pch)\n      - [Main](#main)\n      - [Driver class](#driver-class)\n      - [Device class](#device-class)\n        - [Create](#create)\n        - [Init](#init)\n        - [IOCTLs](#ioctls)\n        - [Read/write](#readwrite)\n      - [Property keys](#property-keys)\n      - [NewImpl](#newimpl)\n    - [Control utility](#control-utility)\n  - [Running](#running)\n  - [Final notes](#final-notes)\n  - [References](#references)\n\n## Introduction\n\nIn this article we'll create a driver for a virtual disk device and a control utility to manage it.\n\n![Introduction](img/intro.png)\n\nThe target operating system is Windows 10. The goal is to demonstrate the following topics:\n\n* how to write a driver?\n* using C++ in drivers\n* using WDF\n* handling IOCTLs\n* understanding of device stacks\n* using Software Device API\n\n### What is a driver?\nA driver is a software component that works tightly with an OS. With the help of drivers an OS exposes functionality of various hardware to user applications. Some drivers have no actual hardware: they emulate it or alter behavior of other drivers.\n\n### What is a device?\nHardware components are called devices (for example CD-ROM or USB flash drive). Drivers create software objects that represent those hardware components and they are also called devices. So a device can be referred to software as well as to hardware.\n\n### What is a virtual disk?\nA disk device provides storage for data. Usually it isn't used directly but by the means of a file system. Thus we can work with files and directories and see disk devices as drive letters: C:, D:, E: and so on. Why is our device virtual? Because it has no real hardware to store data. Instead it stores data in an ordinary file:\n\n![Virtual disk data flow](img/data-flow.png)\n\nVirtual disk drivers are used to mount CD/DVD images (ISO), virtual machine disk images (VHD/VHDX, VMDK, VDI) or to provide an encrypted storage for sensitive data.\n\n### What is WDF?\nWDF is a Windows Driver Framework (formerly called Foundation) developed by Microsoft. It greatly simplifies driver development. For example it reduces a dummy plug-n-play driver from 3000 to 300 lines of code. So we highly recommend using WDF instead of low-level WDM.\n\nWDF consists of 2 parts:\n\n* KMDF (Kernel-Mode Driver Framework)\n* UMDF (User-Mode Driver Framework)\n\nFor our driver we'll use KMDF and thus it will run in kernel mode.\n\n### C++ in drivers\nSome time ago Windows drivers were written only on C. But nowadays C++ gets into this domain as a more advanced and less error-prone language. Our driver will be written on C++ however only a very basic functionality of the language will be used. So the code will be understandable to the people who don't know C++ but know C.\n\n## Prerequisites\nThings you need to have before we begin:\n\n* Windows 10\n* Visual Studio 2019\n* WDK 10\n* basic C/C++ knowledge\n* virtual machine (Hyper-V, VmWare, VirtualBox, QEMU, ...)\n\nAlways use a virtual machine to run the driver you develop! That way you can protect your machine from driver failures.\n\n## Developing\n\nIt's very recommended to setup kernel-mode debugging. You can refer to the official documentation about that: [Getting Started with Windows Debugging](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/getting-started-with-windows-debugging).\n\n### Driver\nA driver project has its own template in Visual Studio. So we will use it. A typical driver project has a bunch of C/CPP and H files with code, RC file for resources and INF file for installation instructions.\n\n![Create a new project](img/create-a-new-project.png)\n\nNow let's go through our virtual disk driver code and say a couple of words about each part of it.\n\n#### Pch\nThis is a precompiled header. It contains includes of system headers and reduces build times as it is compiled only once (and not for each CPP file).\n\n#### Main\n`Main.cpp` contains a driver entry point: the very first driver function that will be called by the Windows kernel. What it does is delegates all the work to our `Driver` class. Note that `DriverEntry` is marked as an `EXTERN_C` function:\n\n```cpp\nEXTERN_C NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT driverObject, _In_ PUNICODE_STRING registryPath)\n{\n    return Driver::create(driverObject, registryPath);\n}\n```\n\n#### Driver class\n`Driver` is a simple class that has only 2 methods:\n\n* `Driver::create` - Initializes a driver object by calling a corresponding WDF function and registers `Driver::onDeviceAdd` callback.\n* `Driver::onDeviceAdd` - A callback that is called for each device object that matches our driver; there we are creating an instance of our `Device` class.\n\n```cpp\nNTSTATUS Driver::create(PDRIVER_OBJECT driverObject, PUNICODE_STRING registryPath)\n{\n    WDF_DRIVER_CONFIG config;\n    WDF_DRIVER_CONFIG_INIT(\u0026config, onDeviceAdd);\n\n    return WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, \u0026config, WDF_NO_HANDLE);\n}\n\nNTSTATUS Driver::onDeviceAdd(WDFDRIVER, PWDFDEVICE_INIT deviceInit)\n{\n    return Device::create(deviceInit);\n}\n```\n\n#### Device class\nThis is where all the work is done.\n\nDevice objects in Windows are layered and form stacks. From below our device object is attached to the so-called physical device object. In our case it belongs to the SoftwareDevice driver and its role is just to report a hardware id to the system so our driver will be matched to that hardware id, loaded and our `Driver::onDeviceAdd` will be called. From above our device is attached (mounted) to the volume device object that belongs to the file system driver. Also note that it forms another device stack. The volume device object is seen by applications as a drive letter.\n\nIn reality there are also filter device objects in both storage and filesystem stacks. They add additional functionality like providing disk snapshots for backup or checking files for viruses.\n\n![Device stack](img/device-stack.png)\n\n##### Create\n`Device::create` method sets device name, security and type. After that it calls `WdfDeviceCreate` to create a device object and allocate a memory for the instance of our `Device` class. Also it registers `Device::onCleanup` handler that will cleanup resources by calling the class destructor.\n\nWhen the instance of our `Device` class is created we call `Device::init` to continue initialization:\n\n```cpp\n//\n// Create device\n//\n\nWDF_OBJECT_ATTRIBUTES deviceAttributes;\nWDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(\u0026deviceAttributes, Device);\ndeviceAttributes.EvtCleanupCallback = onCleanup;\n\nWDFDEVICE wdfDevice;\nstatus = WdfDeviceCreate(\u0026deviceInit, \u0026deviceAttributes, \u0026wdfDevice);\nif (!NT_SUCCESS(status))\n{\n    return status;\n}\n\n//\n// Initialize device\n//\n\nauto self = new(getDevice(wdfDevice)) Device();\n\nstatus = self-\u003einit(wdfDevice);\nif (!NT_SUCCESS(status))\n{\n    return status;\n}\n```\n\n##### Init\n`Device::init` reads the device property key where we store a path to the file we're going to use as a virtual disk. Then it opens the file, gets its size, creates a device interface with the system predefined type `GUID_DEVINTERFACE_VOLUME` and creates 2 queues for processing requests: the default queue and the file queue (we'll get back to them later).\n\n##### IOCTLs\nIOCTLs are used to report meta-information about our device (disk size, writability, geometry and etc):\n\n```cpp\nvoid Device::onIoDeviceControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLength, size_t, ULONG ioControlCode)\n{\n    NTSTATUS status = STATUS_SUCCESS;\n    ULONG_PTR bytesWritten = 0;\n    auto self = getDevice(queue);\n\n    //\n    // Handle required control codes\n    //\n\n    switch (ioControlCode)\n    {\n    case IOCTL_STORAGE_GET_DEVICE_NUMBER:\n    {\n        STORAGE_DEVICE_NUMBER* info;\n        status = WdfRequestRetrieveOutputBuffer(request, sizeof(*info), reinterpret_cast\u003cvoid**\u003e(\u0026info), nullptr);\n        if (!NT_SUCCESS(status))\n        {\n            break;\n        }\n\n        info-\u003eDeviceType = FILE_DEVICE_DISK;\n        info-\u003eDeviceNumber = MAXULONG;\n        info-\u003ePartitionNumber = MAXULONG;\n\n        bytesWritten = sizeof(*info);\n        break;\n    }\n    ...\n```\n\nThe list of implemented IOCTL codes:\n\n* IOCTL_STORAGE_GET_DEVICE_NUMBER\n* IOCTL_STORAGE_GET_HOTPLUG_INFO\n* IOCTL_DISK_GET_LENGTH_INFO\n* IOCTL_DISK_GET_MEDIA_TYPES\n* IOCTL_DISK_GET_DRIVE_GEOMETRY\n* IOCTL_DISK_IS_WRITABLE\n* IOCTL_MOUNTDEV_QUERY_DEVICE_NAME\n* IOCTL_MOUNTDEV_QUERY_UNIQUE_ID\n\nThe system uses more IOCTLs but they are not required for the basic functionality. Because not all IOCTLS are implemented, advanced disk features (like those can be found in the Disk Management snap-in) will not work.\n\n##### Read/write\nDevice read/write handlers get buffer, length and offset and perform read/write with those parameters from the disk image file:\n\n```cpp\nvoid Device::onIoRead(WDFQUEUE queue, WDFREQUEST request, size_t length)\n{\n    //\n    // Get buffer and parameters\n    //\n\n    PVOID outputBuffer;\n    NTSTATUS status = WdfRequestRetrieveOutputBuffer(request, 0, \u0026outputBuffer, nullptr);\n    if (!NT_SUCCESS(status))\n    {\n        WdfRequestCompleteWithInformation(request, status, 0);\n        return;\n    }\n\n    WDF_REQUEST_PARAMETERS requestParams;\n    WDF_REQUEST_PARAMETERS_INIT(\u0026requestParams);\n    WdfRequestGetParameters(request, \u0026requestParams);\n\n    //\n    // Read from file\n    //\n\n    IO_STATUS_BLOCK iosb{};\n    status = ZwReadFile(getDevice(queue)-\u003em_fileHandle,\n        nullptr,\n        nullptr,\n        nullptr,\n        \u0026iosb,\n        outputBuffer,\n        static_cast\u003cULONG\u003e(length),\n        reinterpret_cast\u003cPLARGE_INTEGER\u003e(\u0026requestParams.Parameters.Read.DeviceOffset),\n        nullptr);\n    WdfRequestCompleteWithInformation(request, status, iosb.Information);\n}\n```\n\nBut there is a catch: file operations in the Windows kernel need APC (asynchronous procedure call) to be enabled for the thread. But for the thread where our read/write handler is invoked APC is disabled. So we need another thread to process them.\n\nWe will use the following trick. Our driver has 2 request queues: the default queue and the file queue. The default queue processes all requests. If a request is a read/write operation it forwards the request to the file queue. The default queue can process requests at any IRQL. The file queue supports only PASSIVE_LEVEL IRQL. We will raise IRQL to DISPATCH_LEVEL before forwarding the read/write request thus WDF couldn't process the request in the current thread and will use a worker thread.\n\n![Queues](img/queues.png)\n\nThis is how the forward function looks:\n\n```cpp\nvoid Device::onIoReadWriteForward(WDFQUEUE queue, WDFREQUEST request, size_t)\n{\n    //\n    // Forward read/write requests to the file i/o queue. To force processing in another thread raise IRQL.\n    //\n\n    KIRQL oldIrql;\n    KeRaiseIrql(DISPATCH_LEVEL, \u0026oldIrql);\n    WdfRequestForwardToIoQueue(request, getDevice(queue)-\u003em_fileQueue);\n    KeLowerIrql(oldIrql);\n}\n```\n\n#### Property keys\nWe need somehow to pass a disk file image path to the driver for device initialization. Device properties are a convenient mechanism for that. `PropertyKeys.h` contains a definition of our device property key and it's shared between the driver and the control utility.\n\n```cpp\n// Use this property to pass a disk image file path to the driver\nDEFINE_DEVPROPKEY(DEVPKEY_VIRTUALDISK_FILEPATH, 0x8792f614, 0x3667, 0x4df0, 0x95, 0x49, 0x3a, 0xc6, 0x4b, 0x51, 0xa0, 0xdb, 2);\n```\n\nStandard and custom device properties can be seen in the device manager:\n\n![Device properties](img/device-properties.png)\n\n#### NewImpl\nThis helper file provides placing `new` and `delete` operators for C++ support. The implementation is trivial:\n\n```cpp\nvoid* __cdecl operator new(size_t, void* ptr)\n{\n    return ptr;\n}\n\nvoid __cdecl operator delete(void*, size_t)\n{\n}\n```\n\nPlacing `new` is used to call class constructors. The memory should be already allocated (It's done by WDF in our case as it allocates memory for the device context):\n\n```cpp\nauto self = new(getDevice(wdfDevice)) Device();\n```\n\nClass destructors are called explicitly from the WDF cleanup callback:\n\n```cpp\nvoid Device::onCleanup(WDFOBJECT wdfDevice)\n{\n    getDevice(reinterpret_cast\u003cWDFDEVICE\u003e(wdfDevice))-\u003e~Device();\n}\n```\n\nThis mechanism allows moving initialization/deinitialization code to constructors/destructors and having objects in a memory allocated by the framework.\n\n### Control utility\nThe control utility has a simple commandline interface with 2 commands (open and close) and receives a path to the file we're going to use as a virtual disk:\n\n```cpp\nvoid printHelp()\n{\n    cout\n        \u003c\u003c \"Virtual disk control utility. Copyright(C) 2022 Apriorit, Inc.\" \u003c\u003c endl\n        \u003c\u003c endl\n        \u003c\u003c \"Usage: \" \u003c\u003c endl\n        \u003c\u003c \"  VirtualDiskControl open \u003cfilepath\u003e [filesize] - Open an existing disk image or create a new one\" \u003c\u003c endl\n        \u003c\u003c \"                                                  with the size `filesize` MB.\" \u003c\u003c endl\n        \u003c\u003c \"                                                  `filesize` is optional, default value is 100.\" \u003c\u003c endl\n        \u003c\u003c \"  VirtualDiskControl close \u003cfilepath\u003e           - Close a disk image.\" \u003c\u003c endl;\n}\n```\n\nHow does the control utility load our driver? It uses [Software Device API](https://docs.microsoft.com/en-us/windows/win32/swdevice/software-device-api-portal) for that. It calls [SwDeviceCreate](https://docs.microsoft.com/en-us/windows/win32/api/swdevice/nf-swdevice-swdevicecreate) to instruct the system-provided SoftwareDevice driver to create a new physical device object with the specific hardware id:\n\n```cpp\nconst wchar_t kHardwareIds[] = L\"Root\\\\AprioritVirtualDisk\\0\";\n```\n\nThen the system will search in the driver database the driver matching that hardware id and load it. Also the control utility sets the device property to pass the target file path to the driver:\n\n```cpp\nconst DEVPROPERTY devPropFilePath\n{\n    .CompKey = { DEVPKEY_VIRTUALDISK_FILEPATH, DEVPROP_STORE_SYSTEM, 0 },\n    .Type = DEVPROP_TYPE_STRING,\n    .BufferSize = static_cast\u003cULONG\u003e((fullFilePath.size() + 1) * sizeof(wchar_t)),\n    .Buffer = const_cast\u003cwchar_t*\u003e(fullFilePath.c_str()),\n};\n```\n\nThere can be several devices at the same time. They are distinguished by instance ids. We use a file path hash as an instance id:\n\n```cpp\nconst auto instanceId = to_wstring(hash\u003cwstring\u003e{}(filePath));\n```\n\nDevice lifetime is controlled by the [SwDeviceSetLifetime](https://docs.microsoft.com/en-us/windows/win32/api/swdevice/nf-swdevice-swdevicesetlifetime) function.\n\n## Running\nFire up a virtual machine with Windows 10 and disable driver signature enforcement in the Windows startup settings (it's not required if kernel debugging is active):\n\n![Startup settings](img/startup-settings.png)\n\nThen copy the following files (make sure that they have the same bitness as the OS):\n\n* VirtualDisk.inf\n* VirtualDisk.sys\n* VirtualDiskControl.exe\n\nInstall the driver by right clicking on the INF file and selecting Install from the menu:\n\n![Install](img/install.png)\n\nClick OK several times until the final dialog appears:\n\n![Install finished](img/install-finish.png)\n\nNow our driver is added to the computer driver database. It's time to open a command prompt console with administrator rights and use our control utility:\n\n![Open disk image](img/open-disk-image.png)\n\nA newly created virtual disk is not formatted, so format it:\n\n![Format prompt](img/format-prompt.png)\n\nAfter that we can use it:\n\n![Disk in my computer](img/disk-in-my-computer.png)\n\nAlso we can check our device in the Device Manager:\n\n![Device manager](img/device-manager.png)\n\nWhen the virtual disk is not needed anymore close it:\n\n![Close disk image](img/close-disk-image.png)\n\n## Final notes\nDriver development is fun! But it requires specific knowledge and deep understanding of OS internals. Enjoy studying our virtual disk driver but note that it's a technology demo with academic purpose and not intended to be used in production as is.\n\n## References\n\n* [RAMDisk Storage Driver Sample](https://github.com/microsoftarchive/msdn-code-gallery-microsoft/blob/master/Official%20Windows%20Driver%20Kit%20Sample/Windows%20Driver%20Kit%20(WDK)%208.1%20Samples/%5BC%2B%2B%5D-windows-driver-kit-81-cpp/WDK%208.1%20C%2B%2B%20Samples/RAMDisk%20Storage%20Driver%20Sample/C%2B%2B/src/ramdisk.c)\n* [Getting Started with Windows Debugging](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/getting-started-with-windows-debugging)\n* [Types of WDM Device Objects](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/types-of-wdm-device-objects)\n* [Using WDF to Develop a Driver](https://docs.microsoft.com/en-us/windows-hardware/drivers/wdf/using-the-framework-to-develop-a-driver)\n* [IOCTL_DISK_XXX](https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntdddisk/)\n* [IOCTL_STORAGE_XXX](https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddstor/)\n* [Software Device API](https://docs.microsoft.com/en-us/windows/win32/swdevice/software-device-api-portal)\n* [Types of APCs](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/types-of-apcs)\n* [Placement syntax](https://en.wikipedia.org/wiki/Placement_syntax) ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapriorit%2Fvirtual-disk-driver-for-windows","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapriorit%2Fvirtual-disk-driver-for-windows","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapriorit%2Fvirtual-disk-driver-for-windows/lists"}