Minfilter过滤框架

时间:2023-03-09 20:02:15
Minfilter过滤框架

Minfilter过滤框架

优势

与传统的Sfilter过滤驱动相比,有这样几个优势

1. Minfilter加载顺序更易控制,Sfilter加载是随意的,也就是说它在IO设备栈上的顺序是根据其创建的顺序决定的,越晚创建的,越排在设备栈的顶部,而Minfilter根据它的一个全局变量——altitude规定了它在设备栈上的顺序

2. 具有可卸载能力,一般的hook或者过滤框架在卸载时可能仍然有程序在访问它们的代码,所以如果在有程序访问其代码,而它又被卸载时容易导致蓝屏,这样就不具备可卸载能力。而Minfilter则不会导致蓝屏

3. Minfilter是通过注册回调函数到Minfilter管理器中,由Minfilter管理器来负责调度这些函数,不直接与IO管理器接触,同时我们只需要注册我们感兴趣的回调函数,而不像Sfilter那样,需要提供一个统一的处理函数。所以相对来说更简单

4. 兼容性更好,由IO管理器下发的IRP 请求既可以交给Sfilter框架处理,也可以交给Minfilter处理,也可以给下层的设备驱动处理。

5. 名字处理处理更加容易,相对与Sfilter中需要另外顶一个一个NAME_CONTROL结构,还需要注意长短名来说,Minfilter更加简单,只需要一个简单的函数就可以获取文件的卷设备名称,文件全名,流名等信息

Minfilter的基本框架

Minfilter过滤框架

应用层下发的IO请求首先交由IO管理器处理,IO管理器将请求封装为一个IRP请求包接着网下层分发,当分发到Minfilter管理器时,由Minfilter将IRP封装为一个CALLBACK_DATA结构,并根据不同的请求调用不同的回调函数,由回调函数处理并决定是否分发到下层。

Altitude 变量

这个变量是Minfilter提供的全局变量,用来规定这个Minfilter管理器在IO栈中的高度,值越大越位于上方,它们在IO管理器中的位置如下图

Minfilter过滤框架

在栈中的位置既不是越低越好也不是越高越好,而是根据其具体需求,比如杀毒操作应该放到加解密操作之前,加解密应该放到真实的读写操作之前,所以它们在栈中从上到下的顺序应该是杀毒、加解密、IO设备对象。所以微软为每个功能的Minfilter中的Altitude都提供了大致的位置值,杀毒的过滤驱动是在320000-329999,而加解密的是140000-149999

Minfilter框架详解

注册结构FLT_REGISTRATION

Minfilter中最主要的是各个回调函数,向其中注册回调函数实际是在填写一个叫做FLT_REGISTRATION的结构体,在Minfilter中定义了这样一个结构体的变量:

const FLT_REGISTRATION fileMonitorRegistration =
{
sizeof( FLT_REGISTRATION ), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
ContextRegistration, // ContextRegistration
fileMonitorCallbacks, // Operation callbacks
fileMonUnload, // FilterUnload
fileMonInstanceSetup, // InstanceSetup
NULL, // InstanceQueryTeardown
fileMonInstanceTeardownStart, // InstanceTeardownStart
NULL, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
};
  1. fileMonitorCallbacks是一个函数的指针数组,保存了处理各种事件的回调函数,框架会根据具体的事件来调用这些函数。它的定义如下:
const FLT_OPERATION_REGISTRATION
fileMonitorCallbacks[] =
{
{
IRP_MJ_CREATE,
FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,
HOOK_PreNtCreateFile,
HOOK_PostNtCreateFile
},
{
IRP_MJ_CLEANUP,
0,
HOOK_PreNtCleanup,
NULL
},
{
IRP_MJ_WRITE,
0,
HOOK_PreNtWriteFile,
HOOK_PostNtWriteFile
},
{
IRP_MJ_SET_INFORMATION,
0,
HOOK_PreNtSetInformationFile,
HOOK_PostNtSetInformationFile
},
{
IRP_MJ_OPERATION_END
}
};

利用这个结构体将各种IRP请求与处理它的回调函数绑定起来,每组请求都有两个两个处理它的回调函数,一个Pre表示具体设备处理这个请求之前,Post表示系统处理这个请求之后。拿Sfilter中的sfCreate举例来说,HOOK_PreNtCreateFile处理的是在它调用IoCallDriver之前的操作,而HOOK_PostNtCreateFile表示的是在sfCreate在等到底层设备完成文件创建之后的操作。

