使用 Minifilter 驱动程序方法限制存储设备






4.86/5 (5投票s)
完全可用的示例代码解释了创建 Minifilter 驱动程序以阻止通过接口连接的设备所需的一切。
引言
本文需要基本的 Windows 驱动程序开发和 C/C++ 知识。但是,对于没有 Windows 驱动程序开发经验的人来说,它也可能很有趣。本文介绍了如何开发一个简单的文件系统 Mini-filter 驱动程序。
演示驱动程序会阻止连接到系统的所有 USB 类型设备的写入操作。但是,用户可以修改驱动程序以阻止/允许设备连接到任何总线。
什么是文件系统 Mini-filter 驱动程序?
文件系统是用于访问文件的 I/O 操作的目标。Windows 支持多种文件系统。新技术文件系统 (NTFS) 是其原生文件系统。还存在其他类型的文件系统,例如物理文件系统 (UDFS, CDFS, FAT)、网络重定向器 (RDR, NWRDR, WebDAV) 和特殊文件系统 (Npfs, Msfs)。Minifilter 驱动程序会拦截发送在 I/O 管理器和文件系统驱动程序之间的 I/O 操作。
背景
Windows 提供了一个名为 Filter manager 的传统筛选器驱动程序,该驱动程序实现为一个传统筛选器驱动程序。每个 Mini-filter 都有自己的 Altitude,这决定了它在设备堆栈中的相对位置。Filter manager 接收 IRP,然后 Filter manager 按 Altitude 降序调用它正在管理的 mini-filter。Altitude 是一个无限精度的字符串,被解释为十进制数。数值 Altitude 较低的筛选器驱动程序会加载到 I/O 堆栈中,位于数值较高的筛选器驱动程序下方。
简单的 Mini-Filter 驱动程序
先决条件
在 Visual Studio 2019 中,找到“创建新项目”并找到要使用的模板“空 WDM 驱动程序”。您可以从 GitHub 下载 passthrough 示例,删除不相关的代码,然后开始编写。作为参考,请遵循附带的源代码。
DriverEntry
当驱动程序加载时,I/O 管理器会调用驱动程序的 DriverEntry
例程。在 NT 中,无论驱动程序将控制多少设备,都只会加载一个驱动程序实例。因此,DriverEntry
会被调用一次。它在 IRQL PASSIVE_LEVEL
和系统进程上下文中被调用。
extern "C"
NTSTATUS
DriverEntry (
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER( RegistryPath );
KdPrint(("PocMiniFilter!DriverEntry: Entered\n") );
MiniFilterData.DriverObject = DriverObject;
//
// Register with FltMgr to tell it our callback routines
//
status = FltRegisterFilter( DriverObject,
&FilterRegistration,
&MiniFilterData.Filter );
FLT_ASSERT( NT_SUCCESS( status ) );
if (NT_SUCCESS( status )) {
//
// Start filtering i/o
//
status = FltStartFiltering( MiniFilterData.Filter );
if (!NT_SUCCESS( status )) {
FltUnregisterFilter( MiniFilterData.Filter );
}
}
return status;
}
Mini-filter 驱动程序必须通过指定各种设置(例如它希望拦截的操作)来向 filter manager 注册为 mini-filter。驱动程序设置适当的结构,然后调用 FltRegisterFilter
例程进行注册。如果成功,驱动程序可以根据需要执行进一步的初始化,最后调用 FltStartFiltering
来开始过滤操作。
注意:驱动程序不需要独立设置分派例程,因为驱动程序不在 I/O 路径中;filter manager 才是。
FltRegisterFilter
具有以下原型
NTSTATUS
FltRegisterFilter (
_In_ PDRIVER_OBJECT Driver,
_In_ CONST FLT_REGISTRATION *Registration,
_Outptr_ PFLT_FILTER *RetFilter
);
必需的 FLT_REGISTRATION
结构提供了注册所需的所有信息。此结构中封装了大量信息。
ContextRegistration
- 指向 FLT_CONTEXT_REGISTRATION
结构数组的可选指针。它引用了一些驱动程序定义的可以附加到文件系统实体(如文件和卷)的数据。
OperationRegistration
- 最重要的字段。这是一个指向 FLT_OPERATION_REGISTRATION
结构数组的指针,每个结构都指定了感兴趣的操作以及驱动程序希望在其上被调用的 pre 和 post 回调。
InstanceCallback
- 此回调允许在实例即将附加到新卷时通知驱动程序。驱动程序可以返回 STATUS_SUCCESS
以附加,或者返回 STATUS_FLT_DO_NOT_ATTACH
以表示驱动程序不知道应链接到此卷。
InstanceSetup 回调
每当在卷上创建新实例时,都会调用 InstanceSetup
例程。这使我们有机会决定是否需要附加到卷。如果注册结构中未定义此例程,则会持续创建自动实例。
本文主要实现过滤具有 USB 总线类型的外部媒体卷的阻止功能。在该例程中,首先使用 FltOpenVolume
获取给定 mini-filter 实例附加到的文件系统卷的句柄和文件对象指针。每次成功调用 FltOpenVolume
后必须调用 FltClose
进行匹配。如果在 VolumeFileObject
参数中返回了文件对象指针,则调用者必须在不再需要时通过调用 ObDereferenceObject
来释放它。FltOpenVolume
如果成功,将返回卷根目录的文件对象指针,该指针将在调用 FltDeviceIoControlFile
时用于 IOCTL_STORAGE_QUERY_PROPERTY
查询,以通过存储适配器获取当前设备的磁盘信息。
NTSTATUS
PocMiniFilterInstanceSetup(
__in PCFLT_RELATED_OBJECTS FltObjects,
__in FLT_INSTANCE_SETUP_FLAGS Flags,
__in DEVICE_TYPE VolumeDeviceType,
__in FLT_FILESYSTEM_TYPE VolumeFilesystemType )
{
UNREFERENCED_PARAMETER(Flags);
UNREFERENCED_PARAMETER(VolumeDeviceType);
PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
HANDLE FsVolumeHandle {};
PFILE_OBJECT FsFileObject = NULL;
STORAGE_PROPERTY_ID PropertyId = StorageAdapterProperty;
STORAGE_DESCRIPTOR_HEADER HeaderDescriptor;
PSTORAGE_PROPERTY_QUERY Query = NULL, buffer = NULL,
pQuery = NULL, OutputBuffer = NULL;
ULONG RetLength, OutputLength, Sizeneeded, SizeRequired;
PSTORAGE_ADAPTER_DESCRIPTOR pStorageDescriptor = NULL;
if (VolumeFilesystemType != FLT_FSTYPE_NTFS) {
KdPrint(("Unknown File system is not supported\n"));
status = STATUS_FLT_DO_NOT_ATTACH;
goto end;
}
else {
KdPrint(("InstanceSetup Attched to (Volume = %p, Instance = %p)\n",
FltObjects->Volume,
FltObjects->Instance));
// Involve an internal IOCTL to Disk filter and get the
// desired target disk information
// If it is of interest, then attach to it
// and start filtering else do not attach.
status = FltOpenVolume(FltObjects->Instance, &FsVolumeHandle, &FsFileObject);
if (!NT_SUCCESS(status)) {
KdPrint(("FltOpenVolume failed with status = 0x%x\n", status));
goto end;
}
Sizeneeded = max (sizeof(STORAGE_DESCRIPTOR_HEADER),
sizeof(STORAGE_PROPERTY_QUERY));
OutputBuffer = (PSTORAGE_PROPERTY_QUERY)
ExAllocatePoolWithTag(NonPagedPool, Sizeneeded, 'VedR');
ASSERT(OutputBuffer != NULL);
RtlZeroMemory(OutputBuffer, Sizeneeded);
Query = (PSTORAGE_PROPERTY_QUERY)OutputBuffer;
Query->PropertyId = PropertyId;
Query->QueryType = PropertyStandardQuery;
status = FltDeviceIoControlFile(FltObjects->Instance,
FsFileObject,
IOCTL_STORAGE_QUERY_PROPERTY,
Query,
sizeof(STORAGE_PROPERTY_QUERY),
&HeaderDescriptor,
sizeof(STORAGE_DESCRIPTOR_HEADER),
&RetLength);
if (!NT_SUCCESS(status)) {
KdPrint(("FltDeviceIoControlFile failed with status = 0x%x\n", status));
goto end;
}
// Found the enough size now query property with updated size.
OutputLength = HeaderDescriptor.Size;
ASSERT(OutputLength >= sizeof(STORAGE_DESCRIPTOR_HEADER));
SizeRequired = max(OutputLength, sizeof(STORAGE_PROPERTY_QUERY));
buffer = (PSTORAGE_PROPERTY_QUERY)ExAllocatePoolWithTag
(NonPagedPool, SizeRequired, 'VedR');
ASSERT(buffer != NULL);
RtlZeroMemory(buffer, SizeRequired);
pQuery = (PSTORAGE_PROPERTY_QUERY) buffer;
pQuery->PropertyId = PropertyId;
pQuery->QueryType = PropertyStandardQuery;
status = FltDeviceIoControlFile(FltObjects->Instance,
FsFileObject,
IOCTL_STORAGE_QUERY_PROPERTY,
pQuery,
sizeof(STORAGE_PROPERTY_QUERY),
buffer,
OutputLength,
&RetLength);
if (!NT_SUCCESS(status)) {
KdPrint(("FltDeviceIoControlFile failed with status = 0x%x\n", status));
goto end;
}
pStorageDescriptor = (PSTORAGE_ADAPTER_DESCRIPTOR)buffer;
if (pStorageDescriptor->BusType == BusTypeUsb) {
MiniFilterData.IsTargetDisk = TRUE;
status = STATUS_FLT_DO_NOT_ATTACH;
}
}
end:
if (OutputBuffer != NULL) {
ExFreePoolWithTag(OutputBuffer, 'VedR');
OutputBuffer = NULL;
}
if (buffer != NULL) {
ExFreePoolWithTag(buffer, 'VedR');
buffer = NULL;
}
if (FsVolumeHandle) {
FltClose(FsVolumeHandle);
}
if (FsFileObject != NULL) {
ObDereferenceObject(FsFileObject);
FsFileObject = NULL;
}
return status;
}
驱动程序不会将任何实例附加到外部媒体 USB 设备。除此之外,还有一些与手动从实例分离相关的代码,可以在 teardown 回调中停止。最后,需要驱动程序卸载例程来注销驱动程序,这也可以由一个标志控制。
NTSTATUS
PocMiniFilterUnload (
__in FLT_FILTER_UNLOAD_FLAGS Flags
)
{
UNREFERENCED_PARAMETER( Flags );
PAGED_CODE();
KdPrint(("PocMiniFilter!PtUnload: Entered\n") );
FltUnregisterFilter( MiniFilterData.Filter );
return STATUS_SUCCESS;
}
安装
安装文件系统 mini-filter 驱动程序的正确方法是使用 INF 文件。INF 文件用于安装基于硬件的设备驱动程序。但也可用于在 Windows 系统上安装任何驱动程序。对 INF 文件的完整处理超出了本文的范围。
;
; Copyright (c) Raahul
; PocMiniFilter.inf
[Version]
Signature = "$Windows NT$"
Class = "ActivityMonitor" ;This is determined by the work
;this filter driver does
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by
;the Class
Provider = %ProviderString%
DriverVer = 06/16/2007,1.0.0.1
CatalogFile = PocMiniFilter.cat
[DestinationDirs]
DefaultDestDir = 12
MiniFilter.DriverFiles = 12 ;%windir%\system32\drivers
;;
;; Default install sections
;;
[DefaultInstall]
OptionDesc = %ServiceDescription%
CopyFiles = MiniFilter.DriverFiles
[DefaultInstall.Services]
AddService = %ServiceName%,,MiniFilter.Service
;;
;; Default uninstall sections
;;
[DefaultUninstall]
DelFiles = MiniFilter.DriverFiles
[DefaultUninstall.Services]
DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting
;
; Services Section
;
[MiniFilter.Service]
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\
Dependencies = "FltMgr"
ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER
StartType = 3 ;SERVICE_DEMAND_START
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor"
AddReg = MiniFilter.AddRegistry
;
; Registry Modifications
;
[MiniFilter.AddRegistry]
HKR,,"SupportedFeatures",0x00010001,0x3
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%
;
; Copy Files
;
[MiniFilter.DriverFiles]
%DriverName%.sys
[SourceDisksFiles]
PocMiniFilter.sys = 1,,
[SourceDisksNames]
1 = %DiskId1%,,,
;;
;; String Section
;;
[Strings]
ProviderString = "Raahul"
ServiceDescription = "PocMiniFilter Mini-Filter Driver"
ServiceName = "PocMiniFilter"
DriverName = "PocMiniFilter"
DiskId1 = "PocMiniFilter Device Installation Disk"
;Instances specific information.
DefaultInstance = "PocMiniFilter Instance"
Instance1.Name = "PocMiniFilter Instance"
Instance1.Altitude = "370030"
Instance1.Flags = 0x0 ; Allow all attachments
让我们开始检查 INF 文件的关键方面。
[Version] 部分在 INF 中是强制性的。
signature 指令必须设置为“$Windows NT$
”。call
和 classguid
指令是强制性的,它们指定了此驱动程序所属的类。类的完整列表可以存储在注册表中键“Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class”下,其中 GUID
唯一标识每个类型。
DefaultInstall 部分:指示在“运行”此 INF 时应执行的操作。默认安装部分也可用于安装文件系统 mini-filter 驱动程序。
有关如何修改 INF 的更多信息,请参阅 https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/creating-an-inf-file-for-a-file-system-driver。
安装驱动程序
一旦 INF 文件被充分修改并且驱动程序代码被编译,它就可以安装了。最简单的安装方法是将驱动程序包(SYS、INF 和 CAT 文件)复制到目标系统,然后在文件资源管理器中右键单击 INF 文件并选择“安装”。这将运行 INF。
注意:如果在 INF 中,驱动程序的启动类型为 0,则重新启动系统,驱动程序将在启动时加载。对于此示例,驱动程序的启动类型为按需启动,因此无需重启。
此时,POC mini-filter 驱动程序已安装,可以使用 fltmc
命令行工具(使用管理员命令提示符)加载和运行。
C:\> fltmc load pocminifilter
一旦驱动程序加载到系统中,InstanceSetup
回调将识别设备总线类型并采取相应措施。
要从系统中卸载驱动程序,请使用以下命令
c:\> fltmc unload pocminifilter.sys
测试环境准备
您可以暂时禁用数字驱动程序签名检查以开始 Windows 驱动程序测试。在 Windows 11 中,您可以按以下方式执行此操作
- bcdedit /set nointegritychecks on
- bcdedit /set debug on
- bcdedit /set testsigning on
- 重新启动计算机,然后:
- 按住 Shift 键,然后在主 Windows 菜单中选择“重启”选项。
- 选择“疑难解答”->“高级选项”->“启动设置”->“重启”
- 在“启动设置”中,按 F7 选择“禁用驱动程序签名强制”选项。
就这样 & RFC
就是这样!查看本文,并就文章中的任何内容提出问题或评论。我希望您觉得它很有价值。请随时指出我犯的错误。我将在未来版本中尝试纠正它们。
关注点
在我的一项项目中,任务是阻止某些媒体类型附加筛选器驱动程序实例。
历史
- 2022 年 9 月 12 日:首次发布