☰
Windows防火墙源码深度解析:NDIS中间层驱动与包过滤实战
2026/10/9 8:25:42 网站建设 项目流程

简介:这是一份面向网络安全初学者与防火墙开发爱好者的源代码学习资源,围绕防火墙核心功能的实现展开,适合具备一定C/C++基础、希望深入理解包过滤与网络钩子机制的技术人员参考。压缩包共236个文件,以54个.h头文件与40个.cpp源文件为主体,辅以9个.c文件、4个makefile及4个sources构建配置,另有23个ico图标、10个htm页面、7个dll动态库与5个exe可执行文件,整体约1.23MB,工程结构完整,便于直接编译与调试。内容涵盖数据包处理、收发流程及钩子拦截等模块,从预览可见涉及包过滤与网络驱动相关实现,能帮助读者梳理防火墙底层通信与拦截逻辑。目前已有399人学习下载,可作为课程设计、毕业设计或安全机制研究的参考素材,读者可从中获取完整的工程组织方式、模块划分思路与关键代码实现,用于理解防火墙从数据捕获到规则处理的整体链路。

1. 一份 Windows 防火墙源码包:从 Build.bat 到 xpassthru.c 的完整拆解

拿到一个叫firewall的源码包,目录里躺着Build.bat、SPLASH.BMP、ABOUT.BMP、MINIHOOK.C、PROTHOOK.C、Packet.c、RECV.C、SEND.C、xpassthru.c这几个文件,第一反应往往是「这玩意儿能编译吗、跑起来能拦包吗」。这不是一份教学 Demo,而是一套典型的 Windows 平台 NDIS 中间层驱动 + 用户态界面混合的防火墙工程骨架:xpassthru.c是核心的 Passthru 驱动改造文件,负责在协议栈里挂接过滤逻辑;Packet.c处理包解析与规则匹配;RECV.C/SEND.C分别对应收发包路径上的拦截点;MINIHOOK.C和PROTHOOK.C则是用户态与内核态通信、协议钩子的部分。它适合两类人:一是想搞懂 Windows 防火墙底层到底怎么挂包过滤的驱动开发者,二是需要一套可编译、可改规则的源码基线来做二次开发或教学演示的工程师。下面按「先编译跑通、再拆核心模块、最后避坑」的顺序,把这份资源真正用起来。

2. 编译环境与构建脚本:Build.bat 里到底做了什么

2.1 为什么必须用 WDK 而不是 Visual Studio 直接开

这份源码的构建入口是Build.bat,而不是.sln解决方案文件。这是 Windows 驱动工程的典型特征:驱动编译依赖 WDK(Windows Driver Kit)提供的build环境变量、msvcrt版本、DDK 头文件路径以及sources/makefile约定。如果你直接双击.c文件用 VS 打开,会立刻报ntddk.h找不到、NDIS_HANDLE未定义这类错误,因为用户态编译环境根本没有内核头文件。

常见做法是:安装与目标系统匹配的 WDK 版本(比如目标 Windows 7 用 WDK 7600,目标 Windows 10 用 WDK 10),然后从「开始菜单 → Windows Driver Kits → Build Environments」里选择对应架构的命令行环境。Build.bat内部一般会调用build -cZ或nmake来触发编译。先别急着改代码,第一步是确认这个脚本里的路径和你的 WDK 安装路径是否一致。

2.2 逐行拆 Build.bat 并跑通第一次编译

把Build.bat用文本编辑器打开,典型内容如下(不同版本略有差异,但结构一致):

@echo off REM 设置 DDK 环境变量,指向 WDK 安装根目录 set DDKROOT=C:\WinDDK\7600.16385.1 call %DDKROOT%\bin\setenv.bat %DDKROOT% fre x86 REM 进入源码目录并执行 build cd /d %~dp0 build -cZ pause