2. fileMonUnload 这个函数相当于驱动中的DriverUnload函数,在进行驱动开发时由于很多时候不能进行安全的卸载所以很多驱动不提供DriverUnload函数,防止由于卸载时产生蓝屏,要卸载只能重启机器

3. fileMonInstanceSetup :Minfilter像Sfilter一样,会遍历计算机中的所有卷设备,每当有一个被遍历到,就会绑定一个过滤驱动设备在卷设备上,这个过滤设备就是Minfilter中的Instance,这个函数会在绑定过滤设备对象时触发

4. fileMonInstanceTeardownStart 在卸载这个过滤设备时调用

它们的关系如下图所示

Minfilter过滤框架

Minfilter过滤驱动的注册启动与销毁

在我们自己写的驱动中可以使用函数FltRegisterFilter来向Minfilter管理器注册这个驱动的相关信息,以便Minfilter管理器将我们的程序放入到IO设备栈的合适位置中,该函数的原型如下:

NTSTATUS FltRegisterFilter(
IN PDRIVER_OBJECT Driver,
IN CONST FLT_REGISTRATION *Registration,
OUT PFLT_FILTER *RetFilter
);

第一个参数就是驱动的驱动对象指针,这个可以从DriverEntry中获得。

第二个参数是是我们定义的那个装有各种事件回调函数的一个结构体的指针,通过传入这个参数,将这组回调函数注册到Minfilter管理器中

第三个参数是一个输出参数,如果注册成功,则会返回这个参数用来唯一标识这个过滤驱动,这个参数一般是保存在NULL_FILTER_DATA结构中的FilterHandle中

注册完成后,就是启动这个过滤驱动,进行文件系统的过滤,启动使用函数FltStartFiltering,这个函数需要传入之前注册函数返回的那个过滤驱动的句柄

当我们不需要使用这个过滤驱动时使用函数FltUnregisterFilter卸载,它同样需要传入这个过滤驱动句柄。

下面是一个具体的例子

NTSTATUS
DriverEntry (
__in PDRIVER_OBJECT DriverObject,
__in PUNICODE_STRING RegistryPath
)
{
NTSTATUS status; UNREFERENCED_PARAMETER( RegistryPath );
//注册回调函数
status = FltRegisterFilter( DriverObject,
&FilterRegistration,
&NullFilterData.FilterHandle ); ASSERT( NT_SUCCESS( status ) ); if (NT_SUCCESS( status )) {
//如果注册成功则开启minfilter监控
status = FltStartFiltering( NullFilterData.FilterHandle ); if (!NT_SUCCESS( status )) {
//如果失败则卸载注册的回调
FltUnregisterFilter( NullFilterData.FilterHandle );
}
}
DbgPrint("Minifilter started\n");
return status;
}

对于卸载来说,需要注意的是很多驱动本身是不支持卸载的也就是不提供DriverUnload函数,因为系统中的驱动是被所有进程调用的,如果某个进程正在调用而另外一个进程正在卸载这个驱动,那么很可能会产生蓝屏。

回调函数

Minfilter过滤驱动中处理各个请求的回调函数一般都有两个:一个在IO设备处理之前,一个在IO设备处理之后。下面将称它们问Pre函数和Post函数

1. Pre函数

typedef FLT_PREOP_CALLBACK_STATUS
(*PFLT_PRE_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
OUT PVOID *CompletionContext
);

这个函数中的Data是对IRP的一个封装,与操作IRP相似,Minfilter向R3层返回结果使用的代码与普通的NT模型中的相同

Data->IoStatus.Status = STATUS_SUCCESS;
Data->IoStatus.Information = 0;

另外过滤函数返回的值时直接返回给Minfilter管理器进行处理,而Sfilter返回的值是直接交给IO管理器,回调函数的返回值一般有这样几个常用的:

FLT_PREOP_SUCCESS_WITH_CALLBACK:表示处理请求成功,接着往下发这个请求,下层驱动处理完这个请求后可以触发POST函数的调用

FLT_PREOP_SUCCESS_NO_CALLBACK:与上述返回值类似,只是下层驱动处理完这个请求后不会触发POST函数

FLT_PREOP_COMPLETE:请求处理完成,返回这个值,Minfilter将不会将请求继续往下发

2. Post函数

Post函数的原型如下:

