Windows driver development: FsFilter

Windows driver development: FsFilter prohibits reading U disk files

With the popularity of computers, USB flash drives have gradually become an important data storage device for people. However, in companies and government units, due to security considerations, sometimes it is necessary to prohibit employees or users from using U disks. At this time, we can intercept all U disk read and write operations by developing a method based on the FsFilter driver. This article will specifically introduce how to develop the FsFilter driver under the Windows platform, taking the prohibition of reading U disk data as an example.

What is FsFilter

FsFilter is a driver framework provided by the Windows operating system, which allows developers to participate in the process of file system input and output (I/O) operations, by inserting filter drivers in the I/O stack to capture and analyze file system operations. I/O operations.

 + -------------- +
                             | Applications |
                              + --------------+
                                    |
                                    |
                                    V
                              + --------------+
                             | File System |
                              + --------------+
                                    |
                                    |
                                    V
                              + --------------+
                             | D R I V E R |
                              + --------------+
                                    |
                                    |
                                    V
            + ------------------ + + -------------- +
           | FsFilter driver | | Target driver |
            + ------------------ + + -------------- +
           | Upper layer IoComplete + ------> native I/O operation |
           | I/O operation interception | | + -------------- +
           | and forwarding processing + ------> upper layer I/O operation |
            + ------------------ + | (such as Cmd, Explorer, etc.) |
                 | ^ | + --------------+
                 | | |
                 V | |
            + ------------------+
           | MiniFilter driver |
            + ------------------+
           | File System |
           | I/O operations |
           | Filtration |
            + ------------------+

Among them, the upper application program and the file system access the disk device through the driver program and the target driver program. The FsFilter driver provides the ability of I/O operation interception and forwarding processing to the upper application program, the file system and the lower target driver. The MiniFilter driver implements I/O operation filtering and monitoring at the file system level.
As shown in the figure above, the FsFilter driver framework mechanism requires developers to specify a BaseFileSystem driver in the registry. This driver must be a MiniFilter driver, because the MiniFilter driver only supports one, and the path of the registry key is:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\FilterDrivers

When the file system operation is triggered, the BaseFileSystem driver will construct an I/O stack data structure, and the FsFilter driver will intercept the I/O operation by inserting its own driver object into the I/O stack. By calling the NT kernel API to pass the I/O operation data to the next driver, it realizes the monitoring of all file system I/O operations.

Design implementation

In this article, the driver we want to develop is mainly divided into two parts:

  • Monitor U disk insertion and pull out events, and determine whether the U disk exists and its serial number;
  • Intercept the I/O operation of reading data from the U disk.

1. Monitor U disk insertion and removal events

In order to intercept the U disk I/O operation, we need to detect the insertion and removal of the U disk first. The Windows operating system provides some APIs to receive such events, as follows:

  • IoRegisterPlugPlayontification: Register the Plug and Play notification callback function of the device manager;
  • IoGetCurrentIrpStackLocation: Get the IO_STACK_LOCATION component in the stack structure of the current IRP, which is used to get device attributes;
  • RtlInitUnicodeString: Convert a C string of char type to a Unicode string;
  • IoAttachDeviceToDeviceStackSafe: Get the bottom device object of the device stack.

The key code is as follows:

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    //1. Initialize the driver
    //...

    //2. Register the Plug and Play notification callback function of the device manager
    UNICODE_STRING uniDevName;
    RtlInitUnicodeString( &uniDevName, L"\DosDevices\S8FsFilter");
    IoRegisterPlugPlayontification(
        EventCategoryDeviceInterfaceChange, //register device event
        PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, //Include existing device interfaces
         & amp;uniDevName, //The name of the device to monitor
        pDriverObject, //driver object
        FsFilterPnPontificationCallback, //callback function
        NULL); //The context of the callback function

    //3. Return the driver without unloading
    return STATUS_SUCCESS;
}

