核心内容摘要
《“撸管社区”不止于指尖的无限可能》
引言在现代操作系统的架构设计中文件过滤驱动File Filter Driver与网络栈的协同工作是一个复杂而重要的课题。
无论是反病毒软件、数据防泄露系统DLP、还是企业级网络安全解决方案都需要深入理解这些组件之间的交互机制。
本文将从系统架构层面出发详细探讨文件过滤驱动、TCP/IP协议栈、NDIS网络驱动接口规范以及WFPWindows过滤平台如何协同工作为从事系统驱动开发和网络安全的工程师提供深度技术指导。
基础概念梳理
1 文件过滤驱动的本质文件过滤驱动是一种内核模式驱动程序它通过拦截和修改文件系统的I/O请求来实现对文件访问的监控和控制。
在Windows系统中文件过滤驱动通常基于以下两种机制实现传统过滤驱动架构这种方式通过在文件系统栈中插入自定义驱动拦截IRPI/O请求包来实现过滤。
驱动程序可以在请求到达真实文件系统驱动之前进行预处理Pre-operation也可以在请求完成后进行后处理Post-operation。
微过滤Mini-Filter驱动框架微过滤驱动是Microsoft推荐的现代方式它提供了一个更加安全和易于管理的框架。
与传统驱动不同微过滤驱动不需要直接处理复杂的文件系统细节而是通过Filter Manager提供的接口与文件系统交互。
2 网络栈的分层结构Windows网络栈遵循ISO/OSI七层模型但在实现上进行了优化和整合。
主要分为以下几层应用层包括浏览器、邮件客户端、FTP等应用程序。
传输层TCP和UDP协议实现负责端到端的通信管理。
网络层IP协议实现负责数据包的路由和转发。
数据链路层网络适配器驱动程序负责与硬件的交互。
3 NDIS和WFP的角色**NDIS网络驱动接口规范**是Windows网络驱动的标准接口它提供了一个抽象层使得网络驱动程序可以与底层硬件和上层协议栈无关。
NDIS的主要职责是管理网络适配器的初始化、数据包的发送和接收、以及驱动程序之间的通信。
**WFPWindows过滤平台**是Windows Vista及之后版本引入的高级网络过滤框架。
与NDIS不同WFP工作在更高的网络栈层次能够在TCP/IP栈的多个位置进行过滤和修改。
WFP提供了用户模式和内核模式的接口使得网络安全应用可以更灵活地实现过滤策略。
文件过滤驱动的工作机制
1 微过滤驱动的架构微过滤驱动框架由以下几个关键组件组成应用程序 ↓ Filter Manager (过滤管理器) ↓ 微过滤驱动实例 ↓ 文件系统驱动 ↓ 存储驱动Filter Manager是整个框架的核心。
它负责管理微过滤驱动的注册和卸载在IRP请求中插入过滤驱动的回调函数维护过滤驱动的优先级顺序提供一个统一的接口供驱动程序使用
2 IRP处理流程当一个文件操作请求到达时流程如下用户模式应用发起文件操作 ↓ System Call (CreateFile、ReadFile等) ↓ I/O Manager 创建 IRP ↓ Filter Manager 创建过滤回调上下文 ↓ 微过滤驱动 Pre-operation 回调 ↓ 文件系统驱动处理请求 ↓ 微过滤驱动 Post-operation 回调 ↓ I/O Manager 完成IRP ↓ 返回结果到用户模式应用
3 过滤驱动的回调函数微过滤驱动主要定义两类回调函数Pre-operation回调在请求到达文件系统之前执行。
驱动程序可以检查文件操作的合法性修改IRP中的参数阻止操作继续进行返回FLT_PREOP_COMPLETE等待异步操作完成Post-operation回调在文件系统完成操作之后执行。
驱动程序可以检查操作结果记录审计信息修改返回给应用的数据执行清理操作
4 编程示例以下是一个简单的微过滤驱动框架示例#include fltKernel.h // 全局变量 PFLT_FILTER gFilterHandle NULL; PFLT_PORT gClientPort NULL; // Pre-operation回调 FLT_PREOP_CALLBACK_STATUS PreOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID *CompletionContext ) { PFLT_FILE_NAME_INFORMATION nameInfo NULL; NTSTATUS status; // 获取文件名信息 status FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, nameInfo ); if (NT_SUCCESS(status)) { FltParseFileNameInformation(nameInfo); // 检查文件扩展名 if (RtlCompareUnicodeString( nameInfo-Extension, gBlockedExtension, TRUE )
{ // 阻止操作 Data-IoStatus.Status STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } FltReleaseFileNameInformation(nameInfo); } return FLT_PREOP_SUCCESS_WITH_CALLBACK; } // Post-operation回调 FLT_POSTOP_CALLBACK_STATUS PostOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags ) { if (NT_SUCCESS(Data-IoStatus.Status)) { // 记录成功的操作 LogOperation(Data); } return FLT_POSTOP_FINISHED_PROCESSING; } // 驱动入口 NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { FLT_REGISTRATION filterRegistration { 0 }; // 配置过滤驱动 filterRegistration.Size sizeof(FLT_REGISTRATION); filterRegistration.Version FLT_REGISTRATION_VERSION; filterRegistration.OperationCallbacks gCallbacks; // 注册过滤驱动 return FltRegisterFilter( DriverObject, filterRegistration, gFilterHandle ); }
TCP/IP网络栈详解
1 TCP/IP栈的分层实现Windows TCP/IP栈的实现可以分为以下层次传输层驱动tcpip.sys负责实现TCP和UDP协议。
该驱动程序管理TCP连接的生命周期实现可靠的数据传输和流控处理错误检测和恢复维护各种超时定时器网络层驱动ipv
ipv6负责IP数据包的处理。
包括IP地址的解析和管理路由查询和选择分片和重组TTL管理网络适配器驱动NDIS驱动负责与硬件交互。
包括将数据包发送到网络从网络接收数据包管理网络适配器的状态
2 数据包处理路径在接收方向从网络到应用网络适配器硬件 ↓ (中断) NDIS驱动 - ReceivePkt回调 ↓ 协议驱动tcpip.sys ↓ IP协议处理 ↓ TCP/UDP协议处理 ↓ 套接字层 ↓ 应用程序在发送方向从应用到网络应用程序 (send()) ↓ 套接字层 ↓ TCP/UDP协议处理 ↓ IP协议处理 ↓ NDIS驱动 - SendPackets回调 ↓ 网络适配器硬件
3 TCP/IP协议栈的核心数据结构理解TCP/IP栈的关键是了解其核心数据结构TCB传输控制块每个TCP连接都有一个TCB存储连接的状态信息typedef struct _TCP_CONTROL_BLOCK { // 连接端点 ULONG LocalAddress; USHORT LocalPort; ULONG RemoteAddress; USHORT RemotePort; // 连接状态 UCHAR State; // CLOSED, LISTEN, SYN_SENT, ESTABLISHED等 // 序号管理 ULONG SendSeqNum; ULONG RecvSeqNum; // 流控窗口 USHORT SendWnd; USHORT RecvWnd; // 重传队列 LIST_ENTRY RetransmitQueue; // 接收缓冲 LIST_ENTRY ReceiveQueue; } TCP_CONTROL_BLOCK;IP选路表存储网络路由信息用于确定数据包的下一跳。
ARP缓存存储IP地址到MAC地址的映射加速地址解析。
NDIS网络驱动接口
1 NDIS的分层模型NDIS提供了一个分层的驱动程序框架协议驱动Protocol Driver ↑↓ 中间驱动Intermediate Driver ↑↓ 微型端口驱动Miniport Driver ↑↓ 网络适配器硬件协议驱动实现特定的网络协议如TCP/IP。
微型端口驱动是厂商提供的网络适配器驱动与特定硬件相关。
中间驱动可以在协议驱动和微型端口驱动之间进行过滤或修改数据包。
2 NDIS驱动的关键接口ProtocolReceiveNetBufferLists当网络适配器接收到数据时调用。
VOID ProtocolReceiveNetBufferLists( _In_ NDIS_HANDLE ProtocolBindingContext, _In_ PNET_BUFFER_LIST NetBufferLists, _In_ NDIS_PORT_NUMBER PortNumber, _In_ ULONG NumberOfNetBufferLists, _In_ ULONG ReceiveFlags ) { // 处理接收到的数据包 PNET_BUFFER_LIST currentNbl NetBufferLists; while (currentNbl ! NULL) { // 获取数据包数据 PNET_BUFFER netBuffer NET_BUFFER_LIST_FIRST_NB(currentNbl); while (netBuffer ! NULL) { // 处理单个网络缓冲 // ... netBuffer NET_BUFFER_NEXT_NB(netBuffer); } currentNbl NET_BUFFER_LIST_NEXT_NBL(currentNbl); } }MiniportSendNetBufferLists当协议驱动需要发送数据时调用。
VOID MiniportSendNetBufferLists( _In_ NDIS_HANDLE MiniportAdapterContext, _In_ PNET_BUFFER_LIST NetBufferLists, _In_ NDIS_PORT_NUMBER PortNumber, _In_ ULONG SendFlags ) { // 准备数据包发送到硬件 // ... }
3 中间驱动的实现中间驱动可以在协议驱动和微型端口驱动之间进行数据包的过滤和修改VOID IntermediateReceiveNetBufferLists( _In_ NDIS_HANDLE ProtocolBindingContext, _In_ PNET_BUFFER_LIST NetBufferLists, _In_ NDIS_PORT_NUMBER PortNumber, _In_ ULONG NumberOfNetBufferLists, _In_ ULONG ReceiveFlags ) { PNET_BUFFER_LIST currentNbl NetBufferLists; PNET_BUFFER_LIST firstModifiedNbl NULL; PNET_BUFFER_LIST lastModifiedNbl NULL; while (currentNbl ! NULL) { PNET_BUFFER_LIST nextNbl NET_BUFFER_LIST_NEXT_NBL(currentNbl); // 检查是否应该过滤这个数据包 if (ShouldFilterPacket(currentNbl)) { // 删除数据包不继续传递 FreeNetBufferList(currentNbl); } else { // 保留数据包可能修改其内容 if (firstModifiedNbl NULL) { firstModifiedNbl currentNbl; } if (lastModifiedNbl ! NULL) { NET_BUFFER_LIST_NEXT_NBL(lastModifiedNbl) currentNbl; } lastModifiedNbl currentNbl; } currentNbl nextNbl; } // 将修改后的数据包传递给上层协议 if (firstModifiedNbl ! NULL) { NdisReturnNetBufferLists( gProtocolBindingContext, firstModifiedNbl, 0 ); } }
WFP网络过滤平台
1 WFP的架构和层次WFP在网络栈的多个位置提供了过滤点称为过滤层Filter LayerFWPM_LAYER_INBOUND_IPPACKET_V4/V6最低层在IP协议处理之前。
FWPM_LAYER_INBOUND_TRANSPORT_V4/V6在传输层之前TCP/UDP头部已可用。
FWPM_LAYER_OUTBOUND_IPPACKET_V4/V6发出方向的IP层。
FWPM_LAYER_OUTBOUND_TRANSPORT_V4/V6发出方向的传输层。
FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4/V6应用层处理(ALE)阶段在套接字绑定时。
FWPM_LAYER_ALE_AUTH_LISTEN_V4/V6在应用程序开始监听时。
FWPM_LAYER_ALE_AUTH_CONNECT_V4/V6在应用程序尝试建立连接时。
2 WFP的工作模式WFP支持两种工作模式用户模式过滤通过调用WinAPI如WFPAPIs进行过滤。
优点是易于开发缺点是性能较低。
内核模式过滤通过开发WFP Callout驱动进行过滤。
优点是性能好缺点是开发复杂。
3 WFP内核模式Callout驱动一个WFP Callout驱动的基本结构#include wdm.h #include fwpsk.h #include fwpmk.h // 全局变量 HANDLE gEngineHandle NULL; UINT32 gCalloutId 0; // Callout回调函数 VOID FlowAbortCallback( _In_ UINT16 LayerId, _In_ UINT32 CalloutId, _In_ IF_INDEX IfIndex, _In_ ADDRESS_FAMILY AddressFamily, _In_reads_bytes_(headerLen) const VOID* Header, _In_ UINT8 HeaderLen, _In_reads_bytes_(optionLen) const VOID* Option, _In_ UINT8 OptionLen, _In_reads_bytes_(ipHeaderSize) VOID* IpHeader, _In_ UINT8 ipHeaderSize ) { // 流中止时的处理 } VOID ClassifyCallback( _In_ const FWPS_INCOMING_METADATA_VALUES* IncomingMetaValues, _In_ const FWPS_INCOMING_VALUES* IncomingValues, _In_ FWPS_FILTER* Filter, _In_ UINT64 FlowContext, _Out_ FWPS_CLASSIFY_OUT* ClassifyOut ) { // 检查数据包 UINT8* ipHeader NULL; UINT32 ipHeaderSize 0; // 获取IP头信息 if (FWPS_IS_METADATA_FIELD_PRESENT( IncomingMetaValues, FWPS_METADATA_FIELD_IP_HEADER_SIZE )) { ipHeaderSize IncomingMetaValues-ipHeaderSize; if (FWPS_IS_METADATA_FIELD_PRESENT( IncomingMetaValues, FWPS_METADATA_FIELD_IP_HEADER )) { ipHeader IncomingMetaValues-ipHeader; } } // 进行分类判决 if (ShouldBlockPacket(IncomingValues, ipHeader)) { ClassifyOut-actionType FWP_ACTION_BLOCK; } else { ClassifyOut-actionType FWP_ACTION_PERMIT; } } NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { NTSTATUS status; FWPS_CALLOUT callout { 0 }; FWPM_CALLOUT mCallout { 0 }; // 初始化FWPS_CALLOUT内核模式 callout.calloutKey GUID_CALLOUT; callout.classifyFn ClassifyCallback; callout.flowAbortFn FlowAbortCallback; callout.notifyFn NULL; // 注册Callout status FwpsCalloutRegister( DriverObject, callout, gCalloutId ); if (!NT_SUCCESS(status)) { return status; } // 打开引擎句柄 status FwpmEngineOpen( NULL, RPC_C_AUTHN_WINNT, NULL, NULL, gEngineHandle ); if (!NT_SUCCESS(status)) { FwpsCalloutUnregisterById(gCalloutId); return status; } return STATUS_SUCCESS; }
文件过滤驱动与网络栈的协同工作
1 数据流向分析当一个应用程序通过网络发送文件数据时涉及多个组件的协同用户应用 ↓ (发起网络I/O) 套接字API ↓ [WFP - ALE_AUTH_CONNECT阶段 - 可决定是否允许连接] ↓ TCP/IP栈 - 传输层 ↓ [WFP - OUTBOUND_TRANSPORT阶段 - 可修改TCP头部] ↓ IP层 ↓ [WFP - OUTBOUND_IPPACKET阶段 - 可修改IP头部] ↓ NDIS驱动 ↓ 网络硬件同时如果应用需要从网络读取数据网络硬件 ↓ NDIS驱动 ↓ [WFP - INBOUND_IPPACKET阶段] ↓ IP层处理 ↓ [WFP - INBOUND_TRANSPORT阶段] ↓ TCP/UDP处理 ↓ 套接字缓冲 ↓ 应用程序
2 场景一基于文件内容的网络过滤考虑一个典型的数据防泄露DLP系统需要拦截包含敏感信息的文件通过网络传输。
这需要文件过滤驱动和网络过滤的配合用户应用尝试发送文件 ↓ 文件过滤驱动 - Pre-operation回调 ↓ (读取文件内容进行扫描) ↓ (若包含敏感信息标记该文件) ↓ 应用程序调用send()发送网络数据 ↓ WFP - ALE_AUTH_CONNECT层 ↓ (查询文件标记如果文件被标记为敏感) ↓ (根据策略决定是否允许连接) ↓ WFP - OUTBOUND_TRANSPORT层 ↓ (进一步检查发送的实际数据) ↓ (可以修改数据或阻止发送)实现这种功能需要文件驱动部分监控文件读取操作识别敏感文件。
FLT_PREOP_CALLBACK_STATUS PreReadCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID *CompletionContext ) { // 获取文件信息 PFLT_FILE_NAME_INFORMATION nameInfo; FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED, nameInfo ); // 检查是否是敏感文件 if (IsSensitiveFile(nameInfo)) { // 为该文件的所有句柄标记 MarkFileHandleAsSensitive(Data-Iopb-TargetFileObject); } FltReleaseFileNameInformation(nameInfo); return FLT_PREOP_SUCCESS_WITH_CALLBACK; }网络驱动部分在网络发送阶段检查是否发送来自敏感文件的数据。
VOID OutboundTransportClassify( _In_ const FWPS_INCOMING_METADATA_VALUES* IncomingMetaValues, _In_ const FWPS_INCOMING_VALUES* IncomingValues, _In_ FWPS_FILTER* Filter, _In_ UINT64 FlowContext, _Out_ FWPS_CLASSIFY_OUT* ClassifyOut ) { // 获取应用程序的进程ID UINT64 processId IncomingMetaValues-processId; // 检查该进程是否有打开的敏感文件 if (ProcessHasSensitiveFileOpen(processId)) { ClassifyOut-actionType FWP_ACTION_BLOCK; ClassifyOut-rights ~FWPS_RIGHT_ACTION_SET; } else { ClassifyOut-actionType FWP_ACTION_PERMIT; } }
3 场景二文件上传/下载监控在企业网络中需要监控和审计哪些文件通过网络传输。
这需要网络驱动识别文件操作和网络操作的关联。
挑战网络驱动看到的是网络字节流不知道这些字节对应哪个文件文件驱动看到的是文件操作不知道这些文件的数据是否通过网络传输解决方案使用共享内存区域建立文件句柄和网络连接的对应关系文件驱动在文件被读取时记录读取操作网络驱动通过当前网络连接的源地址、目的地址、端口等信息推断可能关联的文件操作// 共享的上下文结构 typedef struct { HANDLE ProcessId; LARGE_INTEGER FileKey; // 文件的唯一标识 LARGE_INTEGER Timestamp; WCHAR FileName[MAX_PATH]; } FILE_TRANSFER_CONTEXT; // 文件驱动部分 FLT_POSTOP_CALLBACK_STATUS PostReadCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags ) { if (NT_SUCCESS(Data-IoStatus.Status)) { FILE_TRANSFER_CONTEXT context; // 记录读取信息 context.ProcessId PsGetCurrentProcessId(); context.FileKey Data-Iopb-TargetFileObject-FsContext; KeQuerySystemTime(context.Timestamp); // 获取文件名 FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED, context.FileName ); // 记录到共享缓冲区 LogFileTransferContext(context); } return FLT_POSTOP_FINISHED_PROCESSING; } // 网络驱动部分 VOID OutboundTransportClassify( _In_ const FWPS_INCOMING_METADATA_VALUES* IncomingMetaValues, _In_ const FWPS_INCOMING_VALUES* IncomingValues, _In_ FWPS_FILTER* Filter, _In_ UINT64 FlowContext, _Out_ FWPS_CLASSIFY_OUT* ClassifyOut ) { UINT64 processId IncomingMetaValues-processId; UINT16 remotePort IncomingValues-incomingValue[ FWPS_FIELD_OUTBOUND_TRANSPORT_V4_REMOTE_PORT ].value.uint16; // 查询最近的文件读取操作 FILE_TRANSFER_CONTEXT* context QueryRecentFileOperation(processId); if (context ! NULL) { // 记录文件传输事件 LogFileTransferEvent( context-FileName, remotePort, FILE_TRANSFER_UPLOAD ); } ClassifyOut-actionType FWP_ACTION_PERMIT; }
4 场景三加密文件传输需要对某些特定文件的网络传输进行加密。
流程文件驱动识别应被加密的文件读取文件驱动在post-operation中拦截数据应用加密算法网络驱动可以添加特殊标记或头部表示数据已加密接收端的驱动进行相反的操作// 文件驱动部分 - 加密读取的数据 FLT_POSTOP_CALLBACK_STATUS PostReadCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags ) { if (NT_SUCCESS(Data-IoStatus.Status)) { PFLT_FILE_NAME_INFORMATION nameInfo; FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED, nameInfo ); // 检查是否需要加密 if (ShouldEncryptFile(nameInfo)) { // 获取读取的数据 PVOID buffer Data-Iopb-Parameters.Read.ReadBuffer; ULONG length Data-IoStatus.Information; // 应用加密 EncryptBuffer(buffer, length); // 标记该缓冲区已加密 MarkBufferAsEncrypted(buffer); } FltReleaseFileNameInformation(nameInfo); } return FLT_POSTOP_FINISHED_PROCESSING; } // 加密函数 VOID EncryptBuffer(PVOID Buffer, ULONG Length) { // 使用AES或其他加密算法 // ... }
性能优化和最佳实践
1 文件过滤驱动的性能优化
缓存机制频繁访问的文件名、安全上下文等信息应该被缓存以减少重复的计算typedef struct { UNICODE_STRING FileName; BOOLEAN IsSensitive; LARGE_INTEGER LastCheckTime; } CACHED_FILE_INFO; #define CACHE_SIZE 1024 CACHED_FILE_INFO gFileCache[CACHE_SIZE]; EX_SPIN_LOCK gCacheLock; BOOLEAN GetCachedFileInfo( _In_ PUNICODE_STRING FileName, _Out_ PBOOLEAN IsSensitive ) { KIRQL oldIrql; int i; ExAcquireSpinLockExclusive(gCacheLock, oldIrql); for (i 0; i CACHE_SIZE; i) { if (RtlCompareUnicodeString( gFileCache[i].FileName, FileName, TRUE )
{ *IsSensitive gFileCache[i].IsSensitive; ExReleaseSpinLockExclusive(gCacheLock, oldIrql); return TRUE; } } ExReleaseSpinLockExclusive(gCacheLock, oldIrql); return FALSE; }
选择性过滤不是所有操作都需要过滤。
应该尽早识别不相关的操作快速返回FLT_PREOP_CALLBACK_STATUS PreOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID *CompletionContext ) { // 快速过滤只关心特定的操作 switch (Data-Iopb-MajorFunction) { case IRP_MJ_CREATE: case IRP_MJ_READ: case IRP_MJ_WRITE: // 这些操作需要处理 break; default: // 其他操作直接通过 return FLT_PREOP_SUCCESS_NO_CALLBACK; } // 快速过滤只关心特定的进程 HANDLE processId PsGetCurrentProcessId(); if (!IsMonitoredProcess(processId)) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } // 继续处理 return FLT_PREOP_SUCCESS_WITH_CALLBACK; }
异步处理对于耗时的操作如扫描或加密应该异步处理避免阻塞文件系统typedef struct { PFLT_CALLBACK_DATA Data; PFLT_GENERIC_WORK_ITEM WorkItem; } ASYNC_CONTEXT; FLT_PREOP_CALLBACK_STATUS PreOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID *CompletionContext ) { // 创建异步任务 ASYNC_CONTEXT* asyncCtx ExAllocatePoolWithTag( NonPagedPool, sizeof(ASYNC_CONTEXT), MYFT ); if (asyncCtx NULL) { return FLT_PREOP_SUCCESS_NO_CALLBACK; } asyncCtx-Data Data; asyncCtx-WorkItem FltAllocateGenericWorkItem(); if (asyncCtx-WorkItem NULL) { ExFreePool(asyncCtx); return FLT_PREOP_SUCCESS_NO_CALLBACK; } // 在工作者线程中处理 FltQueueGenericWorkItem( asyncCtx-WorkItem, AsyncOperationWorker, asyncCtx ); // 返回待处理等待工作者线程完成 return FLT_PREOP_PENDING; } VOID AsyncOperationWorker( _In_ PFLT_GENERIC_WORK_ITEM WorkItem, _In_ PVOID Context, _In_ PVOID WorkerContext ) { ASYNC_CONTEXT* asyncCtx (ASYNC_CONTEXT*)Context; // 进行耗时操作 BOOLEAN shouldBlock PerformSecurityCheck(asyncCtx-Data); // 完成操作 if (shouldBlock) { asyncCtx-Data-IoStatus.Status STATUS_ACCESS_DENIED; FltCompletePendingOperation(asyncCtx-Data); } FltFreeGenericWorkItem(asyncCtx-WorkItem); ExFreePool(asyncCtx); }
2 网络驱动的性能优化
快速路径分类对于明确允许或阻止的流量应该快速做出决定避免深层检查VOID OutboundTransportClassify( _In_ const FWPS_INCOMING_METADATA_VALUES* IncomingMetaValues, _In_ const FWPS_INCOMING_VALUES* IncomingValues, _In_ FWPS_FILTER* Filter, _In_ UINT64 FlowContext, _Out_ FWPS_CLASSIFY_OUT* ClassifyOut ) { UINT32 remoteAddr IncomingValues-incomingValue[ FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS ].value.uint32; // 检查是否是已知的安全地址 if (IsWhitelistedAddress(remoteAddr)) { ClassifyOut-actionType FWP_ACTION_PERMIT; return; // 快速返回 } // 检查是否是已知的恶意地址 if (IsBlacklistedAddress(remoteAddr)) { ClassifyOut-actionType FWP_ACTION_BLOCK; return; // 快速返回 } // 进行深层检查 // ... }
流关联上下文使用WFP的流关联功能避免对同一流的数据包重复分类VOID OutboundTransportClassify( _In_ const FWPS_INCOMING_METADATA_VALUES* IncomingMetaValues, _In_ const FWPS_INCOMING_VALUES* IncomingValues, _In_ FWPS_FILTER* Filter, _In_ UINT64 FlowContext, _Out_ FWPS_CLASSIFY_OUT* ClassifyOut ) { // 如果该流已经有关联上下文直接使用 if (FlowContext !
{ PFLOW_CONTEXT flowCtx (PFLOW_CONTEXT)FlowContext; ClassifyOut-actionType flowCtx-Action; return; } // 第一个包需要进行完整分类 NTSTATUS status FwpsFlowAssociateContext0( IncomingMetaValues-flowHandle, FWPS_LAYER_OUTBOUND_TRANSPORT_V4, gCalloutId, CreateAndCacheFlowContext(IncomingValues) ); if (!NT_SUCCESS(status)) { ClassifyOut-actionType FWP_ACTION_PERMIT; } }
3 最佳实践
总结
正确的锁使用使用自旋锁保护共享数据结构避免在持有锁时执行阻塞操作使用适当的IRQL级别
内存管理及时释放分配的内存在必要时使用内存池限制机制防止内存耗尽避免在高IRQL下分配分页内存
错误处理检查所有API的返回值在出错时进行适当的清理记录错误信息用于调试
测试和验证使用Driver Verifier进行驱动验证进行压力测试模拟高负载场景验证多核系统上的同步正确性
八、
常见问题和陷阱
1 文件过滤驱动
常见问题问题1死锁文件过滤驱动可能在Pre-operation中调用文件系统API而文件系统本身可能调用其他驱动形成循环等待。
解决方案避免在Pre-operation中执行递归的文件I/O如果必须执行文件I/O使用异步方式并返回FLT_PREOP_PENDING使用FLT_IS_IRP_ORIGINATING_FROM_USER_MODE等宏检查操作来源问题2兼容性问题某些应用程序使用低级文件API可能不兼容某些过滤驱动。
解决方案充分测试与常见应用的兼容性为特定应用提供排除选项在必要时使用FLT_PREOP_SUCCESS_NO_CALLBACK绕过某些操作
2 网络驱动
常见问题问题1包重组问题对于分片的IP数据包或超出MTU的TCP数据需要进行重组才能正确检查。
解决方案在ALE层进行过滤此时TCP/IP栈已进行重组或在Transport层使用缓冲来进行数据重组使用WFP提供的数据包检查接口问题2加密流量处理对于HTTPS、VPN等加密流量无法进行内容检查。
解决方案在TLS握手阶段识别连接属性在应用层进行检查需要与应用集成使用基于元数据的规则地址、端口、应用等结论文件过滤驱动与网络栈的协同工作是构建复杂系统安全解决方案的基础。
理解这些组件的工作原理、交互方式和性能特性对于开发高效、可靠的安全软件至关重要。
总体而言文件过滤驱动提供了在操作系统最低层监控和控制文件访问的能力。
现代的微过滤框架使得驱动开发更加安全和易于维护。
NDIS是网络驱动的标准接口提供了与硬件无关的网络驱动开发框架。
中间驱动可以在协议和微型端口之间进行过滤。
WFP提供了比NDIS更高级的网络过滤能力在TCP/IP栈的多个层次提供了过滤点。
用户模式和内核模式的结合提供了灵活的解决方案。
协同工作的关键是在多个组件之间建立共享的上下文和状态。
通过巧妙的设计可以实现复杂的安全策略如基于文件内容的网络过滤、加密传输等。
性能优化对于不影响系统响应性至关重要。
缓存、选择性过滤、异步处理等技术可以显著提升系统性能。
对于从事系统安全开发的工程师深入掌握这些技术将能够设计和实现更加高效、安全的解决方案。