typedef FLT_POSTOP_CALLBACK_STATUS
(FLTAPI *PFLT_POST_OPERATION_CALLBACK) (
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__in_opt PVOID CompletionContext,
__in FLT_POST_OPERATION_FLAGS Flags
);

在该函数中的返回值一般有如下几个:

FLT_POSTOP_FINISHED_PROCESSING:向Minfilter管理器返回成功

FLT_POSTOP_MORE_PROCESSING_REQUIRED:需要Minfilter管理器另外开一个线程,用来作为工作者线程,处理需要在低IRQL请求中完成的工作

判断DATA的操作类型的宏

FLT_IS_IRP_OPERATION:判断这个是否是一个IRP请求

FLT_IS_FASTIO_OPERATION:判断这个是否是一个FASTIO请求

FLT_IS_FS_FILTER_OPERATION:判断这个是否是一个文件系统过滤的请求

Minfilter的安装

以inf文件安装

inf文件是一个安装信息的配置文件,指明了安装的.sys文件路径,安装到哪个位置,以及写到注册表中的何种位置,下面是一个具体的例子,在这只列举了比较重要的部分:

[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 = %Msft%
DriverVer = 06/16/2007,1.0.0.0
CatalogFile = nullfilter.cat [DestinationDirs]
DefaultDestDir = 12
NullFilter.DriverFiles = 12 ;%windir%\system32\drivers ;;
;; Default install sections
;; [DefaultInstall]
OptionDesc = %ServiceDescription%
CopyFiles = NullFilter.DriverFiles [DefaultInstall.Services]
AddService = %ServiceName%,,NullFilter.Service [DefaultUninstall]
DelFiles = NullFilter.DriverFiles [DefaultUninstall.Services]
DelService = %ServiceName%,0x200 ;Ensure service is stopped before deleting [NullFilter.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 = NullFilter.AddRegistry [NullFilter.AddRegistry]
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% [NullFilter.DriverFiles]
%DriverName%.sys [SourceDisksFiles]
nullfilter.sys = 1,, [SourceDisksNames]
1 = %DiskId1%,,, [Strings]
Msft = "Microsoft Corporation"
ServiceDescription = "NullFilter mini-filter driver"
ServiceName = "NullFilter"
DriverName = "NullFilter"
DiskId1 = "NullFilter Device Installation Disk" ;Instances specific information.
DefaultInstance = "Null Instance"
Instance1.Name = "Null Instance"
Instance1.Altitude = "370020"
Instance1.Flags = 0x1 ; Suppress automatic attachments

下面对这些部分进行说明:

1. 以[]括起来的部分是一个节,inf文件就是由不同的节组成

2. version节规定了程序的版本信息

2.1 Class是程序的类,这个值是由驱动具体的功能决定的,这个值其实规定了Altitude的值,也就是驱动在IO栈上的位置。微软提供了不同用途的具体的class值,只需要去对应的网站查询,然后根据具体情况填写即可,点击这里查看

2.2 ClassGuid:是上述Class所对应的GUID值,这个值在上面的链接中可以查到

3. DestinationDirs 规定目标文件的路径,就是你希望将驱动文件安装到哪个目录下,填入的12表示是在C:\Windows\System32\Drivers目录

4. DefaultInstall.Services节表示了要将这个驱动以服务的形式启动起来,AddService 表示添加一个服务,后面是服务的名称,标志,以及安装详细信息的节名称,这里指定安装详细信息在节NullFilter.Service中

5. DefaultUninstall 表示卸载的节点,DelFiles值表名卸载的信息在节点NullFilter.DriverFiles中

6. NullFilter.Service节点中规定了安装的详细信息:

6.1 DisplayName表示的是服务的显示名称

6.2 ServiceDescription 服务的描述信息

6.3 ServiceBinary服务程序所在的路径

6.4 Dependencies该服务的依赖项,就是这个服务要运行必须提前启动他的依赖项服务,Minfilter的驱动是依赖与”FltMgr”服务

6.5 ServiceType 表示服务程序的类型,2 是表示文件系统的服务,这些值可以在MSDN中查到,只需要搜索关于Service的函数 即可,比如CreateService

6.6 StartType 启动类型,3表示手动启动,这个信息也可以通过查询MSDN,方法与ServiceType值的查询相同

6.7 ErrorControl,错误码,当驱动出错时系统怎么处理,1表示一个常规的处理方式

6.8 LoadOrderGroup 加载这个驱动的用户组

6.9 AddReg加入注册表信息所在的节点

7. NullFilter.AddRegistry 加入注册表信息的节点,其中的每一项都是一个注册表的项

8. String节中的内容可以看作是一组变量值的定义,当某些字符串过长或者需要反复使用,可以为它定义一个变量,为了以后使用时的方便。在使用时按照%变量名%的形式,最终在解析时会将其进行替换

这份文件是一个通用的模板,以后在使用inf进行Minfilter驱动程序的安装时,只需要修改其中的名称,另外需要修改的可能就是服务启动的那部分内容了

inf文件编写完成后,只需要点击右键–>安装即可,安装完成后在cmd下使用命令net start 驱动名 即可启动命令,卸载则使用net stop 驱动名

用编程的方法动态加载

安装驱动

驱动安装主要的工作是将驱动加载到服务程序并填写相关注册表项,大致需要这样几步:

1. 调用OpenSCManager 打开服务控制管理器的句柄

2. 调用CreateService函数,为驱动创建一个服务

3. 设置注册表的值,其中打开注册表键使用函数RegCreateKeyEx,设置注册表的值用函数RegSetValueEx。

BOOL InstallDriver(const char* lpszDriverName,const char* lpszDriverPath,const char* lpszAltitude)
{
char szTempStr[MAX_PATH];
HKEY hKey;
DWORD dwData;
char szDriverImagePath[MAX_PATH]; if( NULL==lpszDriverName || NULL==lpszDriverPath )
{
return FALSE;
}
//得到完整的驱动路径
GetFullPathName(lpszDriverPath, MAX_PATH, szDriverImagePath, NULL); SC_HANDLE hServiceMgr=NULL;// SCM管理器的句柄
SC_HANDLE hService=NULL;// NT驱动程序的服务句柄 //打开服务控制管理器
hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if( hServiceMgr == NULL )
{
// OpenSCManager失败
CloseServiceHandle(hServiceMgr);
return FALSE;
} // OpenSCManager成功 //创建驱动所对应的服务
hService = CreateService( hServiceMgr,
lpszDriverName, // 驱动程序的在注册表中的名字
lpszDriverName, // 注册表驱动程序的DisplayName 值
SERVICE_ALL_ACCESS, // 加载驱动程序的访问权限
SERVICE_FILE_SYSTEM_DRIVER, // 表示加载的服务是文件系统驱动程序
SERVICE_DEMAND_START, // 注册表驱动程序的Start 值
SERVICE_ERROR_IGNORE, // 注册表驱动程序的ErrorControl 值
szDriverImagePath, // 注册表驱动程序的ImagePath 值
"FSFilter Activity Monitor",// 注册表驱动程序的Group 值
NULL,
"FltMgr", // 注册表驱动程序的DependOnService 值
NULL,
NULL); if( hService == NULL )
{
if( GetLastError() == ERROR_SERVICE_EXISTS )
{
//服务创建失败,是由于服务已经创立过
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄
return TRUE;
}
else
{
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄
return FALSE;
}
}
CloseServiceHandle(hService); // 服务句柄
CloseServiceHandle(hServiceMgr); // SCM句柄 //-------------------------------------------------------------------------------------------------------
// SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances子健下的键值项
//-------------------------------------------------------------------------------------------------------
strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr,"\\Instances");
if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的DefaultInstance 值
strcpy(szTempStr,lpszDriverName);
strcat(szTempStr," Instance");
if(RegSetValueEx(hKey,"DefaultInstance",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
{
return FALSE;
}
RegFlushKey(hKey);//刷新注册表
RegCloseKey(hKey); //-------------------------------------------------------------------------------------------------------
// SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance子健下的键值项
//-------------------------------------------------------------------------------------------------------
strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr,"\\Instances\\");
strcat(szTempStr,lpszDriverName);
strcat(szTempStr," Instance");
if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的Altitude 值
strcpy(szTempStr,lpszAltitude);
if(RegSetValueEx(hKey,"Altitude",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
{
return FALSE;
}
// 注册表驱动程序的Flags 值
dwData=0x0;
if(RegSetValueEx(hKey,"Flags",0,REG_DWORD,(CONST BYTE*)&dwData,sizeof(DWORD))!=ERROR_SUCCESS)
{
return FALSE;
}
RegFlushKey(hKey);//刷新注册表
RegCloseKey(hKey); return TRUE;
}

这里提供一个安装驱动的函数,在这个函数中将某些信息写入了注册表,注册表中变化的信息如下:

HKEY_LOCAL_MACHINE中的SYSTEM\CurrentControlSet\Services\Nullfilter\Instances 中键 DefaultInstance = “nullfilter Instance”

HKEY_LOCAL_MACHINE中的SYSTEM\CurrentControlSet\Services\Nullfilter\ nullfilter Instances 中键 altitude = “370020”,键

Flags = 0

启动驱动

驱动的启动与普通的服务程序的启动方法一样,这里直接贴上代码:

BOOL StartDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService; if(NULL==lpszDriverName)
{
return FALSE;
} schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
CloseServiceHandle(schManager);
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
} if(!StartService(schService,0,NULL))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
if( GetLastError() == ERROR_SERVICE_ALREADY_RUNNING )
{
// 服务已经开启
return TRUE;
}
return FALSE;
} CloseServiceHandle(schService);
CloseServiceHandle(schManager); return TRUE;
}

停止驱动的运行

BOOL StopDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService;
SERVICE_STATUS svcStatus;
bool bStopped=false; schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schManager);
return FALSE;
}
if(!ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus) && (svcStatus.dwCurrentState!=SERVICE_STOPPED))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
} CloseServiceHandle(schService);
CloseServiceHandle(schManager); return TRUE;
}

