File Systems Minifilter - how to only monitor specific folder/directory

As a newbie learning file systems minifilter, tutorials generally capture file events in the machine. I wasn’t able to find out where do I set to monitor only a specific folder (eg. C:\Confidential).

For reference, here is a tutorial code that I am using:

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

PFLT_FILTER FilterHandle = NULL;
NTSTATUS MiniUnload(FLT_FILTER_UNLOAD_FLAGS Flags);
FLT_POSTOP_CALLBACK_STATUS MiniPostCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext, FLT_POST_OPERATION_FLAGS Flags);
FLT_PREOP_CALLBACK_STATUS MiniPreCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);
FLT_PREOP_CALLBACK_STATUS MiniPreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext);

const FLT_OPERATION_REGISTRATION Callbacks = {
{IRP_MJ_CREATE, 0, MiniPreCreate, MiniPostCreate},
{IRP_MJ_WRITE, 0, MiniPreWrite, NULL},
{IRP_MJ_OPERATION_END}
};

const FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION),
FLT_REGISTRATION_VERSION,
0,
NULL,
Callbacks,
MiniUnload,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};

NTSTATUS MiniUnload(FLT_FILTER_UNLOAD_FLAGS Flags)
{
KdPrint((“Driver unloaded \r\n”));
FltUnregisterFilter(FilterHandle);

return STATUS_SUCCESS;
}

FLT_POSTOP_CALLBACK_STATUS MiniPostCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext, FLT_POST_OPERATION_FLAGS Flags)
{
KdPrint((“Post create is running \r\n”));
return FLT_POSTOP_FINISHED_PROCESSING;
}

FLT_PREOP_CALLBACK_STATUS MiniPreCreate(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext)
{
PFLT_FILE_NAME_INFORMATION FileNameInfo;
NTSTATUS status;
WCHAR Name[200] = { 0 };

status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &FileNameInfo);

if (NT_SUCCESS(status))
{
status = FltParseFileNameInformation(FileNameInfo);

if (NT_SUCCESS(status))
{

if (FileNameInfo->Name.MaximumLength < 260)
{
RtlCopyMemory(Name, FileNameInfo->Name.Buffer, FileNameInfo->Name.MaximumLength);
KdPrint((“Create file: %ws \r\n”, Name));
}
}

FltReleaseFileNameInformation(FileNameInfo);
}

return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

FLT_PREOP_CALLBACK_STATUS MiniPreWrite(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext)
{
PFLT_FILE_NAME_INFORMATION FileNameInfo;
NTSTATUS status;
WCHAR Name[200] = { 0 };

status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &FileNameInfo);

if (NT_SUCCESS(status))
{
status = FltParseFileNameInformation(FileNameInfo);

if (NT_SUCCESS(status))
{

if (FileNameInfo->Name.MaximumLength < 260)
{
RtlCopyMemory(Name, FileNameInfo->Name.Buffer, FileNameInfo->Name.MaximumLength);
_wcsupr(Name);
if (wcsstr(Name, L"OPENME.TXT") != NULL)
{
KdPrint((“Write file: %ws blocked \r\n”, Name));
Data->IoStatus.Status = STATUS_INVALID_PARAMETER;
Data->IoStatus.Information = 0;
FltReleaseFileNameInformation(FileNameInfo);
return FLT_PREOP_COMPLETE;
}
KdPrint((“Create file: %ws \r\n”, Name));

}
}

FltReleaseFileNameInformation(FileNameInfo);
}

return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;

status = FltRegisterFilter(DriverObject, &FilterRegistration, &FilterHandle);

if (NT_SUCCESS(status))
{

status = FltStartFiltering(FilterHandle);

if (!NT_SUCCESS(status))
{
FltUnregisterFilter(FilterHandle);
}
}

return status;
}</suppress.h></dontuse.h></fltkernel.h>

Throw that tutorial code away, there’s a lot of things wrong with it…

Assuming that you have no experience in this space and given the lack of
real functional requirements, the best I can do is give you a rough sketch
of the general idea.

Generally you want to query the normalized file name in PostCreate. If the
path is of interest, you establish a Stream Context. In all of your other
entry points (e.g. IRP_MJ_WRITE), you retrieve your Stream Context and know
that the target file is of interest to you.

If you want to deny modification you’re almost always better off denying the
Create operation than trying to fail the writes later. if you do want to
fail writes at least make sure you’re not failing paging writes unless you
really, really know what you’re getting yourself into.

Of course, there are lots of little details to trip you up along the way.
You need to think about hard links and renames. You may also need to worry
about transactions (ugh). If you don’t want files in your nominated
directory to be deleted you also have a bunch of problems to deal with.
Basically, it’s going to come down to requirements. But you need to do some
experimentation and learn more about the problem space before you can even
begin to tackle the problem.

The Context sample is probably a decent place for you to start:

https://github.com/Microsoft/Windows-driver-samples/tree/master/filesys/miniFilter/ctx

It at least demonstrates querying a name and attaching a Stream Context. It
unfortunately also attaches File and Stream Handle contexts which confuses
the story a bit, but you can worry about if you need those later.

Instrument the filter with trace messages and breakpoints then start
performing user level operations (e.g. drag/drop a file, open a file in
Notepad, etc.) to understand the flow of operations in the filter. Don’t
expect it to be intuitive and expect a reasonably high learning curve.

Also, in kernel mode we primarily deal with UNICODE_STRING structures and
not NULL terminated strings. Some UNICODE_STRING management functions are:

* RtlCompareUnicodeString
* RtlPrefixUnicodeString
* FsRtlIsNameInExpression

Just grabbing the UNICODE_STRING.Buffer field and assuming it’s NULL
terminated can lead to disaster as in many cases the buffer is NOT
guaranteed to be NULL terminated.

HTH,

-scott
OSR
@OSRDrivers