逻辑说明:setenv.bat是 WDK 提供的环境初始化脚本,第一个参数是 WDK 根目录,第二个参数fre表示 free build(发布版,不带调试符号),第三个参数x86是目标架构。build -cZ中-c表示 clean 后重新编译,-Z表示输出详细编译信息。参数怎么改:如果你要调试驱动,把fre改成chk(checked build),会生成带断点信息的.sys;如果目标是 64 位系统,把x86改成amd64。

第一次编译大概率会在xpassthru.c上报错,常见的是NDIS_PROTOCOL_CHARACTERISTICS结构体成员不匹配,因为不同 WDK 版本的 NDIS 版本号不同。解决办法是在sources文件里确认NDIS_MINIPORT_MAJOR_VERSION和NDIS_PROTOCOL_MAJOR_VERSION是否与你的 WDK 匹配。编译成功后会在objfre_wxp_x86\i386目录下生成firewall.sys,这就是驱动二进制。

提示:编译前先确认sources文件存在且内容完整,很多流传的源码包会漏掉这个文件,导致build直接报No sources file found。

2.3 安装与加载驱动的正确姿势

编译出.sys只是第一步,加载它需要配合.inf文件。如果源码包里没有.inf,你需要自己写一个最简安装脚本,或者用sc create手动注册服务:

REM 注册驱动服务,类型为内核驱动 sc create FirewallFilter type= kernel binPath= C:\path\to\firewall.sys REM 启动服务 sc start FirewallFilter

参数说明:type= kernel表示内核驱动,binPath必须写绝对路径且等号后有空格。启动后可以用sc query FirewallFilter看状态,如果是RUNNING说明驱动已挂载。但注意,NDIS 中间层驱动还需要在网卡属性里绑定,否则不会真正过滤流量。这一步是新手最容易翻车的地方:驱动跑起来了,但网卡没绑定,等于没装。

3. 核心过滤逻辑:xpassthru.c 与 Packet.c 怎么配合

3.1 Passthru 驱动模型与挂接点选择

xpassthru.c这个名字来自 WDK 自带的 Passthru 示例,它的本质是一个 NDIS 中间层驱动,插在协议栈的 miniport 和 protocol 之间。数据包从网卡上来,先经过 miniport 边缘,再经过中间层,最后到 TCP/IP 协议栈。xpassthru.c里最关键的函数是PtReceive和PtSendPackets,前者处理接收路径,后者处理发送路径。

为什么选中间层而不是 WFP 或 TDI?因为这份源码的年代和结构决定了它是 NDIS 5.x/6.x 时代的产物,中间层驱动能同时看到收发两个方向,且不依赖 Windows Filtering Platform 的复杂回调注册。代价是兼容性差,在 Windows 10 新版本上可能因为驱动签名强制策略而加载失败。如果你只是学习包过滤原理,这份代码足够;如果要上生产,得考虑迁移到 WFP 或 NDIS 6.30 以上的轻量过滤框架。

3.2 Packet.c 里的规则匹配与包解析

Packet.c负责把xpassthru.c递过来的NDIS_PACKET拆成以太网头、IP 头、TCP/UDP 头,然后跟规则表比对。典型代码片段如下:

// 解析以太网头,判断是否为 IP 包 typedef struct _ETH_HEADER { UCHAR DstMac[6]; UCHAR SrcMac[6]; USHORT EtherType; } ETH_HEADER, *PETH_HEADER; BOOLEAN CheckPacket(PNDIS_PACKET Packet) { PETH_HEADER eth = (PETH_HEADER)NDIS_BUFFER_TO_SPAN(Packet); // 0x0800 表示 IPv4 if (ntohs(eth->EtherType) != 0x0800) { return TRUE; // 非 IP 包直接放行 } // 继续解析 IP 头,提取源/目的 IP 和端口 // 与规则表比对,命中则返回 FALSE 表示丢弃 return MatchRule(eth); }

逻辑说明:NDIS_BUFFER_TO_SPAN拿到包的首地址,ntohs做网络字节序到主机字节序转换。参数怎么改:规则表通常是一个链表或数组,你可以把MatchRule改成从注册表或用户态 ioctl 动态读取,这样就能实现运行时更新规则。失败时看什么:如果CheckPacket返回 FALSE 但包没被丢,检查xpassthru.c里调用CheckPacket后是否正确调用了NdisReturnPackets或直接丢弃。