NTSTATUS FsFilterPnPontificationCallback(PVOID pontificationStructure, PVOID pContext)
{
    //1. Get device information
    PDEVICE_INTERFACE_CHANGEIONTIFICATION pontification = (PDEVICE_INTERFACE_CHANGEIONTIFICATION)pontificationStructure;
    ULONG uSymbolicLinkNameLen = pontification->SymbolicLinkName->Length / sizeof(WCHAR);
    PWCHAR pSymbolicLinkName = (PWCHAR)ExAllocatePoolWithTag(PagedPool, pontification->SymbolicLinkName->Length, 'link');
    RtlCopyMemory(pSymbolicLinkName, pontification->SymbolicLinkName->Buffer, pontification->SymbolicLinkName->Length);

    //2. Determine whether the device is a U disk
    PFILE_OBJECT pFileObj;
    PDEVICE_OBJECT pDevObj;
    if (NT_SUCCESS(IoGetDeviceObjectPointer(pontification->SymbolicLinkName,
                                             FILE_READSATTRIBUTES,
                                              &pFileObj,
                                              &pDevObj)))
    {
        //3. Determine whether the device is a U disk
        ULONG uRemovalPolicy;
        NTSTATUS status = IoGetDeviceProperty(pDevObj,
                                               DevicePropertyRemovalPolicy,
                                               sizeof(uRemovalPolicy),
                                                &uRemovalPolicy,
                                               NULL);
        if (NT_SUCCESS(status) & amp; & amp; uRemovalPolicy == RemovalPolicyExpectSurpriseRemoval) //It is a U disk
        {
            //4. Unplug event processing
            if (pontification->Event ==_DEVI_DEVICE_INTERFACE_REMOVAL) //Pull out event
            {
                //5. Delete U disk information
                RemoveUsbDevice(pSymbolicLinkName);
            }
            //6. Insert event handler
            else if (pontification->Event == GUID_DEVICE_INTERFACE_ARRIVAL) //insert event
            {
                //7. Get the serial number of the U disk
                PWCHAR pId;
                if (NT_SUCCESS(IoGetDeviceProperty(pDevObj,
                                                   DevicePropertyHardwareID,
                                                   0,
                                                   NULL,
                                                    &uSymbolicLinkNameLen)))
                {
                    pId = (PWCHAR)ExAllocatePoolWithTag(PagedPool, uSymbolicLinkNameLen * sizeof(WCHAR), 'id');
                    if (NT_SUCCESS(IoGetDeviceProperty(pDevObj,
                                                       DevicePropertyHardwareID,
                                                       uSymbolicLinkNameLen * sizeof(WCHAR),
                                                       pId,
                                                        &uSymbolicLinkNameLen)))
                    {
                        //8. Save U disk information
                        AddUsbDevice(pSymbolicLinkName, pId);
                    }
                    ExFreePoolWithTag(pId, 'id');
                }
            }
        }
    }

    //9. Release the memory
    ExFreePoolWithTag(pSymbolicLinkName, 'link');

    return STATUS_SUCCESS;
}

2. Intercept the I/O operation of reading U disk data

In order to intercept the U disk I/O operation, we need to insert the FsFilter driver object into the I/O stack of the target device. When the operating system accesses the U disk device for the first time and performs I/O operations, the FsFilter driver will be called by the system to establish an I/O stack structure for the next I/O operation, and insert its own device object into The middle of the I/O stack.

The key code is as follows:

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    //...

    //2. Initialize the filter object
    NTSTATUS status = IoCreateDevice(pDriverObject, //current driver object
                                      0, //Device extension size
                                      NULL, //device name
                                      FILE_DEVICE_UNKNOWN, //device type
                                      FILE_DEVICE_SECURE_OPEN, //Device characteristics
                                      FALSE, // does not exclude synchronous operations
                                       & amp;g_pFilterDevice); //Filter device object
    if (!NT_SUCCESS(status))
    {
        return status;
    }
    
    //3. Insert the device object into all readable U disk device I/O stacks
    for (PLIST_ENTRY pCurEntry = g_pUsbDevList->Flink; pCurEntry != g_pUsbDevList; pCurEntry = pCurEntry->Flink)
    {
        PUSBDEVINFO pUsbDevInfo = CONTAINING_RECORD(pCurEntry, USBDEVINFO, ListEntry);
        status = InsertFilterDeviceObject(pUsbDevInfo->pSymbolicLinkName, g_pFilterDevice);
        if (!NT_SUCCESS(status))
        {
            //4. Delete all filter device objects corresponding to the U disk
            for (PLIST_ENTRY pCurEntry2 = g_pUsbDevList->Flink; pCurEntry2 != pCurEntry & amp; & amp; pCurEntry2 != g_pUsbDevList; pCurEntry2 = pCurEntry2->Flink)
            {
                PUSBDEVINFO pUsbDevInfo2 = CONTAINING_RECORD(pCurEntry2, USBDEVINFO, ListEntry);
                RemoveFilter(pUsbDevInfo2->pSymbolicLinkName); //Remove filter
            }
            IoDeleteDevice(g_pFilterDevice); //Delete Filter device object
            return status;
        }
    }

    return STATUS_SUCCESS;
}

