October 18, 2026
Enumerating MiniFilter Drivers from Usermode
Welcome to this new Medium post, today we are going to explore MiniFilter driver enumeration from usermode, a reconnaissance primitive that…

By S12 - 0x12Dark Development
2 min read
Welcome to this new Medium post, today we are going to explore MiniFilter driver enumeration from usermode, a reconnaissance primitive that maps every filesystem interception driver registered on a Windows endpoint before you commit to any evasion technique
When you land on a Windows endpoint, one of the first things you want to know is what's watching you. EDRs, AV engines, and security products almost universally rely on minifilter drivers to intercept filesystem operations: reads, writes, process image loads, and more.
The good news: Windows exposes a clean, documented usermode API for this via FltLib.dll
Want to go deeper into Windows offensive development?
Video-based courses from beginner to advanced, and text-based modules (mini courses) with new releases constantly. Plus a technique database with 100+ real techniques updated weekly, and custom C2 agents and consulting for teams.
0x12 Dark Development Skip to content Join our offensive development courses and modules, and explore the techniques database with 100+ real…
Introduction
MiniFilters are kernel drivers that plug into the Filter Manager (fltmgr.sys), a Microsoft framework introduced to simplify filesystem filter driver development. Instead of directly chaining into the IRP dispatch table like legacy filter drivers, minifilters register with the Filter Manager at a specific altitude.
Each minifilter can register pre-operation and post-operation callbacks for specific I/O operations (IRP_MJ_CREATE, IRP_MJ_READ, IRP_MJ_WRITE, and others). When a process opens a file, every registered minifilter gets a chance to inspect, modify, or block that operation before it reaches the filesystem
The enumeration workflow uses three functions:
FilterFindFirst: opens an enumeration handle and returns the first minifilter
HRESULT FilterFindFirst(
[in] FILTER_INFORMATION_CLASS dwInformationClass,
[out] LPVOID lpBuffer,
[in] DWORD dwBufferSize,
[out] LPDWORD lpBytesReturned,
[out] LPHANDLE lpFilterFind
);HRESULT FilterFindFirst(
[in] FILTER_INFORMATION_CLASS dwInformationClass,
[out] LPVOID lpBuffer,
[in] DWORD dwBufferSize,
[out] LPDWORD lpBytesReturned,
[out] LPHANDLE lpFilterFind
);FilterFindNext: iterates to the next minifilter
HRESULT FilterFindNext(
[in] HANDLE hFilterFind,
[in] FILTER_INFORMATION_CLASS dwInformationClass,
[out] LPVOID lpBuffer,
[in] DWORD dwBufferSize,
[out] LPDWORD lpBytesReturned
);HRESULT FilterFindNext(
[in] HANDLE hFilterFind,
[in] FILTER_INFORMATION_CLASS dwInformationClass,
[out] LPVOID lpBuffer,
[in] DWORD dwBufferSize,
[out] LPDWORD lpBytesReturned
);FilterFindClose: closes the enumeration handle
The key structure returned is FILTER_FULL_INFORMATION:
typedef struct _FILTER_FULL_INFORMATION {
ULONG NextEntryOffset;
ULONG FrameID;
ULONG NumberOfInstances;
USHORT FilterNameLength;
WCHAR FilterNameBuffer[1];
} FILTER_FULL_INFORMATION, *PFILTER_FULL_INFORMATION;typedef struct _FILTER_FULL_INFORMATION {
ULONG NextEntryOffset;
ULONG FrameID;
ULONG NumberOfInstances;
USHORT FilterNameLength;
WCHAR FilterNameBuffer[1];
} FILTER_FULL_INFORMATION, *PFILTER_FULL_INFORMATION;The most relevant thing is the FilterNameBuffer, string with the name of the filter
Code
#include <windows.h>
#include <fltuser.h>
#include <stdio.h>
#pragma comment(lib, "fltlib.lib")
int main() {
HANDLE hFilter = INVALID_HANDLE_VALUE;
FILTER_FULL_INFORMATION* pInfo = nullptr;
DWORD dwBytesReturned = 0;
DWORD dwBufferSize = 1024;
HRESULT hr = S_OK;
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("malloc failed\n");
return 1;
}
hr = FilterFindFirst(FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);
if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
dwBufferSize = dwBytesReturned;
free(pInfo);
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("realloc failed\n");
return 1;
}
hr = FilterFindFirst(FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);
}
if (FAILED(hr)) {
printf("FilterFindFirst failed: 0x%08X\n", hr);
free(pInfo);
return 1;
}
printf("%-6s %-50s %s\n", "Alt.", "Name", "Instances");
printf("%-6s %-50s %s\n", "----", "----", "---------");
do {
wchar_t name[256] = {};
DWORD nameLen = pInfo->FilterNameLength / sizeof(wchar_t);
wcsncpy_s(name, 256, pInfo->FilterNameBuffer, nameLen);
printf("%-6u %-50ws %u\n", pInfo->FrameID, name, pInfo->NumberOfInstances);
hr = FilterFindNext(hFilter, FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned);
if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
dwBufferSize = dwBytesReturned;
free(pInfo);
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("realloc failed\n");
break;
}
hr = FilterFindNext(hFilter, FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned);
}
} while (SUCCEEDED(hr));
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) {
printf("FilterFindNext error: 0x%08X\n", hr);
}
FilterFindClose(hFilter);
free(pInfo);
return 0;
}#include <windows.h>
#include <fltuser.h>
#include <stdio.h>
#pragma comment(lib, "fltlib.lib")
int main() {
HANDLE hFilter = INVALID_HANDLE_VALUE;
FILTER_FULL_INFORMATION* pInfo = nullptr;
DWORD dwBytesReturned = 0;
DWORD dwBufferSize = 1024;
HRESULT hr = S_OK;
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("malloc failed\n");
return 1;
}
hr = FilterFindFirst(FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);
if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
dwBufferSize = dwBytesReturned;
free(pInfo);
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("realloc failed\n");
return 1;
}
hr = FilterFindFirst(FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);
}
if (FAILED(hr)) {
printf("FilterFindFirst failed: 0x%08X\n", hr);
free(pInfo);
return 1;
}
printf("%-6s %-50s %s\n", "Alt.", "Name", "Instances");
printf("%-6s %-50s %s\n", "----", "----", "---------");
do {
wchar_t name[256] = {};
DWORD nameLen = pInfo->FilterNameLength / sizeof(wchar_t);
wcsncpy_s(name, 256, pInfo->FilterNameBuffer, nameLen);
printf("%-6u %-50ws %u\n", pInfo->FrameID, name, pInfo->NumberOfInstances);
hr = FilterFindNext(hFilter, FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned);
if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
dwBufferSize = dwBytesReturned;
free(pInfo);
pInfo = (FILTER_FULL_INFORMATION*)malloc(dwBufferSize);
if (!pInfo) {
printf("realloc failed\n");
break;
}
hr = FilterFindNext(hFilter, FilterFullInformation, pInfo, dwBufferSize, &dwBytesReturned);
}
} while (SUCCEEDED(hr));
if (hr != HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS)) {
printf("FilterFindNext error: 0x%08X\n", hr);
}
FilterFindClose(hFilter);
free(pInfo);
return 0;
}Proof of Concept
We run it as Administrator:
C:\Windows\System32>GetMinifiltersFromUserland.exe
Alt. Name Instances
---- ---- ---------
0 bindflt 1
0 UCPD 5
0 SysmonDrv 4
0 WdFilter 5
0 storqosflt 0
0 wcifs 0
0 CldFlt 0
0 bfs 7
0 FileCrypt 0
0 luafv 1
0 UnionFS 0
0 npsvctrig 1
0 Wof 2
0 FileInfo 5C:\Windows\System32>GetMinifiltersFromUserland.exe
Alt. Name Instances
---- ---- ---------
0 bindflt 1
0 UCPD 5
0 SysmonDrv 4
0 WdFilter 5
0 storqosflt 0
0 wcifs 0
0 CldFlt 0
0 bfs 7
0 FileCrypt 0
0 luafv 1
0 UnionFS 0
0 npsvctrig 1
0 Wof 2
0 FileInfo 5Getting the Altitude
FilterFullInformation doesn't return the altitude string directly. To get it, switch to FilterAggregateStandardInformation:
hr = FilterFindFirst(FilterAggregateStandardInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);hr = FilterFindFirst(FilterAggregateStandardInformation, pInfo, dwBufferSize, &dwBytesReturned, &hFilter);This returns FILTER_AGGREGATE_STANDARD_INFORMATION, which contains both the filter name and altitude as wide strings
Conclusions
Minifilter enumeration from usermode is a simple, low noise recon primitive that gives you a complete map of the filesystem interception stack on a target.
📌 Follow me: 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter | YouTube
S12.