删除驱动

BOOL DeleteDriver(const char* lpszDriverName)
{
SC_HANDLE schManager;
SC_HANDLE schService;
SERVICE_STATUS svcStatus; schManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(NULL==schManager)
{
return FALSE;
}
schService=OpenService(schManager,lpszDriverName,SERVICE_ALL_ACCESS);
if(NULL==schService)
{
CloseServiceHandle(schManager);
return FALSE;
}
ControlService(schService,SERVICE_CONTROL_STOP,&svcStatus);
if(!DeleteService(schService))
{
CloseServiceHandle(schService);
CloseServiceHandle(schManager);
return FALSE;
}
CloseServiceHandle(schService);
CloseServiceHandle(schManager); return TRUE;
}

Minfilter中如何获取各种信息的值

Minfilter中将IRP进行了封装,但是在获取各种值时基本上变化不大,而且相对于之前的Sfilter简单了许多,下面假定在函数中有了它的CALL_BACK_DATA结构,有这样一条语句PFLT_CALLBACK_DATA Data

获取当前进程的EPROCESS结构

/*如果当前线程的ETHREAD结构不为NULL则根据线程ETHREAD来获取进程否则调用函数PsGetCurrentProcess()获取当前进程的EPROCESS结构*/
PEPROCESS processObject =
Data->Thread ? IoThreadToProcess(Data->Thread) : PsGetCurrentProcess();