NTSTATUS InsertFilterDeviceObject(PUNICODE_STRING pDeviceName, PDEVICE_OBJECT pFilterDevice)
{
    PDEVICE_OBJECT pTargetDeviceObj;

    //1. Get the target device object
    NTSTATUS status = IoGetDeviceObjectPointer(pDeviceName, FILE_READSATTRIBUTES, &pTargetFileObject, &pTargetDeviceObj);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //2. Create and initialize a new device object to make it a filter device object
    PDEVICE_OBJECT pNewDeviceObj;
    status = IoCreateDevice(g_pDriverObject, //current driver object
                             0, //The device expansion size is 0
                             NULL, //The device name is NULL
                             pTargetDeviceObj->DeviceType, //The device type is the type of Target device
                             pTargetDeviceObj->Characteristics, //Use the same value as the Characteristics property of the subsequent FileObject object
                             FALSE, // does not exclude synchronous operations
                              & amp;pNewDeviceObj); //New filter object

    //3. Link the new device object to the I/O stack of the Target device
    pNewDeviceObj->Flags |= pTargetDeviceObj->Flags & amp; (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
    pNewDeviceObj->Flags & amp;= ~DO_DEVICE_INITIALIZING;

    DeviceExtension *pDevExt = (DeviceExtension *)pNewDeviceObj->DeviceExtension;
    pDevExt->pTargetDevObj = pTargetDeviceObj;

    IoAttachDeviceToDeviceStack(pNewDeviceObj,
                                 pTargetDeviceObj);

    return STATUS_SUCCESS;
}

NTSTATUS FsFilterIrpPreOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    if (IsDeviceUsb(pDeviceObject)) //It is a U disk device, intercepting I/O operations
    {
        switch (IoGetCurrentIrpStackLocation(pIrp)->MajorFunction)
        {
            case IRP_MJ_READ: //Intercept all read U disk data operations
            {
                PrintLog("Intercept the I/O operation of reading U disk data.\
");
                pIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
                pIrp->IoStatus nformation = 0;
                IoCompleteRequest(pIrp, IO_NO_INCREMENT);
                return STATUS_ACCESS_DENIED;
            }

            //... }
    }

    //...
}

NTSTATUS FsFilterIrpPostOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    //...
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    //...

    //2. Register I/O operation interception function
    for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i ++ )
    {
        pDriverObject->MajorFunction[i] = FsFilterIrpDefaultHandler;
    }

    pDriverObject->MajorFunction[IRP_MJ_READ] = FsFilterIrpPreOperation; //Intercept read U disk data operation
    //...

    return STATUS_SUCCESS;
}

Through the above code, we have completed the key content of the FsFilter driver development, and then integrate them, and then we can compile and run our driver. The integrated driver source code is as follows:

#include <ntifs.h>
#include <Ntstrsafe.h>

#define PrintLog(str) DbgPrint("[S8FsFilter] %s", str)

typedef struct _USBDEVINFO {
    PWCHAR pSymbolicLinkName; //symbolic link name
    PWCHAR pHardwareID; //hardware ID
    LIST_ENTRY ListEntry; //Linked list item
} USBDEVINFO, *PUSBDEVINFO;

typedef struct _DEVICE_EXTENSION {
    PDEVICE_OBJECT pTargetDevObj; //target device object
} DeviceExtension, *PDeviceExtension;

PDRIVER_OBJECT g_pDriverObject = NULL;
UNICODE_STRING g_uniLinkName;
PDEVICE_OBJECT g_pFilterDevice = NULL;
LIST_ENTRY *g_pUsbDevList = NULL;

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath);

NTSTATUS FsFilterPnPontificationCallback(PVOID pontificationStructure, PVOID pContext);

NTSTATUS InsertFilterDeviceObject(PUNICODE_STRING pDeviceName, PDEVICE_OBJECT pFilterDevice);

