Windows内核防火墙驱动开发:从WFP架构到SuperDriver落地实践

发布时间:2026/10/5 11:30:27
Windows内核防火墙驱动开发:从WFP架构到SuperDriver落地实践 简介WFPWindows过滤平台网络驱动防火墙源码包面向Windows驱动开发、网络安全研究与内核编程学习者。资源围绕Windows平台下的防火墙过滤机制展开展示如何基于WFP框架构建网络驱动层拦截与管控逻辑适合需要参考驱动级安全方案或进行二次开发的工程师。对于初学驱动编程的人这份代码也能作为理解WFP核心概念的起步模板帮助厘清驱动注册、过滤条件、进程/端口绑定等常见环节。压缩包体积约766KB属于轻量级示例代码便于快速阅读与移植由于平台暂未提供文件总数与具体类型明细包内源码结构需下载后自行查看。资料目前已有283人学习可帮助理解WFP函数调用流程、过滤层挂载方式以及驱动与防火墙交互的常见实现思路也可作为课程设计或产品预研的起点。1. 从SuperDriver到Windows防火墙WFP驱动的价值落在哪SuperDriver这个项目代号背后是一类很明确的诉求在Windows上做一个由内核驱动支撑的防火墙而WFP即Windows Filtering PlatformWindows过滤平台就是这条技术路线的地基。普通应用层防火墙能拦网卡数据吗能但进程一旦被结束规则随之失效攻击者也能通过注入或提权绕过它的钩子。把拦截点下沉到内核态交给TCP/IP协议栈的必经路径失败的代价会高得多。这篇文章要讲的是SuperDriver这类驱动从零到落地的一条完整路径先讲清楚为什么选WFP而不是旧方案再给出最小驱动、规则引擎和黑白名单设计最后把我在真实开发中踩过的坑按“现象-原因-解决”梳理出来。适合正在学习Windows内核网络过滤、或者准备做企业终端安全、EDR外联管控的工程师。2. 为什么防火墙驱动一定要选WFP绕开NDIS与TDI的三个理由WFP并不是唯一的历史选项。在Vista之前想做内核态防火墙通常走TDI或NDIS过滤驱动TDI方案要自己解析协议头NDIS过滤驱动则工作在网卡层处理流量的位置太低。WFP从Vista开始成为微软主推的统一网络过滤架构直到Win11依然是系统原生支持的过滤框架之一。如果你现在还要评估“用SuperDriver做防火墙该选哪种驱动”结论基本不需要犹豫选WFP。2.1 WFP的分层过滤与callout机制到底怎么工作WFP的核心思路是把网络数据在协议栈中的路径切成若干个“层”layer。IPv4出站、IPv4入站、转发、ALE授权连接、ALE授权接收、IPsec等都有独立层ID。每个层上挂着两类对象过滤器和callout。过滤器描述“什么样流量在什么条件下要做什么动作”callout则是一段有业务逻辑的回调代码。当数据经过某层时过滤引擎把满足条件的流量交给对应的calloutcallout内部的classify函数最终返回放行或拦截。我一般把WFP比作一条流水线检查表数据包每到一个工位层检查表过滤条件命中后就叫对应岗位的工人callout来判定。判定结果不是只有拦和放还可以挂起、直通、继续由下层处理。Windows自带防火墙本身就是基于WFP做的第三方的WFP驱动可以注册自己的子层和callout和系统防火墙平级工作。这里有个关键区别只注册callout并不会影响任何流量真正触发callout的是过滤器。过滤器可以来自驱动自身也可以来自用户态配置程序。配置程序打开过滤引擎后添加一条过滤器动作选择“调用callout”并指定callout ID和一组条件字段那么后续数据才会进到你的classify函数里。WFP在内核态为开发者暴露了一组稳定的API用户态也有一组几乎等价的API。内核态负责流量判定用户态负责策略管理两层通过同一个引擎协作。这使得防火墙策略可以集中托管在系统服务中驱动崩溃不会直接影响保存的配置这也是SuperDriver采用“内核callout 用户态管理程序”这套成熟架构的原因。2.2 与NDIS/TDI对比以及“关闭防火墙有影响吗”的实际答案我之前维护过一个老项目用的是NDIS中间层驱动。它的优势是能覆盖更老的系统但问题很明显工作在网卡驱动之上、协议栈之下所有包都是裸的以太网帧要自己解析IP、端口、协议还要处理分片重组。一旦协议解析有疏漏性能和正确性就会失衡。TDI则更老主要面向TCP/UDP会话后来也被微软放弃了。把WFP、NDIS中间层、TDI放在一张对比表里看得更清楚对比项WFPNDIS中间层TDI系统支持Vista起全系Win11下依然是推荐路线仍可用微软不推荐新开发已废弃数据可见度已解析好的IP/端口/协议字段原始帧自行解析会话级不适用于IP层用户态配合同一套引擎配置天然统一需要额外通信把规则传给驱动难以和现代防火墙兼容状态跟踪ALE层自带连接状态自己维护连接表有但仅限TCP/UDP开发成本中高但上限高高坑多没必要再学适合场景现代防火墙/EDR外联管控老旧系统兼容历史遗留项目关于网上经常搜到的“关闭防火墙有影响吗”这个问题在WFP架构下有个容易被误解的地方如果第三方防火墙是基于WFP的你在系统设置里把“Windows防火墙”关掉并不会关闭WFP引擎本身也不会让你的SuperDriver失效。因为Windows防火墙只是WFP引擎里的一组过滤器和策略它和你注册的callout是并列关系。关掉系统防火墙你注册的过滤逻辑还在协议栈路径上继续执行。这也是很多企业终端管理软件敢直接接管系统防火墙的底气自带的Windows防火墙与第三方WFP驱动并不互斥反而是可以叠加的。如果你问的是“把SuperDriver这个第三方防火墙关了会有影响吗”那当然有规则不再被加载流量全部走默认放行策略但至少要知道自己注册的callout不会因为系统防火墙的开关而被卸载这是排查问题时的起点。3. 搭建SuperDriver最小驱动从DriverEntry到第一个可拦截流量的callout理论讲再多不如先跑通一条最小链路。这个最小链路包括三个动作内核驱动注册callout、用户态程序向引擎添加过滤器、实际流量触发分类回调。下面我按这个顺序拆开写代码以C/C为主环境是Visual Studio WDK目标系统Win11 x64。3.1 驱动入口与WFP callout注册先看驱动里最核心的一段。一般我会创建一个WDF驱动程序在DeviceIoControl里同时做IRP分发但WFP相关代码跟IRP无关只需在DriverEntry里注册callout即可。以下是一个精简但能编译通过的DriverEntry片段#include ntddk.h #include fwpsk.h #include fwpmk.h #define SUPERDRIVER_TAG pDSx static UINT32 g_calloutId 0; // 分类回调所有被过滤器命中的流量都会进到这里 static void SuperDriverClassify( const FWPS_INCOMING_VALUES0* inFixed, const FWPS_INCOMING_METADATA_VALUES0* inMeta, void* layerData, const void* classifyContext, const FWPS_FILTER0* filter, UINT64 flowContext, FWPS_CLASSIFY_OUT0* classifyOut) { // 默认拦截只有明确命中放行规则的流量才放行 classifyOut-actionType FWP_ACTION_BLOCK; } NTSTATUS SuperDriverNotify( FWPS_CALLOUT_NOTIFY_TYPE notifyType, const GUID* filterKey, FWPS_FILTER0* filter) { if (notifyType FWPS_CALLOUT_NOTIFY_ADD_FILTER) { // 过滤器添加时这里可以预先做一些规则相关初始化 } return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING registryPath) { NTSTATUS status; FWPS_CALLOUT0 callout {0}; FWPM_CALLOUT0 m {0}; UNREFERENCED_PARAMETER(registryPath); callout.flowDeleteFn NULL; callout.notifyFn SuperDriverNotify; callout.classifyFn SuperDriverClassify; status FwpsCalloutRegister0(driverObject, callout, g_calloutId); if (!NT_SUCCESS(status)) { return status; } m.applicableLayer FWPM_LAYER_ALE_AUTH_CONNECT_V4; m.calloutId g_calloutId; // displayData用于在监控工具里展示callout名称 m.displayData.name LSuperDriver ALE Connect Callout; m.displayData.description LBlock/Allow outbound connections; status FwpmCalloutAdd0(NULL, m, NULL, NULL); return status; }这段代码里有个需要特别注意的地方FwpmCalloutAdd0的引擎句柄传的是NULL。这个API从内核态调用时如果句柄为空系统会尝试使用当前进程的默认会话很多刚学WFP的人在这里会拿一个无效句柄导致添加失败。我一般会选择把callout注册放在驱动里而callout的Add操作放到用户态管理程序里做这样引擎句柄和事务控制都在用户态完成更符合微软推荐的“用户态管理、内核态执行”模型。这段代码还说明了一个关键点即使callout注册成功如果不添加过滤器classify函数前面没有任何流量会进来这叫“只挂号不看诊”。所以下一步必须在引擎里添加过滤器。3.2 用户态添加过滤器让classify真正被调用用户态代码用C链接fwpuclnt.lib。下面是我常用的“添加一条出站拦截规则”的代码它把TCP 443端口的所有流出连接交给SuperDriver的callout处理#include windows.h #include fwpmu.h #pragma comment(lib, fwpuclnt.lib) bool AddConnectFilter() { HANDLE engine NULL; FWPM_SESSION0 session {0}; FWPM_FILTER0 filter {0}; FWPM_FILTER_CONDITION0 conds[2] {0}; DWORD result; // 动态会话进程退出后过滤器自动清除调试时不容易残留 session.flags FWPM_SESSION_FLAG_DYNAMIC; result FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, session, engine); if (result ! ERROR_SUCCESS) return false; filter.layerKey FWPM_LAYER_ALE_AUTH_CONNECT_V4; filter.displayData.name LSuperDriver Block 443; filter.action.type FWP_ACTION_CALLOUT_TERMINATING; filter.action.calloutKey SUPERDRIVER_CALLOUT_GUID; filter.weight 0x100; conds[0].fieldKey FWPM_CONDITION_IP_REMOTE_PORT; conds[0].matchType FWP_MATCH_EQUAL; conds[0].conditionValue.uint16 443; conds[1].fieldKey FWPM_CONDITION_IP_PROTOCOL; conds[1].matchType FWP_MATCH_EQUAL; conds[1].conditionValue.uint8 IPPROTO_TCP; filter.filterCondition conds; filter.numFilterConditions 2; result FwpmFilterAdd0(engine, filter, NULL, NULL); FwpmEngineClose0(engine); return result ERROR_SUCCESS; }这里有两个容易踩坑的参数。第一是filter.weight它决定同一层里多个过滤器的优先级权重越大优先级越高如果系统里已经有别的防火墙你的规则可能在它后面执行action类型选择FWP_ACTION_CALLOUT_TERMINATING表示命中你的callout之后终止后续规则处理相当于把决定权完全收到SuperDriver手里。第二是条件字段类型FWPM_CONDITION_IP_REMOTE_PORT对应的是uint16协议字段uint8写错类型会让条件匹配结果直接错乱。运行到这里SuperDriver已经具备“拦截指定连接”的最小能力。把它做成一套产品还需要规则引擎。下一章就处理这个。4. 把规则引擎做成黑白名单从四元组匹配到配置“导包”落地规则引擎是防火墙的头脑。WFP引擎本身提供了非常快的内核态条件匹配但业务规则是动态的企业要一键放行某个网段研发要临时屏蔽某个IP这些不能靠改代码重新编译。因此SuperDriver要把规则维护在内核里再提供一套用户态接口来增删改查。4.1 黑白名单的数据结构与匹配优先级黑白名单是老生常谈但设计时有两个细节经常被忽略规则优先级和命中后的动作。我的习惯是给每条规则一个rank数值小的先匹配黑白名单同时存在时白名单rank更小先命中白名单即放行剩下没命中的流量再看黑名单最后落到默认策略。下面是一张典型规则表字段类型示例说明direction枚举outbound / inbound / forward匹配流量方向protocol枚举tcp / udp / icmp / any协议过滤localIp字符串或IPv4192.168.1.0/24本地地址或网段remoteIp字符串或IPv410.0.0.0/8远端地址或网段localPortuint168080应用层端口remotePortuint16443远端端口action枚举allow / block动作rankuint32100越小越先匹配enabledbooltrue是否启用内核态不适合直接存字符串我一般把规则编码成二进制结构体IP地址用网络序的UINT32存网段则拆成“起始地址掩码长度”两个字段。内核维护一个数组classify函数按rank排序后线性遍历。规则量少时线性遍历足够上千条规则后可以按端口或网段建哈希索引但这属于优化阶段的事第一版别过度设计。对应的C结构体可以是typedef struct _SUPERDRIVER_RULE { UINT32 rank; BOOLEAN enabled; UINT8 direction; // 0in 1out 2forward UINT8 protocol; // IPPROTO_TCP/UDP/ICMP/0any UINT8 action; // 0block 1allow UINT32 localAddr; UINT32 localMask; UINT32 remoteAddr; UINT32 remoteMask; UINT16 localPort; UINT16 remotePort; } SUPERDRIVER_RULE;classify函数里拿到流量后从FWPS_INCOMING_VALUES0中提取对应字段依次比较。如果协议字段是any就跳过协议匹配端口是0表示不限制端口IP和掩码配合可以匹配网段。比较时我特别强调一下WFP字段里的IP地址是网络字节序而Windows内核常用主机字节序千万不能直接拿内存值比较建议统一转成主机序再比较避免在大小端上翻车。黑白名单的语义要明确先按rank跑一遍所有启用规则第一条命中的规则决定动作如果整张表都没有命中执行默认策略。企业防火墙默认策略通常是“白名单模式下默认拦截黑名单模式下默认放行”。SuperDriver的classify函数里我默认把actionType设为FWP_ACTION_BLOCK然后遍历规则命中允许规则就改成FWP_ACTION_PERMIT这与“先白名单后黑名单”是一致的。4.2 与用户态联动的配置下发与“导包”落地规则要能从外部注入到内核。常见做法是创建设备对象和IOCTLSuperDriver创建一个\Device\SuperDriverFw设备用户态用CreateFile DeviceIoControl发送规则。下面是接收“添加规则”的IRP处理片段NTSTATUS SuperDriverDeviceControl(PDEVICE_OBJECT device, PIRP irp) { PIO_STACK_LOCATION stack IoGetCurrentIrpStackLocation(irp); ULONG code stack-Parameters.DeviceIoControl.IoControlCode; PVOID inBuf irp-AssociatedIrp.SystemBuffer; ULONG inLen stack-Parameters.DeviceIoControl.InputBufferLength; if (code IOCTL_SUPERDRIVER_ADD_RULE inLen sizeof(SUPERDRIVER_RULE)) { SUPERDRIVER_RULE* rule (SUPERDRIVER_RULE*)inBuf; NTSTATUS status SuperDriverAddRule(rule); irp-IoStatus.Status status; } IoCompleteRequest(irp, IO_NO_INCREMENT); return irp-IoStatus.Status; }SuperDriverAddRule内部会先取一个自旋锁再把规则追加到全局数组末尾。注意IOCTL的Buffer是SystemBuffer用户态下发时直接传结构体指针不要传带指针的链式结构如果规则里带了变长字符串我建议改用固定长度上限序列化时统一按字节数组传避免内核态解析字符串的各种边界麻烦。规则导入导出是产品化必须的“导包”能力。用户态把当前所有规则按一个JSON格式包导出来再在另一台机器上导入。这个包只保存业务层规则不保存内核状态导入时逐条调用IOCTL_ADD_RULE先清空再添加。JSON里的IP网段、协议、动作等字段做严格校验非法值直接拒绝避免导包文件被篡改后在内核里产生意想不到的匹配结果。我遇到过一种情况让用户导出的包在导入时把IPv6地址字符串截断会解析出错误规则这一点后面避坑章还会展开。规则更新时机也值得注意。如果classify函数正在遍历规则数组用户态同时插入新规则会读到半新的状态。我在设计里用读写锁classify走读锁规则增删走写锁。WFP分类回调运行在DISPATCH_LEVEL上不能直接拿快速锁所以更建议在classify里先把当前规则快照复制到一个局部副本再基于副本做匹配。这个细节做到位动态下发规则时才不会蓝屏。5. WFP驱动常见问题排查从0x800706d9到分类回调不触发这一章是硬通货。标题里出现了firewall和wfp实际开发中八成时间不是在写过滤逻辑而是在查基础设施问题。这里列出几条我踩过、也看同事踩过的坑每条都按“现象 → 原因 → 解决”写清楚。5.1 现象FwpmEngineOpen0返回0x800706d9刚搭好用户态管理程序一执行就报“操作失败”错误码0x800706d9。网上一搜大家都在说Win11防火墙错误代码0x800706d9。这个错误经常被理解成“Windows防火墙坏了”但在WFP开发里还有另一层含义FwpmEngineOpen0实际是通过RPC连接Base Filtering EngineBFE服务如果这个服务处于禁用或启动失败状态打开引擎就会返回这个错误码。解决路径先在管理员PowerShell里执行Get-Service BFE确认服务状态如果停止就Start-Service BFE。如果服务启动失败去事件查看器看System日志里BFE依赖的服务比如PolicyAgentIPsec Policy Agent是否被禁用了。做完之后重启SuperDriver的配置程序基本就好了。这个坑最容易发生在刚装好的Win11虚拟机里有人为了“优化系统”把一堆服务禁用掉BFE也在其列。5.2 现象Win11下驱动加载失败提示“无法验证发布者”使用测试证书编译出来的SuperDriver在Win11实机上装不上事件管理器里显示“驱动程序无法验证数字签名”。原因是近年Windows对内核驱动签名策略收紧了要求测试签名证书只对开启了测试签名模式的系统有效。解决把开发机切到测试签名模式管理员命令行执行bcdedit /set testsigning on然后重启。如果连测试模式都没开用signtool对.sys文件做签名后再尝试加载。注意不要用吊销的证书内核会拒绝。还有一个被忽略的细节WDK自带的INF文件里CatalogFile节点必须指向实际的.cat文件否则签名校验过不了。这个问题不做提前准备等驱动写完了再去排真的很浪费时间。5.3 现象callout注册成功但classify函数就是不回调这个坑我见过至少三次。在用户态执行FwpmFilterAdd0返回成功抓包也能看到流量在走但SuperDriverClassify就是不执行。原因在于过滤器的action.type可能不是callout类常见的错误是把action.type设成FWP_ACTION_BLOCK或FWP_ACTION_PERMIT那样流量被引擎自己拦掉或直通永远不会进callout。解决把action.type改为FWP_ACTION_CALLOUT_TERMINATING同时action.calloutKey要填对全局唯一GUID。如果GUID不一致FwpmFilterAdd0也会报错但最隐蔽的是GUID一致、层不一致——callout注册在ALE_AUTH_CONNECT_V4过滤器却加到OUTBOUND_IPPACKET_V4那也不会进你的回调。所以排查时先确认callout的applicableLayer和filter.layerKey完全一致。5.4 现象规则下发后用一会儿就消失重启后全部丢失之前为了调试方便在FWPM_SESSION0里设置了FWPM_SESSION_FLAG_DYNAMIC。动态会话的过滤器在进程退出时自动删除所以SuperDriver配置程序一关规则全部没了。这在开发期很方便但产品化之后是事故。解决动态会话只用于开发调试正式安装版要用持久化会话且在FwpmFilterAdd0前开启一个事务FwpmTransactionBegin0 FwpmFilterAdd0 FwpmTransactionCommit0让规则写入到系统持久化存储中。另一层保险是SuperDriver自己把规则备份到一个配置文件里服务启动时重新加载避免WFP存储异常导致规则丢失。5.5 现象接入SuperDriver后访问网页卡顿或者大流量拖垮CPU某次在测试机上装完驱动打开浏览器要等好几秒。问题出在classify函数里做了太多事情每次TCP连接都查一次注册表、读一次数据库、还要做一次证书解析。WFP分类回调运行在协议栈关键路径上任何慢操作都会直接拖垮网络。解决把classify里能前置的过滤条件全部用WFP引擎的条件字段完成比如IP、端口、协议用FWPM_FILTER0去匹配让引擎在进callout之前就丢掉大部分流量只把那些需要业务逻辑的流量交给callout。如果确实需要做耗时的内容检查可以记录待处理队列等用户态拿结果后再决定放行还是拦截。另外注意不要在DISPATCH_LEVEL主动睡眠或锁大自旋区这是蓝屏的典型来源。6. 进阶让过滤粒度变粗同一层只裁决一次如果你已经跑通上面的最小链路下一个优化方向是把过滤粒度从“每个包”提升到“每条连接”。ALE层Application Layer Enforcement是WFP专门为连接层过滤设计的一条TCP连接只在建立时经过ALE_AUTH_CONNECT_V4或ALE_AUTH_RECV_ACCEPT_V4之后的数据包不再重复进classify。与其在OUTBOUND_IPPACKET_V4这种包级层里逐包遍历规则不如在ALE层把连接裁决完然后在classifyOut里直接返回FWP_ACTION_PERMIT。常见做法是结合流上下文在ALE层首次命中时把SuperDriver的流上下文和连接绑定后续该连接的所有数据包无需再走业务规则。这个操作的收益很明显——一个网页几十个小连接如果每个包都走一遍规则CPU开销会高一个数量级而只裁决每一条连接的建立规则引擎的负载就下来了。验证SuperDriver是否生效我通常做一轮干净的端到端测试先在测试机上关闭系统自带防火墙验证“关掉自带防火墙有没有影响”再加载SuperDriver并下发一条拦截外网TCP 443的规则然后依次做三件事。第一用tcping或curl访问一个HTTPS站点连接超时说明出站被拦第二把规则改成只允许特定IP网段正常访问网段内站点访问网段外站点失败第三在抓包工具里看SYN包发出去但收不到SYN/ACK证明拦截点位于协议栈核心路径而不是用户态。最后说个我的教训。WFP驱动的签名和BFE依赖问题一定要在项目第一天就准备好基础环境别等代码写完才去开测试签名模式。养成每次改动后用signtool重新签名、实机验证一轮的习惯比事后排查快得多。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询