Welcome to this new Medium post. In this article, I'll walk you through the implementation of a basic Windows File System Minifilter Driver capable of intercepting file operations at the kernel level

We will use the Windows Filter Manager framework to build a minifilter that hooks file system requests, monitor file access events, and demonstrate how to inspect or block operations such as file creation/opening

By the end of this post, you'll understand the core architecture of minifilter drivers, how callback registration works, and how to start building your own file system monitoring

None

Courses: Learn how offensive development works on Windows OS from beginner to advanced taking our courses, all explained in C++.

Technique Database: Access 70+ real offensive techniques with weekly updates, complete with code, PoCs, and AV scan results:

Modules: Dive deep into essential offensive topics with our modular text-training program! Get a new module every 14 days. Start at just $1.99 per module, or unlock lifetime access to all modules for $100.

Methodology

Before diving into the full source code, let's break down the logic behind the technique at a high level.

To intercept file system operations using a Windows Minifilter Driver, we follow these logical steps:

  1. Register the Minifilter: First, we register our driver with the Windows Filter Manager using FltRegisterFilter. This tells the operating system that our driver wants to participate in the file system filtering pipeline.
  2. Define the Callback Operations: Next, we specify which file system operations we want to intercept by filling the FLT_OPERATION_REGISTRATION array. In this example, we register callbacks for IRP_MJ_CREATE, which is triggered whenever a file is opened or created
  3. Start Filtering: Once registration is complete, we call FltStartFiltering. At this point, the minifilter becomes active and Windows will begin invoking our callbacks whenever the registered operations occur
  4. Inspect and Process File Requests: Inside the pre-operation callback, we retrieve information about the target file being accessed. This allows us to inspect the filename, extension, path, or other metadata before the request reaches the filesystem
  5. Allow or Block the Operation: Finally, based on our filtering logic, we either: Allow the request to continue normally, or Block it by completing the IRP with an error status such as STATUS_ACCESS_DENIED

Code

Full code:

#include <fltKernel.h>
#include <dontuse.h>      
#include <suppress.h>      

#pragma comment(lib, "FltMgr.lib")

#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")
PFLT_FILTER gFilterHandle = NULL;   

DRIVER_UNLOAD                  DriverUnload;
NTSTATUS                       DriverEntry(_In_ PDRIVER_OBJECT DriverObject,
    _In_ PUNICODE_STRING RegistryPath);

FLT_PREOP_CALLBACK_STATUS      PreCreateCallback(
    _Inout_                        PFLT_CALLBACK_DATA    Data,
    _In_                           PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID* CompletionContext);

FLT_POSTOP_CALLBACK_STATUS     PostCreateCallback(
    _Inout_  PFLT_CALLBACK_DATA    Data,
    _In_     PCFLT_RELATED_OBJECTS FltObjects,
    _In_opt_ PVOID                 CompletionContext,
    _In_     FLT_POST_OPERATION_FLAGS Flags);

NTSTATUS                       FilterUnloadCallback(_In_ FLT_FILTER_UNLOAD_FLAGS Flags);

NTSTATUS                       InstanceSetupCallback(
    _In_ PCFLT_RELATED_OBJECTS  FltObjects,
    _In_ FLT_INSTANCE_SETUP_FLAGS Flags,
    _In_ DEVICE_TYPE            VolumeDeviceType,
    _In_ FLT_FILESYSTEM_TYPE    VolumeFilesystemType);

//  Operations array
//  Tells FltMgr which IRPs we want to intercept.

const FLT_OPERATION_REGISTRATION Callbacks[] =
{
    {
        IRP_MJ_CREATE,              // Intercept file open / create
        0,                          // No special flags
        PreCreateCallback,          // Pre-operation  (before the FS sees it)
        PostCreateCallback          // Post-operation (after the FS completes it)
    },

    // Add more operations here as needed, e.g.:
    // { IRP_MJ_WRITE,  0, PreWriteCallback,  NULL },
    // { IRP_MJ_READ,   0, PreReadCallback,   NULL },
    // { IRP_MJ_SET_INFORMATION, 0, PreSetInfoCallback, NULL },

    { IRP_MJ_OPERATION_END }        // Mandatory sentinel
};