NTSTATUS FsFilterIrpPreOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp);

NTSTATUS FsFilterIrpPostOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp);

BOOLEAN IsDeviceUsb(PDEVICE_OBJECT pDeviceObject);

VOID AddUsbDevice(PWCHAR pSymbolicLinkName, PWCHAR pHardwareID);

VOID RemoveUsbDevice(PWCHAR pSymbolicLinkName);

VOID RemoveAllUsbDevices();

NTSTATUS FsFilterIrpDefaultHandler(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    //...

    return IoCallDriver(pDevExt->pTargetDevObj, pIrp);
}

VOID Unload(PDRIVER_OBJECT pDriverObject)
{
    removeAllUsbDevices();
    IoDeleteDevice(g_pFilterDevice);
   }

BOOLEAN IsDeviceUsb(PDEVICE_OBJECT pDeviceObject)
{
    KIRQL irql;
    KeAcquireSpinLock( &g_SpinLock, &irql);
    for (PLIST_ENTRY pCurEntry = g_pUsbDevList->Flink; pCurEntry != g_pUsbDevList; pCurEntry = pCurEntry->Flink)
    {
        PUSBDEVINFO pUsbDevInfo = CONTAINING_RECORD(pCurEntry, USBDEVINFO, ListEntry);
        if (pDeviceObject == IoGetRelatedDeviceObject(pUsbDevInfo->pSymbolicLinkName))
        {
            KeReleaseSpinLock( & g_SpinLock, irql);
            return TRUE;
        }
    }
    KeReleaseSpinLock( & g_SpinLock, irql);
    return FALSE;
}

VOID AddUsbDevice(PWCHAR pSymbolicLinkName, PWCHAR pHardwareID)
{
    KIRQL irql;
    KeAcquireSpinLock( &g_SpinLock, &irql);
    PUSBDEVINFO pUsbDevInfo = (PUSBDEVINFO)ExAllocatePoolWithTag(NonPagedPool, sizeof(USBDEVINFO), 'usb');
    if (pUsbDevInfo != NULL)
    {
        pUsbDevInfo->pSymbolicLinkName = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,
                                                                        (wcslen(pSymbolicLinkName) + 1) * sizeof(WCHAR),
                                                                        'link');
        pUsbDevInfo->pHardwareID = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,
                                                                  (wcslen(pHardwareID) + 1) * sizeof(WCHAR),
                                                                  'id');
        if (pUsbDevInfo->pSymbolicLinkName != NULL & amp; & amp; pUsbDevInfo->pHardwareID != NULL)
        {
           RtlCopyMemory(pUsbDevInfo->pSymbolicLinkName, pSymbolicLinkName, (wcslen(pSymbolicLinkName) + 1) * sizeof(WCHAR));
            RtlCopyMemory(pUsbDevInfo->pHardwareID, pHardwareID, (wcslen(pHardwareID) + 1) * sizeof(WCHAR));
            InsertTailList(g_pUsbDevList, & amp;(pUsbDevInfo->ListEntry));
        }
        else
        {
            if (pUsbDevInfo->pSymbolicLinkName != NULL) ExFreePoolWithTag(pUsbDevInfo->pSymbolicLinkName, 'link');
            if (pUsbDevInfo->pHardwareID != NULL) ExFreePoolWithTag(pUsbDevInfo->pHardwareID, 'id');
            if (pUsbDevInfo != NULL) ExFreePoolWithTag(pUsbDevInfo, 'usb');
        }
    }
    KeReleaseSpinLock( & g_SpinLock, irql);
}

VOID RemoveUsbDevice(PWCHAR pSymbolicLinkName)
{
    KIRQL irql;
    KeAcquireSpinLock( &g_SpinLock, &irql);
    for (PLIST_ENTRY pCurEntry = g_pUsbDevList->Flink; pCurEntry != g_pUsbDevList; pCurEntry = pCurEntry->Flink)
    {
        PUSBDEVINFO pUsbDevInfo = CONTAINING_RECORD(pCurEntry, USBDEVINFO, ListEntry);
        if (_wcsicmp(pSymbolicLinkName, pUsbDevInfo->pSymbolicLinkName) == 0)
        {
            RemoveEntryList(pCurEntry);
            ExFreePoolWithTag(pUsbDevInfo->pSymbolicLinkName, 'link');
            ExFreePoolWithTag(pUsbDevInfo->pHardwareID, 'id');
            ExFreePoolWithTag(pUsbDevInfo, 'usb');
            break;
        }
    }
    KeReleaseSpinLock( & g_SpinLock, irql);
}