3.3 RECV.C 与 SEND.C 的分工差异

RECV.C和SEND.C分别处理接收和发送路径上的过滤。接收路径上,包已经过 miniport,你要决定是NdisMIndicateReceivePacket往上递还是直接丢弃;发送路径上,包来自协议栈,你要决定是NdisSendPackets往下发还是拦截。两者的关键区别在于:接收路径丢包不会通知发送方,发送路径丢包会让上层协议超时重传。

常见做法是在SEND.C里做出站过滤(比如禁止访问某些 IP),在RECV.C里做入站过滤(比如阻止特定端口扫描)。参数上,SEND.C里要注意NdisSendPackets的返回值处理,如果返回NDIS_STATUS_PENDING,你得在发送完成回调里释放包,否则内存泄漏。这是驱动开发里最隐蔽的坑之一,跑久了系统蓝屏往往就是这里没处理好。

4. 用户态与内核态通信:MINIHOOK.C 和 PROTHOOK.C 的角色

4.1 钩子机制与 ioctl 通信通道

MINIHOOK.C和PROTHOOK.C这两个文件从命名看,一个偏向 miniport 侧的钩子,一个偏向 protocol 侧的钩子。它们通常配合一个用户态界面程序(源码包里可能没带界面,只有驱动部分),通过DeviceIoControl下发规则。典型流程是:用户态程序打开\\.\FirewallFilter设备,调用IOCTL_ADD_RULE把规则结构体传进内核,内核在PROTHOOK.C里解析并插入规则表。

// 用户态下发规则的典型调用 HANDLE hDevice = CreateFile("\\\\.\\FirewallFilter", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); RULE rule = {0}; rule.SrcIp = inet_addr("192.168.1.100"); rule.Action = ACTION_BLOCK; DWORD bytesReturned; DeviceIoControl(hDevice, IOCTL_ADD_RULE, &rule, sizeof(rule), NULL, 0, &bytesReturned, NULL);

逻辑说明:CreateFile打开的是驱动创建的符号链接,IOCTL_ADD_RULE是自定义的控制码,需要在驱动里用CTL_CODE宏定义一致。参数怎么改:rule结构体的字段必须与驱动里PROTHOOK.C定义的结构体完全一致,包括对齐方式,否则会读到垃圾数据。失败时看什么:DeviceIoControl返回 FALSE 时用GetLastError看错误码,ERROR_ACCESS_DENIED通常是权限不够,需要管理员运行。

4.2 规则表同步与并发保护

内核态规则表会被多个路径同时访问:RECV.C在读,SEND.C在读,PROTHOOK.C在写。如果不加锁,轻则规则错乱,重则蓝屏。常见做法是用KeAcquireSpinLock或ExAcquireFastMutex保护规则链表。MINIHOOK.C里如果涉及钩子链的修改,还要考虑InterlockedExchangePointer这类原子操作。

我一般会建议在规则表结构里加一个版本号,用户态每次下发规则时递增,内核态读取时比对版本号,避免读到半更新的状态。这个技巧在调试时特别有用,能快速判断是规则没下发成功还是匹配逻辑写错了。

5. 避坑与排查:这份源码包最容易翻车的五个地方

5.1 编译报错cannot open file 'ntddk.h'

现象:运行Build.bat后立刻报找不到ntddk.h或ndis.h。原因:WDK 环境变量没设置成功,或者setenv.bat的路径写错了。解决:手动在命令行里执行set DDKROOT=你的WDK路径,然后call %DDKROOT%\bin\setenv.bat %DDKROOT% fre x86,再运行build。如果还不行,检查 WDK 是否完整安装,有些精简版 WDK 不带 NDIS 头文件。

5.2 驱动加载失败,错误码 577 或 1275