向R3层返回数据

Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;

与使用IRP相似,在DATA中仍然使用IoStatus成员向R3返回,上述两句的意思与使用IRP时完全相同

文件路径的获取

文件路径的获取与Sfilter相比,简单了许多,只需要调用一个函数FltGetFileNameInformation,这个函数的定义在MSDN中可以查到,所以就不再这里做过多的说明,该函数会返回一个FLT_FILE_NAME_INFORMATION结构体用来保存文件名信息,结构体FLT_FILE_NAME_INFORMATION的定义如下所示

typedef struct _FLT_FILE_NAME_INFORMATION {
USHORT Size;
FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
FLT_FILE_NAME_OPTIONS Format;
UNICODE_STRING Name;
UNICODE_STRING Volume;
UNICODE_STRING Share;
UNICODE_STRING Extension;
UNICODE_STRING Stream;
UNICODE_STRING FinalComponent;
UNICODE_STRING ParentDir;
} FLT_FILE_NAME_INFORMATION, *PFLT_FILE_NAME_INFORMATION;

这个结构体记录了文件路径的各种信息,其中各个字符串都是根据Format的值来进行格式化得到的,需要注意的是在上述位置得到的文件名是类似与“\Device\HarddiskVolume1\Documents and Settings\MyUser\My Documents\Test Results.txt:stream1”的而不是我们之前熟悉的”C:\Documents and Settings\MyUser\My Documents\Test Results.txt:stream1”,这就需要进行转化。

需要注意的是在获取文件名时要在PostCreate函数中获取,因为当调用这个函数时说明根据传进来的文件路径已经正确打开了文件,这个时候的路径名一定是可靠的。