VOID RemoveAllUsbDevices()
{
    KIRQL irql;
    KeAcquireSpinLock( &g_SpinLock, &irql);
    while (!IsListEmpty(g_pUsbDevList))
    {
        PLIST_ENTRY pCurEntry = RemoveHeadList(g_pUsbDevList);
        PUSBDEVINFO pUsbDevInfo = CONTAINING_RECORD(pCurEntry, USBDEVINFO, ListEntry);
        ExFreePoolWithTag(pUsbDevInfo->pSymbolicLinkName, 'link');
        ExFreePoolWithTag(pUsbDevInfo->pHardwareID, 'id');
        ExFreePoolWithTag(pUsbDevInfo, 'usb');
    }
    KeReleaseSpinLock( & g_SpinLock, irql);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    g_pDriverObject = pDriverObject;

    //1. Initialize the symbolic link name
    RtlInitUnicodeString( &g_uniLinkName, L"\DosDevices\S8FsFilter");

    //2. Create and initialize a new device object to make it a filter device object
    NTSTATUS status = IoCreateDevice(pDriverObject, //current driver object
                                     sizeof(DeviceExtension), //device extension size
                                      & amp;g_uniLinkName, //device name
                                     FILE_DEVICE_UNKNOWN, //device type
                                     FILE_DEVICE_SECURE_OPEN, //The sticcteristics attribute for subsequent creation of FileObject objects
                                     FALSE, // does not exclude synchronous operations
                                      & amp;g_pFilterDevice); //New filter object

    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //3. Register PnP event callback function
    PVOID pCallbackHandle;
    status = IoRegisterPlugPlayontification(EventCategoryDeviceInterfaceChange,
                                            PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
                                             &GUID_DEVINTERFACE_USB_DEVICE,
                                            g_pDriverObject,
                                            FsFilterPnPontificationCallback,
                                            NULL,
                                             &pCallbackHandle);

    if (!NT_SUCCESS(status))
    {
        IoDeleteDevice(g_pFilterDevice);
        return status;
    }

    //4. Initialize the linked list and spin lock
    g_pUsbDevList = (PLIST_ENTRY)ExAllocatePoolWithTag(NonPagedPool, sizeof(LIST_ENTRY), 'list');
   eInitializeListHead(g_pUsbDevList);
    KeInitializeSpinLock( & g_SpinLock);

    //5. Register I/O operation interception function
    for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i ++ )
    {
        pDriverObject->MajorFunction[i] = FsFilterIrpDefaultHandler;
    }

    pDriverObject->MajorFunction[IRP_MJ_READ] = FsFilterIrpPreOperation;

    pDriverObject->DriverUnload = Unload;

    return STATUS_SUCCESS;
}