const FLT_REGISTRATION FilterRegistration =
{
    sizeof(FLT_REGISTRATION),       // Size
    FLT_REGISTRATION_VERSION,       // Version
    0,                              // Flags
    //   FLTFL_REGISTRATION_DO_NOT_SUPPORT_SERVICE_STOP_ON_UNLOAD

    NULL,                           // ContextRegistration
    Callbacks,                      // Pointer to operations array

    FilterUnloadCallback,           // Called when fltMC unload or DriverUnload

    InstanceSetupCallback,          // Called when filter attaches to a volume
    NULL,                           // InstanceQueryTeardownCallback
    NULL,                           // InstanceTeardownStartCallback
    NULL,                           // InstanceTeardownCompleteCallback
    NULL,                           // GenerateFileNameCallback
    NULL,                           // NormalizeNameComponentCallback
    NULL                            // TransactionNotificationCallback
};


NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT  DriverObject, _In_ PUNICODE_STRING RegistryPath){
    UNREFERENCED_PARAMETER(RegistryPath);

    NTSTATUS status;

    DbgPrint("[BasicMiniFilter] DriverEntry called.\n");

    // Step 1: Register the filter with FltMgr.
    //         This does NOT start interception yet.
    status = FltRegisterFilter(
        DriverObject,
        &FilterRegistration,
        &gFilterHandle);

    if (!NT_SUCCESS(status))
    {
        DbgPrint("[BasicMiniFilter] FltRegisterFilter failed: 0x%08X\n", status);
        return status;
    }

    // Step 2: Start filtering. From this point callbacks will fire.
    status = FltStartFiltering(gFilterHandle);

    if (!NT_SUCCESS(status))
    {
        DbgPrint("[BasicMiniFilter] FltStartFiltering failed: 0x%08X\n", status);
        FltUnregisterFilter(gFilterHandle);
        gFilterHandle = NULL;
        return status;
    }

    DbgPrint("[BasicMiniFilter] Loaded successfully.\n");
    return STATUS_SUCCESS;
}

//  FilterUnloadCallback
//  Called by FltMgr when the filter is about to be unloaded.
//  Must call FltUnregisterFilter to detach from all volumes.

NTSTATUS FilterUnloadCallback(_In_ FLT_FILTER_UNLOAD_FLAGS Flags){
    UNREFERENCED_PARAMETER(Flags);

    DbgPrint("[BasicMiniFilter] FilterUnloadCallback called.\n");

    if (gFilterHandle != NULL)
    {
        FltUnregisterFilter(gFilterHandle);
        gFilterHandle = NULL;
    }

    return STATUS_SUCCESS;
}

//  InstanceSetupCallback
//  Called when the filter is about to attach to a volume.
NTSTATUS InstanceSetupCallback(
    _In_ PCFLT_RELATED_OBJECTS   FltObjects,
    _In_ FLT_INSTANCE_SETUP_FLAGS Flags,
    _In_ DEVICE_TYPE             VolumeDeviceType,
    _In_ FLT_FILESYSTEM_TYPE     VolumeFilesystemType)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(Flags);
    UNREFERENCED_PARAMETER(VolumeDeviceType);

    // Only attach to NTFS volumes.
    // Remove this check to monitor all filesystems (FAT32, ReFS, network, etc.)
    if (VolumeFilesystemType != FLT_FSTYPE_NTFS)
    {
        DbgPrint("[BasicMiniFilter] Skipping non-NTFS volume.\n");
        return STATUS_FLT_DO_NOT_ATTACH;
    }

    DbgPrint("[BasicMiniFilter] Attaching to NTFS volume.\n");
    return STATUS_SUCCESS;
}