NTSTATUS ntStatus;
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL; UNREFERENCED_PARAMETER( Data );
UNREFERENCED_PARAMETER( FltObjects );
UNREFERENCED_PARAMETER( CompletionContext );
UNREFERENCED_PARAMETER( Flags ); //获取文件路径信息
ntStatus = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED|
FLT_FILE_NAME_QUERY_DEFAULT,
&pNameInfo);
if (NT_SUCCESS(ntStatus))
{
//解析获取到的信息
FltParseFileNameInformation(pNameInfo);
DbgPrint("FileName:%wZ\n", &pNameInfo->Name);
//使用完成后释放这个空间
FltReleaseFileNameInformation(pNameInfo);
}

函数FltGetFileNameInformation会自动为我们分配一个缓冲区来存储文件的信息,所以最后需要调用Release函数释放。另外通过调试的情况来看,FltGetFileNameInformation函数并不能获取文件路径的所有信息,一些扩展信息像Extension或者Parent这样的信息开始是没有的,只有通过FltParseFileNameInformation在已有信息的基础之上进行解析才会有。

至于重命名的文件路径的获取使用下面的语句:

PFILE_RENAME_INFORMATION
pFileRenameInfomation = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

在MInfilter中进行文件操作尽量使用以Flt开头的函数,不要使用Zw开头的那一组,以Zw开头的函数最终会触发Minfilter的回调函数,最终会造成无限递归

Minfilter上下文

在编程中,我们经常会遇到上下文这个概念,比如说进程上下文,线程上下文等等,在这里上下文表示某些代码执行的具体环境,系统一般位于进程上下文和中断上下文。当系统处理进程上下文时,系统在代替R3层做某些事,此时系统在调用系统API,执行系统功能,当系统处于进程上下文时是可以被挂起的。而当系统响应中断与具体硬件进行交互时处于中断上下文,此时的数据都位于非分页内存,而且不能睡眠

而Minfilter上下文指的并不是代码运行的环境,而是一组数据,这组数据是附加到具体的设备对象上的,由用户自己定义。

在使用时先利用函数AllocateContext分配一段内存空间,然后使用一组Set和Get函数来设置和获取设备上下文。具体的函数由不同的上下文来决定,下面是不同的上下文与他们对应的Set函数之间的关系

Context Type Set-Context Routine
FLT_FILE_CONTEXT Windows Vista and later only.) FltSetFileContext
FLT_INSTANCE_CONTEXT FltSetInstanceContext
FLT_STREAM_CONTEXT FltSetStreamContext
FLT_STREAMHANDLE_CONTEXT FltSetStreamHandleContext
FLT_TRANSACTION_CONTEXT (Windows Vista and later only.) FltSetTransactionContext
FLT_VOLUME_CONTEXT FltSetVolumeContext

Get函数的使用与上述相同。最后使用完成后需要使用FltReleaseContext来释放这个上下文。

下面是一个使用的具体例子

