引言
Windows文件过滤驱动应用广泛,覆盖安全EDR/DLP,游戏反作弊,云存储,云游戏等商用场景,有良好的市场表现,本文主题过程性实践和文件过滤安全技术分享。
MiniFilter简介:
MiniFilter(Mini-filter Installable File System)框架,MiniFilter拥有良好兼容性,高封装接口,加快软件开发周期和降低了难度门槛,也是微软官方推荐文件过滤框架。早期微软各版本驱动兼容性问题,开发周期变动大。对外统一接口,按照API来约束办事,也是一贯风格,解决安全挂钩合理性,减少开发者文件过滤驱动对系统带来的不确定因素。
用户请求IO(用户态出发),经历如下步骤:
1. 将I/O请求转发到文件系统.
2. 过滤管理拦截请求按照循序A-B-C来调用已注册的微型过滤器,然后将 I/O 请求转发到下一个较低的驱动程序。
3. 文件系统驱动程序处理并转发修改后的请求。
4. 目标卷的存储驱动程序堆栈准备对硬件请求(HAL)。
See Minifilter:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/ifs/advantages-of-the-filter-manager-model
Minifilter框架分析:
Miniflter Register Filter Callback
传统型文件过滤寻找对象生成过滤驱动绑定设备栈,编写分发,体积庞大,而新框架代码层面主要注册-启动-预操作-后操作四部分。IRP工作都交给了Filter Manager,非常清爽:
CONST FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
NULL, // Context
Callbacks, // Operation callbacks
NULL, // MiniFilterUnload
FsFilter1InstanceSetup, // InstanceSetup
FsFilter1InstanceQueryTeardown, // InstanceQueryTeardown
FsFilter1InstanceTeardownStart, // InstanceTeardownStart
FsFilter1InstanceTeardownComplete, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
};
NTSTATUS status;
status = FltRegisterFilter(
DriverObject, //Driver
&FilterRegistration, //Registration
&MiniSpyData.FilterHandle); //RetFilter
See Msdn: https://docs.microsoft.com/zh-cn/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_registration
CONST FLT_OPERATION_REGISTRATION结构体中第五个域,需要重点关注的
typedef struct _FLT_OPERATION_REGISTRATION {
UCHAR MajorFunction;
FLT_OPERATION_REGISTRATION_FLAGS Flags;
PFLT_PRE_OPERATION_CALLBACK PreOperation;
PFLT_POST_OPERATION_CALLBACK PostOperation;
PVOID Reserved1;
} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;
参数一功能号,想拦截读操作,需要从IRP_MJ_READ入手,文件创建则需要IRP_MJ_CREATE入手。
IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
IRP_MJ_QUERY_INFORMATION
IRP_MJ_READ
IRP_MJ_SET_INFORMATION
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE
参数二
SKIP_CACHED_IO不过滤缓冲区读写。
SKIP_PAGING_IO不过滤分页读写。
SKIP_NON_DASD_IO仅对读写回调有用。
参数三和四是功能函数,预操作和后操作回调,IRP攻防业务处理的地方。传统的过滤中,基于IRP分发和事件回调两种方式可以实现主要的功能模块,处理预操作回调和后操作回调即可,通常是在预操作回调实现过滤。
预操作回调函数参数,可以从Data和Object对象中,获取I/O请求的基础数据,使用Flt系列API来获取文件信息和上下文。后操作回调函数在IRP完成后被调用,已完成动作基本不做太多处理。
Minifilter IPC
R0~R3 Minifilter提供了一套交互IPC,类似于创建Socket通信。
r0
// FltBuildDefaultSecurityDescriptor绑定安全描述符,FLT_PORT_ALL_ACCESS。
FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);
// 初始化属性对象OBJECT_ATTRIBUTES对象, 初始化通信端口定义名称MINSPY_PORT_NAME。
RtlSetDaclSecurityDescriptor(sd, TRUE, NULL, FALSE);
RtlInitUnicodeString(&EventPortName, L"\\HadesEventFltPort");
InitializeObjectAttributes(
&oa,
&EventPortName,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
NULL,
sd
);
status = FltCreateCommunicationPort(
g_FltServerPortEvnet,
&g_FltServerPortEvnetPort,
&oa,
NULL,
CommunicateConnect,
CommunicateDisconnect,
NULL,
1
);
FltCreateCommunicationPort注册,这个地方有三个回调函数是必须处理的,利用FilterConnectCommunicationPort和FilterSendMessage进行交互。
r3
#include <fltuser.h>
// 连接驱动创建的Port
Status = FilterConnectCommunicationPort(
L"\\HadesEventFltPort", // Driver CreatePortName
0,
NULL,
0,
NULL,
&g_hPort);
if (Status == HRESULT_FROM_WIN32(S_OK))
// 绑定IOCP端口(异步接收)
g_comPletion = CreateIoCompletionPort(g_hPort, NULL, 0, 4);
if (nullptr == g_comPletion)
{
CloseHandle(g_hPort);
g_hPort = nullptr;
continue;
}
// 获取消息分发处理
Status = FilterGetMessage(
g_hPort,
&msg->MessageHeader,
FIELD_OFFSET(COMMAND_MESSAGE, Overlapped),
&msg->Overlapped
);
Minifilter安全技术:
文件/目录保护:
访问文件或目录,Windows下要通过文件句柄Handle进行操作,也就是Open或者Create。基于Minfilter推荐在IRP_MJ_CREATE PreCallback进行过滤操作,IRP_MJ_CREATE不一定全面,还会有删除/重命名/修改等属性,需要注册IRP_MJ_SET_INFORMATION。
{ IRP_MJ_CREATE,
0,
FsFilter1PreOperation,
NULL/*FsFilter1PostOperation*/},
{ IRP_MJ_SET_INFORMATION,
0,
FsFilter1PreOperation,
NULL },
不同的IRP操作,注册回调可以使用同函数进行处理,可以都使用FsFilter1PreOperation前操作回调,通过IRP类别区分即可。r3通过CreateFile或者OpenFile等API访问文件,I/O到过滤文件系统以后,会触发我们注册的回调,需要业务规则检测访问权限。
- IRP类别
const unsigned char IRP_MJ_CODE = Data->Iopb->MajorFunction;
if (IRP_MJ_CODE == IRP_MJ_CREATE) {
}
else if (IRP_MJ_CODE == IRP_MJ_SET_INFORMATION) {
}
-
访问进程PID
const DWORD dwPID = (DWORD)PsGetCurrentProcessId();
-
通过Data参数获取被访问的文件或目录完整路径
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL; NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &pNameInfo);
-
访问目标属性,比如我们需要对IRP_MJ_SET_INFORMATION做细分识别
Data->Iopb->Parameters.SetFileInformation.FileInformationClass;
// Rename case FileRenameInformation: // IRP_MJ_SET_INFORMATION Rename触发NameInfo case FileNameInformation: // Delete case FileDispositionInformation:
-
正常访问返回FLT_PREOP_SUCCESS_NO_CALLBACK,不允许访问FLT_PREOP_COMPLETE
Data->IoStatus.Status = STATUS_ACCESS_DENIED; Data->IoStatus.Information = 0; return FLT_PREOP_COMPLETE;
-
IRP_MJ_CREATE权限
if (((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_CREATE ||
((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_OPEN_IF ||
((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_OVERWRITE_IF)
- FILE_DELETE_ON_CLOSE也比较特殊
上述的过程性代码可以根据PID,被访问路径,访问的属性和权限进行业务规则,拒绝被恶意进程访问,可以使用FLT_PREOP_COMPLETE进行保护或者拦截。
文件隐藏:
隐藏挂钩方式有很多种,应用层可以通过WINAPI来查询,递归枚举目录文件。大部分查找都是通过查询方式来做的,但基于Ntfs MFT磁盘解析,动作不会进入到文件过滤驱动IRP。
Minifilter通过IRP查询拦截可以使用IRP_MJ_DIRECTORY_CONTROL过滤来实现,前回调FsFilterAntsDrPostFileHide处理,IRP处理不好会影响性能,访问枚举性能很高,基线条件要处理好。
{ IRP_MJ_DIRECTORY_CONTROL,
0,
FsFilterAntsDrPostFileHide,
NULL },
-
请求属性获取IRP_MN_QUERY_DIRECTORY,判断FileBothDirectoryInformation.
if (Data->Iopb->MinorFunction == IRP_MN_QUERY_DIRECTORY && (Data->Iopb->Parameters.DirectoryControl.QueryDirectory.FileInformationClass == FileBothDirectoryInformation) && Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length > 0 && NT_SUCCESS(Data->IoStatus.Status)) { }
-
IRP_MJ_DIRECTORY_CONTROL不能通过返回值来进行权限拒绝,需要通过映射MDL进行修改.
if (Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress != NULL)
{
Bufferptr = MmGetSystemAddressForMdl(Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress,
NormalPagePriority);
}
else
{
Bufferptr = Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;
}
if (Bufferptr == NULL)
return FLT_POSTOP_FINISHED_PROCESSING;
// 推荐使用 MmGetSystemAddressForMdlSafe
- 获取映射的MDL,FILE_BOTH_DIR_INFORMATION进行摘除.
PFILE_BOTH_DIR_INFORMATION pCutFileInfo = (PFILE_BOTH_DIR_INFORMATION)Bufferptr;
PFILE_BOTH_DIR_INFORMATION pPreFileInfo = pCutFileInfo;
PFILE_BOTH_DIR_INFORMATION pNextFileInfo = 0;
ULONG uNextOffset = 0;
if (pCutFileInfo == NULL)
return FLT_POSTOP_FINISHED_PROCESSING;
// 可以找到隐藏的文件名
pCutFileInfo->FileName, HideFileName, wcslen(HideFileName)
// 摘除
if (uNextOffset == 0)
pPreFileInfo->NextEntryOffset = 0;
else
pPreFileInfo->NextEntryOffset = (ULONG)((PCHAR)pCutFileInfo - (PCHAR)pPreFileInfo + uNextOffset);
pCutFileInfo = pNextFileInfo;
进程/模块拦截:
进程拦截,保护推荐使用PsSetCreateProcessNotifyRoutineEx、ObRegisterCallbacks.
// See: Available starting with Windows Vista with SP1 and Windows Server 2008.
// Msdn: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)Process_NotifyProcessEx, FALSE);
// See: Available starting with Windows Vista with Service Pack 1 (SP1) and Windows Server 2008.
// Msdn: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks
NTSTATUS status = ObRegisterCallbacks(&obReg, &g_handleobj);
// 卸载
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)Process_NotifyProcessEx, TRUE);
ObUnRegisterCallbacks(g_handleobj);
DLL拦截模块有多种方式,已落地文件在Open/Craete IRP做拦截,使其没有权限打开失败,基于Minfilter注册IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION也可以做到相同效果的拦截。
{ IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
0,
FsFilterAntsDrvPreExe,
NULL },
- 加载的名字或PID
const DWORD dwPID = (DWORD)PsGetCurrentProcessId(); FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &pNameInfo)
- PageProtection执行状态
if (Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE) { return FLT_PREOP_SUCCESS_NO_CALLBACK; }
- 拦截方式
实践中发现很多其他的调用也会进入到注册回调,这时候可以做PE区分,通过解析来区分有效性EXE/DLL模块,FltReadFile读取FileObject对象,拿到数据做NT解析即可。Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES // DLL弹出资源错误窗口,可以设置. Data->IoStatus.Status = STATUS_ACCESS_DENIED Data->Iostatus.information = 0; return FLT_PREOP_COMPLETE;