//  PreCreateCallback
//  Called BEFORE the filesystem processes an IRP_MJ_CREATE.
FLT_PREOP_CALLBACK_STATUS PreCreateCallback(
    _Inout_                        PFLT_CALLBACK_DATA    Data,
    _In_                           PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID* CompletionContext)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    NTSTATUS status;
    PFLT_FILE_NAME_INFORMATION nameInfo = NULL;

    // Skip kernel-mode opens to avoid recursion and noise
    if (Data->RequestorMode == KernelMode)
    {
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    // Retrieve the normalized file name.
    // FLT_FILE_NAME_QUERY_DEFAULT is safe here (PASSIVE_LEVEL, pre-create).
    status = FltGetFileNameInformation(
        Data,
        FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo);

    if (!NT_SUCCESS(status))
    {
        // Name not available (e.g. unnamed pipe, pagefile) — let it pass
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    // Parse the name info to populate individual fields (Extension, FinalComponent, etc.)
    FltParseFileNameInformation(nameInfo);

    DbgPrint("[BasicMiniFilter] PRE-CREATE: %wZ\n", &nameInfo->Name);

    //  Example: Block access to any file named "blocked.txt"
    UNICODE_STRING blockedName = RTL_CONSTANT_STRING(L"blocked.txt");

    if (RtlEqualUnicodeString(&nameInfo->FinalComponent, &blockedName, TRUE /*case insensitive*/))
    {
        DbgPrint("[BasicMiniFilter] Blocking access to: %wZ\n", &nameInfo->Name);

        // Set the completion status that the caller will see
        Data->IoStatus.Status = STATUS_ACCESS_DENIED;
        Data->IoStatus.Information = 0;

        FltReleaseFileNameInformation(nameInfo);

        // FLT_PREOP_COMPLETE: tells FltMgr to complete the IRP right now,
        // skipping the filesystem and going straight to post-processing / IRP completion.
        return FLT_PREOP_COMPLETE;
    }

    FltReleaseFileNameInformation(nameInfo);

    // Pass through; request post-callback so we can log the result.
    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

//  PostCreateCallback
//  Called AFTER the filesystem has completed IRP_MJ_CREATE.

FLT_POSTOP_CALLBACK_STATUS PostCreateCallback(
    _Inout_  PFLT_CALLBACK_DATA       Data,
    _In_     PCFLT_RELATED_OBJECTS    FltObjects,
    _In_opt_ PVOID                    CompletionContext,
    _In_     FLT_POST_OPERATION_FLAGS Flags)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    // FLTFL_POST_OPERATION_DRAINING: filter is being torn down, do minimal work
    if (FlagOn(Flags, FLTFL_POST_OPERATION_DRAINING))
    {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    DbgPrint("[BasicMiniFilter] POST-CREATE: status=0x%08X\n",
        Data->IoStatus.Status);

    return FLT_POSTOP_FINISHED_PROCESSING;
}

// INSTALL DRIVER

//
//copy "C:\Users\s12de\Documents\Github\kernelutils\MiniFiltersWDM\x64\Debug\MiniFiltersWDM.sys" C:\Windows\System32\drivers\MiniFiltersWDM.sys
//
//sc stop filecallbacks
//sc delete filecallbacks
//
//sc create MiniFiltersWDM type = filesys binpath = "C:\Windows\System32\drivers\MiniFiltersWDM.sys" start = demand
//
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters" / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters" / v "SupportedFeatures" / t REG_DWORD / d 3 / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances" / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances" / v "DefaultInstance" / t REG_SZ / d "MiniFiltersWDM Instance" / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / v "Altitude" / t REG_SZ / d "370030" / f
//reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / v "Flags" / t REG_DWORD / d 0 / f
//
//fltMC load MiniFiltersWDM

.inf

[Version]
Signature   = "$Windows NT$"
Class       = "ActivityMonitor"
ClassGuid   = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}
Provider    = %ManufacturerName%
DriverVer   = 01/01/2024,1.0.0.0
CatalogFile = MiniFilterDriver.cat
PnpLockdown = 1                         ; Correct key (not PnpLockdownFiles)

; ----------------------------------------------------------------
;  Destination: Driver Store only (DIRID 13)
;  DIRID 13 does NOT support COPYFLG_PROTECTED_WINDOWS_DRIVER_FILE (0x100)
;  so DriverFiles section uses no flags.
; ----------------------------------------------------------------

[DestinationDirs]
MiniFilter.DriverFiles = 13             ; Driver Store (Win10 2004+)

[SourceDisksNames]
1 = %DiskName%

[SourceDisksFiles]
MiniFiltersWDM.sys = 1

; ----------------------------------------------------------------
;  Install
; ----------------------------------------------------------------

[DefaultInstall.NTamd64]
OptionDesc = %ServiceDescription%
CopyFiles  = MiniFilter.DriverFiles

[DefaultInstall.NTamd64.Services]
AddService = %ServiceName%,,MiniFilter.Service

; ----------------------------------------------------------------
;  Uninstall
;  LegacyUninstall=1 required when using DIRID 13 to support
;  both Primitive INF and downlevel compatibility.
; ----------------------------------------------------------------

[DefaultUninstall.NTamd64]
LegacyUninstall = 1
; NOTE: DelFiles is NOT supported with DIRID 13.
; The Driver Store manages file cleanup automatically when DelService runs.

[DefaultUninstall.NTamd64.Services]
DelService = %ServiceName%,0x200        ; Stop and delete service

; ----------------------------------------------------------------
;  Files
; ----------------------------------------------------------------

[MiniFilter.DriverFiles]
MiniFiltersWDM.sys                    ; No flags — 0x100 invalid with DIRID 13

; ----------------------------------------------------------------
;  Service
; ----------------------------------------------------------------

[MiniFilter.Service]
DisplayName    = %ServiceName%
Description    = %ServiceDescription%
ServiceBinary  = %13%\MiniFilterDriver.sys
ServiceType    = 2                      ; SERVICE_FILE_SYSTEM_DRIVER
StartType      = 3                      ; SERVICE_DEMAND_START
ErrorControl   = 1                      ; SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor"
AddReg         = MiniFilter.AddRegistry

[MiniFilter.AddRegistry]
; All minifilter registry values must live under the Parameters subkey
HKR,"Parameters","DebugFlags",0x00010001,0x0
HKR,"Parameters","SupportedFeatures",0x00010001,0x3
HKR,"Parameters\Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Parameters\Instances\"%DefaultInstance%,"Altitude",0x00000000,%MiniFilterAltitude%
HKR,"Parameters\Instances\"%DefaultInstance%,"Flags",0x00010001,0x0

; ----------------------------------------------------------------
;  Strings
; ----------------------------------------------------------------

[Strings]
ManufacturerName    = "YourName"
DiskName            = "MiniFilterDriver Installation Disk"
ServiceName         = "MiniFilterDriver"
ServiceDescription  = "Basic MiniFilter Driver"
DefaultInstance     = "MiniFilterDriver Instance"
MiniFilterAltitude  = "370030"

FLT_OPERATION_REGISTRATION

This structure tells the Filter Manager which I/O operations our minifilter wants to intercept:

const FLT_OPERATION_REGISTRATION Callbacks[] =
{
    {
        IRP_MJ_CREATE,
        0,
        PreCreateCallback,
        PostCreateCallback
    },
    { IRP_MJ_OPERATION_END }
};

In this case, we intercept IRP_MJ_CREATE, which is triggered whenever a process attempts to create or open a file

PreCreateCallback

This callback is invoked before the filesystem processes the open/create request

Here we can:

  • Inspect the file path
  • Modify the request
  • Block the operation entirely

PostCreateCallback

This callback executes after the filesystem has processed the request

Useful for:

  • Logging success/failure
  • Measuring latency
  • Post-processing handles

Proof of Concept

Let's install and check the resuts:

Installation:

copy "C:\Users\s12de\Documents\Github\kernelutils\MiniFiltersWDM\x64\Debug\MiniFiltersWDM.sys" C:\Windows\System32\drivers\MiniFiltersWDM.sys

sc stop filecallbacks
sc delete filecallbacks

sc create MiniFiltersWDM type=filesys binpath="C:\Windows\System32\drivers\MiniFiltersWDM.sys" start=demand

reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters" / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters" / v "SupportedFeatures" / t REG_DWORD / d 3 / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances" / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances" / v "DefaultInstance" / t REG_SZ / d "MiniFiltersWDM Instance" / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / v "Altitude" / t REG_SZ / d "370030" / f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MiniFiltersWDM\Parameters\Instances\MiniFiltersWDM Instance" / v "Flags" / t REG_DWORD / d 0 / f

And finally to start the driver:

fltMC load MiniFiltersWDM

And then we open the DebugView application:

None

Conclusions

Windows Minifilter drivers provide a way to intercept and control file system operations at the kernel level. In this post, we explored how to register a minifilter, define callback routines, and process file I/O requests using the Filter Manager framework

📌 Follow me: YouTube | 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter

S12.