typedef struct _INSTANCE_CONTEXT {

} INSTANCE_CONTEXT, *PINSTANCE_CONTEXT;
PINSTANCE_CONTEXT pContext = NULL;
//从设备对象上获取上下文
ntStatus = FltGetInstanceContext(FltObjects->Instance, & pContext);
if(NT_SUCCESS(Status) == FALSE)
{
//设备上没有上下文则分配上下文的内存
ntStatus = FltAllocateContext(g_pFilter,FLT_INSTANCE_CONTEXT,
sizeof(INSTANCE_CONTEXT),
PagedPool,& pContext);
if(NT_SUCCESS(Status) == FALSE)
{
//返回资源不足
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(pContext, sizeof(INSTANCE_CONTEXT));
}
pContext ->m_DeviceType = VolumeDeviceType;
pContext->m_FSType = VolumeFilesystemType;
FltSetInstanceContext(FltObjects->Instance, FLT_SET_CONTEXT_REPLACE_IF_EXISTS,pContext,NULL);
if (pContext)
{
FltReleaseContext(pContext);
} //获取访问
PINSTANCE_CONTEXT pContext = NULL;
Status = FltGetInstanceContext(FltObjects->Instance,&pContext);
//下面是使用这个上下文,和一些其他的操作
pContext->xxx = xxx;

回调函数运行的中断请求级别(IRQL)

  1. pre回调函数可以运行在APC_LEVEL或者PASSIVE_LEVEL 级别,但是一般是运行在PASSIVE_LEVEL级别
  2. 如果一个Pre函数返回的是FLT_PREOP_SYNCHRONIZE,那么相对的,在同一个线程内部,它对应的POST函数将在IRQL <= APC_LEVEL级别运行。
  3. 与对应的Pre在一个线程内时,fast IO请求对应的Post函数运行在PASSIVE_LEVEL级别
  4. Create回调的Post函数运行在PASSIVE_LEVEL级别

    当我们不确定当前代码所处的IRQL时可以使用函数KeCurrentIrql获取当前执行环境所在的IRQL。

R3 与R0的通信

R3在调用Minfilter中的相关函数时需要包含相关的库文件,与Lib文件,具体怎么包含这个我不太清楚,只需要在库项目属性的VC++目录下面这几项包含具体的路径即可

包含目录 :“$(VC_IncludePath);$(WindowsSDK_IncludePath);”

库路径: “$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86”

之前R3与R0进行通信是通过R3调用DeviceIoControl函数将数据下发到R0层,然后R3等待一个事件,在R0处理完成R3的请求并准备好往上发的数据后将事件的对象设置为有信号,这样R3再从缓冲区中取数据,完成双方的通信,但是在Minfilter中准备了专门的函数用于双方通信。

R3向R0下发数据

Minfilter过滤框架

R3层通过函数FilterSendMessage向R0下发数据,而R0通过fnMessageFromClient接收从R3发下来的数据。

在Minfilter中R3与R0是通过各自的端口进行通讯的,这个端口与传统意义上的网络的通信端口不同,是一个抽象的概念,不需要太过于关注。在与R3进行通讯之前需要设置这个端口,端口的设置使用函数FltCreateComunicationPort,在这个函数调用时需要提供这样几个回调函数

1. ConnectNotifyCallback:这个函数在R3层链接到R0的这个通讯端口时调用,在这个函数中可以拿到R3的进程句柄和R3的端口

2. DisconnectNotifyCallback:当R3层与R0层断开时调用这个回调函数

3. MessageNotifyCallback:当R3有数据下发下来调用这个回调,在这个函数中取R3发下来的数据

R0向R3上报数据

Minfilter过滤框架

R0向R3上报数据时是一个双向的过程,R0既要上报数据,又要等待R3的返回,就好像之前的弹窗一样,当R0上报数据后就等待40s,在接收到R3的进一步指示时进行下一步,R0向R3上报大致经历这样的几个过程:

1. R0通过函数FltSengMesage将数据发送到R3

2. R3通过函数FilterGetMessage函数接收到R0的数据,并进行相应的处理

3. 处理完成后,调用函数FilterReplyMessage将处理结果返回给R0。

另外需要注意一点,在进行通讯时需要两套数据结构,这两套分别运用在R3和R0两层,每一层都有两个数据结构,用来表示接收和返回的数据,拿R3来说,它需要一个MESSAGE结构体来接收从R0层发过来的数据,另外需要一个REPLY用于向R0返回数据。R3上报和下发的数据相比于R0需要多加一个FILTER_MESSAGE_HEADER的头,便于Minfilter分辨是哪个客户端下发的数据,Minfilter 根据这个头做相关的处理后将其于的数据发送到R0,所以R0能够正确知道是哪个进程在进行相关请求,而不需要添加额外的结构体下面是一个例子

NTSTATUS
ScannerpScanFileInUserMode (
__in PFLT_INSTANCE Instance,
__in PFILE_OBJECT FileObject,
__out PBOOLEAN SafeToOpen
)
/*读取文件中的部分数据,然后交由R3处理,并根据R3返回的结果判断R3是否安全*/ {
NTSTATUS status = STATUS_SUCCESS;
PVOID buffer = NULL;
ULONG bytesRead;
PSCANNER_NOTIFICATION notification = NULL;
FLT_VOLUME_PROPERTIES volumeProps;
LARGE_INTEGER offset;
ULONG replyLength, length;
PFLT_VOLUME volume = NULL; *SafeToOpen = TRUE; if (ScannerData.ClientPort == NULL) { return STATUS_SUCCESS;
} try {
status = FltGetVolumeFromInstance( Instance, &volume );
if (!NT_SUCCESS( status )) {
leave;
}
status = FltGetVolumeProperties( volume,
&volumeProps,
sizeof( volumeProps ),
&length );
if (NT_ERROR( status )) {
leave;
} //取1024和扇区的最大值,保证读取的数据至少有一个扇区的大小
length = max( SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize );
/*分配一块内存用于从文件中读取数据,在minfilter中进行文件操作时申请缓冲区最好使用flt开头的一组函数,因为它不是简单的分配一块内存,还能保证在有程序使用这段内存时不会出现内存已被释放的情况,内部可能也用了引用计数*/
buffer = FltAllocatePoolAlignedWithTag( Instance,
NonPagedPool,
length,
'nacS' ); if (NULL == buffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}
//构建发送给R3层的结构体的内存
notification = ExAllocatePoolWithTag( NonPagedPool,
sizeof( SCANNER_NOTIFICATION ),
'nacS' ); if(NULL == notification) { status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}
offset.QuadPart = bytesRead = 0;
status = FltReadFile( Instance,
FileObject,
&offset,
length,
buffer,
FLTFL_IO_OPERATION_NON_CACHED |
FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
&bytesRead,
NULL,
NULL ); if (NT_SUCCESS( status ) && (0 != bytesRead)) { notification->BytesToScan = (ULONG) bytesRead; /*将这段信息发送到R3,这个函数是一个阻塞的函数,只有当超时值过了或者R3返回了数据才会返回,在这设置超时值为NULL表示会一直等待,在这返回值也是使用notification做为接受返回值的缓冲,在这不会出现覆盖的情况,因为这个函数在调用后首先是R3接受数据,然后进行处理,处理完成后R3才会主动调用另一个API返回数据,所以这里有一个时间差,当获取到返回值时之前传入的数据已经没有用了,这里将发送的缓冲与返回的缓冲定义为同一个只是为了节省内存*/
RtlCopyMemory( &notification->Contents,
buffer,
min( notification->BytesToScan, SCANNER_READ_BUFFER_SIZE ) ); replyLength = sizeof( SCANNER_REPLY );
//在这我们将
status = FltSendMessage( ScannerData.Filter,
&ScannerData.ClientPort,
notification,
sizeof(SCANNER_NOTIFICATION),
notification,
&replyLength,
NULL ); if (STATUS_SUCCESS == status) { *SafeToOpen = ((PSCANNER_REPLY) notification)->SafeToOpen; } else {
DbgPrint( "!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status );
}
} } finally {
//最后清理内存
if (NULL != buffer) { FltFreePoolAlignedWithTag( Instance, buffer, 'nacS' );
} if (NULL != notification) { ExFreePoolWithTag( notification, 'nacS' );
} if (NULL != volume) { FltObjectDereference( volume );
}
} return status;
}

这个例子演示了R0层如何从磁盘中读取文件内容,然后发送到R3,并从R3层上接受返回。

下面这个例子将演示R3如何读取R0发上来的数据

    while (TRUE)
{
//这是一个阻塞函数,当能从R0接受到数据的时候返回
result = GetQueuedCompletionStatus( Context->Completion, &outSize, &key, &pOvlp, INFINITE );
message = CONTAINING_RECORD( pOvlp, SCANNER_MESSAGE, Ovlp );
if (!result)
{
hr = HRESULT_FROM_WIN32( GetLastError() );
break;
} printf( "Received message, size %d\n", pOvlp->InternalHigh );
notification = &message->Notification; assert(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE);
/*R3层定义的这个SCANNER_MESSAGE结构体是在notification这个结构体的基础之上加了一个头,所以大小会大于这个notification*/
__analysis_assume(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE); //这个函数是一个自定义函数,用来扫描R0传上来的数据有没有特定的字符
result = ScanBuffer( notification->Contents, notification->BytesToScan ); replyMessage.ReplyHeader.Status = 0;
replyMessage.ReplyHeader.MessageId = message->MessageHeader.MessageId; replyMessage.Reply.SafeToOpen = !result; printf( "Replying message, SafeToOpen: %d\n", replyMessage.Reply.SafeToOpen );
//向R0返回数据
hr = FilterReplyMessage( Context->Port,
(PFILTER_REPLY_HEADER) &replyMessage,
sizeof( replyMessage ) ); if (SUCCEEDED( hr )) { printf( "Replied message\n" ); } else { printf( "Scanner: Error replying message. Error = 0x%X\n", hr );
break;
} memset( &message->Ovlp, 0, sizeof( OVERLAPPED ) );
//再次等待从R0下发的数据
hr = FilterGetMessage( Context->Port,
&message->MessageHeader,
FIELD_OFFSET( SCANNER_MESSAGE, Ovlp ),
&message->Ovlp ); if (hr != HRESULT_FROM_WIN32( ERROR_IO_PENDING ))
{
break;
}
}