现象:sc start FirewallFilter返回错误 577或错误 1275。原因:577 表示驱动签名验证失败,1275 表示驱动被阻止加载。解决:在测试机上临时关闭驱动签名强制(bcdedit /set testsigning on后重启),或者用测试签名对.sys签名。注意,这份源码年代较早,可能没有有效的数字签名,生产环境不要这么干。

5.3 网卡绑定后网络直接断掉

现象:驱动加载成功,网卡也绑定了,但浏览器打不开网页,ping 也不通。原因:Packet.c里的默认规则是「全部丢弃」,或者CheckPacket的返回值逻辑写反了。解决:先在CheckPacket开头加一行return TRUE;强制放行所有包,确认网络恢复,再逐步加规则。这个排查方法能快速定位是过滤逻辑问题还是驱动挂接问题。

5.4 系统运行一段时间后蓝屏,错误码IRQL_NOT_LESS_OR_EQUAL

现象:驱动跑几分钟到几小时不等,突然蓝屏,dump 指向xpassthru.c或Packet.c。原因:在DISPATCH_LEVEL上调用了分页内存或浮点运算,或者包完成回调里重复释放了NDIS_PACKET。解决:检查所有在PtReceive/PtSendPackets里调用的函数,确保没有ExAllocatePoolWithTag带PagedPool,也没有浮点操作。包释放用NdisReturnPackets或NdisFreePacket只能二选一,不能重复。

5.5 规则下发成功但不生效

现象:DeviceIoControl返回成功,但目标 IP 还是能访问。原因:规则表插入了,但RECV.C/SEND.C里比对时用的字段偏移不对,比如 IP 头长度没考虑可选字段,导致读到的端口号是错的。解决:在Packet.c里加调试输出,把解析出的源 IP、目的 IP、端口打印到DbgPrint,用DebugView抓下来看。常见错误是iphdr->ihl没乘以 4,导致 TCP 头偏移算错。

6. 进阶技巧:用 DbgPrint 和 WinDbg 验证过滤路径

6.1 在关键路径埋点并抓取实时日志

驱动调试最有效的手段不是加界面,而是在关键分支加DbgPrint,然后用DebugView或 WinDbg 抓。比如在xpassthru.c的PtReceive入口加:

DbgPrint("[Firewall] PtReceive: pkt=%p, len=%d\n", Packet, NdisGetPacketLength(Packet));

逻辑说明:DbgPrint是内核态打印函数,输出到调试器或DebugView。参数怎么改:把Packet指针和包长度打出来,能快速判断包有没有进到过滤函数。注意DbgPrint在 free build 里可能被优化掉,调试时用 checked build 更稳。抓日志时把DebugView的「Capture Kernel」打开,否则看不到内核输出。

6.2 用 WinDbg 断点验证规则匹配

如果DbgPrint不够,可以用 WinDbg 双机调试,在CheckPacket函数上下断点:

# 在 WinDbg 里设置符号路径并下断点 .sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbols bp firewall!CheckPacket g

参数说明:bp是断点命令,firewall!CheckPacket是模块名加函数名。断下来后用dv看局部变量,用kb看调用栈。这个方法的门槛是得配双机调试环境,但一旦配好,定位规则匹配问题比DbgPrint快得多。我一般会在规则表插入和查询两个点都下断点,对比传入的包信息和规则信息是否一致。

6.3 一个验证过滤是否真正生效的笨办法

最后分享一个不依赖调试器的验证方法:在测试机上开一个持续 ping,然后在用户态下发一条「阻止 ICMP」的规则。如果 ping 立刻从「回复」变成「请求超时」,说明发送路径过滤生效;如果本机 ping 外部不通但外部 ping 本机也不通,说明收发两个路径都挂上了。反过来,如果规则下发后 ping 没变化,先检查网卡绑定,再检查规则表是否真的插入了。这个办法虽然笨,但能排除掉八成「以为生效了其实没生效」的玄学问题。

从那以后我每次拿到这类驱动源码,都强制先跑通「全放行」再逐条加规则,绝不一上来就写复杂过滤逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询