NTSTATUS FsFilterPnPontificationCallback(PVOID pontificationStructure, PVOID pContext)
{
    PPNPIONTIFICATION pNotif = (PPNPIONTIFICATION)pontificationStructure;

    switch (pNotif->Event)
    {
        case TargetDeviceRelationAdd:
        {
            PDEVICE_RELATIONS pDevRels = (PDEVICE_RELATIONS)pNotif->Data;
            for (ULONG i = 0; i < pDevRels->Count; i ++ )
            {
                UNICODE_STRING uniSymbolicLinkName;
                RtlInitUnicodeString( & amp;uniSymbolicLinkName, pDevRels->Objects[i]->DriverObject->DriverName.Buffer);
                if (wcsstr(uniSymbolicLinkName.Buffer, L"USBSTOR") != NULL) //It is a U disk device
                {
                    WCHAR *pwchHardwareID = NULL;
                    status = IoGetDeviceProperty(pDevRels->Objects[i],
                                                 DevicePropertyHardwareID,
                                                 0,
                                                 NULL,
                                                  &uLen);
                    if (status == STATUS_BUFFER_TOO_SMALL)
                    {
                        pwchHardwareID = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool, uLen, 'hard');
                        if (pwchHardwareID != NULL)
                        {
                            status = IoGetDeviceProperty(pDevRels->Objects[i],
                                                         DevicePropertyHardwareID,
                                                         uLen,
                                                         pwchHardwareID,
                                                         NULL);
                            if (NT_SUCCESS(status)) AddUsbDevice(uniSymbolicLinkName.Buffer, pwchHardwareID);
                            ExFreePoolWithTag(pwchHardwareID, 'hard');
                        }
                        else
                        {
                            PrintLog("FsFilterPnPontificationCallback: Failed to allocate memory for HardwareID.\
");
                        }
                    }
                    else
                    {
                        PrintLog("FsFilterPnPontificationCallback: Failed to get length of HardwareID.\
");
                    }
                }
            }
            break;
        }
        case TargetDeviceRelationRemove:
        {
            PDEVICE_RELATIONS pDevRels = (PDEVICE_RELATIONS)pNotif->Data;
            for (ULONG i = 0; i < pDevRels->Count; i ++ )
            {
                UNICODE_STRING uniSymbolicLinkName;
                RtlInitUnicodeString( & amp;uniSymbolicLinkName, pDevRels->Objects[i]->DriverObject->DriverName.Buffer);
                if (wcsstr(uniSymbolicLinkName.Buffer, L"USBSTOR") != NULL) //It is a U disk device
                {
                    RemoveUsbDevice(uniSymbolicLinkName. Buffer);
                }
            }
            break;
        }
        default:
            break;
    }

    return STATUS_SUCCESS;
}

NTSTATUS InsertFilterDeviceObject(PUNICODE_STRING pDeviceName, PDEVICE_OBJECT pFilterDevice)
{
    PFILE_OBJECT pTargetFileObject = NULL;
    PDEVICE_OBJECT pTargetDeviceObj = NULL;

    //1. Get the target device object
    NTSTATUS status = IoGetDeviceObjectPointer(pDeviceName, FILE_READSATTRIBUTES, &pTargetFileObject, &pTargetDeviceObj);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //2. Create and initialize a new device object to make it a filter device object
    PDEVICE_OBJECT pNewDeviceObj;
    status = IoCreateDevice(g_pDriverObject, //current driver object
                             0, //The device expansion size is 0
                             NULL, //The device name is NULL
                             pTargetDeviceObj->DeviceType, //The device type is the type of Target device
                             pTargetDeviceObj->Characteristics, //Use the same value as the Characteristics property of the subsequent FileObject object
                             FALSE, // does not exclude synchronous operations
                              & amp;pNewDeviceObj); //New filter object

    //3. Link the new device object to the I/O stack of the Target device
    pNewDeviceObj->Flags |= pTargetDeviceObj->Flags & amp; (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE);
    pNewDeviceObj->Flags & amp;= ~DO_DEVICE_INITIALIZING;

    DeviceExtension *pDevExt = (DeviceExtension *)pNewDeviceObj->DeviceExtension;
    pDevExt->pTargetDevObj = pTargetDeviceObj;

    IoAttachDeviceToDeviceStack(pNewDeviceObj,
                                 pTargetDeviceObj);

    return STATUS_SUCCESS;
}

NTSTATUS FsFilterIrpPreOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    if (IsDeviceUsb(pDeviceObject)) //It is a U disk device, intercepting I/O operations
    {
        switch (IoGetCurrentIrpStackLocation(pIrp)->MajorFunction)
        {
            case IRP_MJ_READ: //Intercept all read U disk data operations
            {
                PrintLog("FsFilterIrpPreOperation: Intercepted read U disk data I/O operation.\
");
                pIrp->IoStatus.Status = STATUS_ACCESS_DENIED;
                pIrp->IoStatus nformation = 0;
                IoCompleteRequest(pIrp, IO_NO_INCREMENT);
                return STATUS_ACCESS_DENIED;
            }

            //... }
    }

    //...

    return FsFilterIrpDefaultHandler(pDeviceObject, pIrp);
}

NTSTATUS FsFilterIrpPostOperation(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    //...

    return FsFilterIrpDefaultHandler(pDeviceObject, pIrp);
}

Finally, we need to compile the above code into a driver. Regarding the compilation and loading of the driver, we have described it in the previous article, so I won’t repeat it here. In this way, when we load the compiled driver into the system, we can intercept the reading operation of the U disk. Of course, this is just a simple example, we can modify and expand as needed to achieve more complex I/O operation interception.