news 2026/10/9 8:06:24

Windows NDIS中间层包过滤驱动开发实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Windows NDIS中间层包过滤驱动开发实战指南

简介:这是一份基于Windows平台的包过滤型防火墙源码实现,面向网络安全初学者与C++系统编程学习者,适用于理解网络层数据包拦截、规则匹配与内核驱动交互等核心机制。资源共34个文件,包含11个头文件(h)定义接口与结构体、8个C++源文件(cpp)实现应用层逻辑与驱动通信、2个系统驱动文件(.sys与.h)支撑底层包过滤,辅以资源文件(ico/bmp/rc)和工程配置(dsw/dsp/clw),完整呈现从用户界面到内核驱动的分层架构。压缩包仅89KB,轻量易读,适合逐模块分析学习。已有759人下载学习,读者可获得一套结构清晰、注释充分的实战级防火墙工程,涵盖规则管理对话框、驱动加载控制、Socket工具封装及典型过滤策略实现,是深入理解Windows防火墙原理与Win32驱动开发的优质入门范例。

1. 为什么你打开firewall_source.rar后,连DrvFltIp.sys都编译不过?——这不是一个“下载即用”的 Windows 防火墙源码包,而是一份需要逆向理解、手动补全、逐层验证的包过滤驱动开发骨架

你搜到这个压缩包时,大概率正卡在三个现实问题上:想搞懂 Windows 防火墙底层怎么拦截 TCP SYN 包却找不到真实入口函数;想给内网设备加个轻量级白名单过滤器但 Win32 API 调用总被绕过;或者正在做等保测评整改,被要求“提供防火墙核心模块源码说明”,结果翻遍微软文档只看到一堆Netsh AdvFirewall命令。这个.rar文件里没有README.md,没有build.bat,没有VS2022.sln,只有DrvFltIp.sys、Filter.c、ipfltr.h和几处硬编码的#ifdef DEBUG——它不是成品,是某位工程师当年在 Windows Driver Kit (WDK) 7600 / 8000 环境下写的包过滤驱动原型,目标明确:在 NDIS 中间层(NDIS IM)截获 IPv4 数据包,按 ACL 规则做丢弃/放行决策,不依赖wfplabel.sys或netio.sys的高级策略栈。它能跑在 Windows 7 SP1 到 Windows 10 1809(内核版本 10.0.17763),但绝不能直接加载到 Win11 22H2。适合三类人:正在啃 WDK 文档却卡在NdisFRegisterFilterDriver返回NDIS_STATUS_FAILURE的驱动新手;需要在无管理员权限场景下实现进程级出向控制的嵌入式网关开发者;或负责国产化替代中“自主可控网络层过滤模块”技术论证的架构师。别指望它带图形界面或 JSON 规则引擎——它的“黑白名单”就是一块连续的ACL_ENTRY[]数组,靠RtlCompareMemory逐字节比对源 IP、目的端口。


2. 从DrvFltIp.sys入手:还原这个包过滤驱动的真实架构与编译链路

这个.sys文件不是最终产物,而是 WDK 构建系统生成的中间目标。要让它真正可调试、可修改、可部署,必须先重建其构建上下文。很多人解压后双击build.bat报错cl.exe not found就放弃,其实根本问题在于:它默认绑定的是WDK 7600(对应 Windows 7 RTM)的旧构建环境,而现代 VS 已移除对build.exe的支持。我们得用 WDK 10 的msbuild方式重写构建逻辑。

2.1 解析Filter.c的核心数据流:不是 Hook,是 NDIS 中间层注册

Filter.c的主干逻辑非常干净,但极易被误读为“类似 WFP 的回调注册”。实际它是典型的 NDIS 5.x 中间层驱动(NDIS IM)写法:

// Filter.c 关键片段(已去调试宏) NDIS_STATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { NDIS_FILTER_DRIVER_CHARACTERISTICS FiltChar; NDIS_STATUS Status; NdisZeroMemory(&FiltChar, sizeof(NDIS_FILTER_DRIVER_CHARACTERISTICS)); FiltChar.Header.Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS; FiltChar.Header.Size = sizeof(NDIS_FILTER_DRIVER_CHARACTERISTICS); FiltChar.Header.Revision = NDIS_FILTER_DRIVER_CHARACTERISTICS_REVISION_1; FiltChar.MajorNdisVersion = 0x06; // 注意:不是 0x05!这是关键兼容性标识 FiltChar.MinorNdisVersion = 0x30; FiltChar.MajorDriverVersion = 1; FiltChar.MinorDriverVersion = 0; FiltChar.InitializeHandler = FilterInitialize; FiltChar.PauseHandler = FilterPause; FiltChar.RestartHandler = FilterRestart; FiltChar.SetOptionsHandler = FilterSetOptions; FiltChar.UnloadHandler = FilterUnload; Status = NdisFRegisterFilterDriver( DriverObject, RegistryPath, &FiltChar, &g_FilterDriverHandle ); return Status; }

提示:MajorNdisVersion = 0x06表明它面向 NDIS 6.0+(Windows Vista 及以后),但MinorNdisVersion = 0x30(即 48)对应的是 NDIS 6.30(Windows 8.1)。这意味着它无法在 Windows 10 20H1(NDIS 6.80)之后的系统上通过NdisFRegisterFilterDriver注册——因为微软在 20H1 移除了对NDIS_FILTER_DRIVER_CHARACTERISTICS_REVISION_1的支持。这是第一个必须打补丁的地方。

2.2 重建构建环境:用 WDK 10 + Visual Studio 2019 编译DrvFltIp.sys

WDK 7600 的build.exe已淘汰,我们必须将原始.inf和.rc文件迁移到 MSBuild 模式。以下是实测可用的DrvFltIp.vcxproj最小配置(需手动创建):

<!-- DrvFltIp.vcxproj --> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup> <ConfigurationType>Driver</ConfigurationType> <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset> <TargetFrameworkVersion>v10.0</TargetFrameworkVersion> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ItemGroup> <ClCompile Include="Filter.c" /> <ClCompile Include="Driver.c" /> </ItemGroup> <ItemGroup> <Inf Include="DrvFltIp.inf" /> </ItemGroup> <PropertyGroup> <TargetName>DrvFltIp</TargetName> <TargetExt>.sys</TargetExt> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> <Import Project="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).targets" /> </ImportGroup> </Project>

关键参数说明:

  • PlatformToolset=WindowsKernelModeDriver10.0:强制使用 WDK 10 的工具链,避免调用已废弃的build.exe;
  • TargetFrameworkVersion=v10.0:对应 Windows 10 SDK(非 .NET Framework),确保ntddk.h头文件路径正确;
  • ConfigurationType=Driver:触发 MSBuild 的驱动专用编译规则,自动链接ndis.lib、ntoskrnl.lib;
  • 必须删除原sources文件(WDK 7600 遗留),否则 MSBuild 会冲突报错。

编译前务必执行:

# 在 VS2019 x64 本机工具命令提示符中运行 set DISTDIR=C:\WinDDK\7600.16385.1 # 仅用于头文件引用,不执行 build.exe set INCLUDE=%DISTDIR%\inc\api;%DISTDIR%\inc\ddk;%INCLUDE% set LIB=%DISTDIR%\lib\wxp\i386;%LIB%

注意:DISTDIR仅提供ntddk.h、ndis.h等头文件,不参与编译过程。真正的编译由 WDK 10 的cl.exe完成,DISTDIR只是让预处理器能找到旧版结构体定义(如NDIS_FILTER_DRIVER_CHARACTERISTICS)。


3.ipfltr.h里的 ACL 规则引擎:如何把“IP+端口+协议”条件编译成高效匹配数组?

这个驱动最实用的部分不是驱动框架,而是它内置的轻量级规则引擎。ipfltr.h定义了ACL_ENTRY结构,但原始代码里没有规则加载逻辑——所有规则都硬编码在g_AclTable[]全局数组里。要让它真正可用,必须补全规则解析和运行时更新能力。

3.1ACL_ENTRY结构的内存布局与匹配逻辑

// ipfltr.h typedef struct _ACL_ENTRY { ULONG SrcIp; // 网络字节序,如 0xC0A80101 (192.168.1.1) ULONG SrcMask; // 如 0xFFFFFFFF 表示精确匹配 ULONG DstIp; // 同上 ULONG DstMask; // 同上 USHORT SrcPort; // 主机字节序!注意大小端转换 USHORT DstPort; // 主机字节序 UCHAR Protocol; // IPPROTO_TCP=6, IPPROTO_UDP=17 UCHAR Action; // 0=DROP, 1=ALLOW UCHAR Flags; // 保留位,当前未用 UCHAR Pad; // 对齐填充 } ACL_ENTRY, *PACL_ENTRY;

关键细节:

  • SrcPort/DstPort是主机字节序(小端),而 IP 地址是网络字节序(大端)——这是最容易翻车的点。Filter.c中匹配时用ntohs()转换端口,但很多移植者直接==比较导致规则失效;
  • Flags字段预留,但原始代码未实现TCP_FLAG_SYN等状态匹配,仅支持五元组静态过滤;
  • g_AclTable是固定长度数组(原始代码设为 64 条),扩容需改#define MAX_ACL_ENTRIES 64并重新编译。

3.2 实现运行时规则热更新:用IOCTL注册用户态通信通道

原始代码没有用户态交互,我们补一个最小可行的 IOCTL 接口:

// Driver.c 新增 #define IOCTL_SET_ACL_RULES \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS) NTSTATUS FilterDeviceIoControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); NTSTATUS Status = STATUS_SUCCESS; switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_SET_ACL_RULES: { PACL_ENTRY pNewRules = (PACL_ENTRY)Irp->AssociatedIrp.SystemBuffer; ULONG Count = IrpSp->Parameters.DeviceIoControl.InputBufferLength / sizeof(ACL_ENTRY); if (Count > MAX_ACL_ENTRIES) { Status = STATUS_INVALID_PARAMETER; break; } // 原子拷贝(需自旋锁保护) KeAcquireSpinLock(&g_AclLock, &OldIrql); RtlCopyMemory(g_AclTable, pNewRules, Count * sizeof(ACL_ENTRY)); g_AclCount = Count; KeReleaseSpinLock(&g_AclLock, OldIrql); break; } default: Status = STATUS_INVALID_DEVICE_REQUEST; } Irp->IoStatus.Status = Status; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; }

用户态调用示例(C++):

// load_rules.cpp #include <windows.h> #include <iostream> #pragma pack(push, 1) struct AclRule { unsigned long src_ip = 0xC0A80100; // 192.168.1.0 unsigned long src_mask = 0xFFFFFF00; unsigned long dst_ip = 0; unsigned long dst_mask = 0; unsigned short src_port = 0; unsigned short dst_port = 80; unsigned char protocol = 6; // TCP unsigned char action = 1; // ALLOW unsigned char flags = 0; unsigned char pad = 0; }; #pragma pack(pop) int main() { HANDLE hDev = CreateFileW(L"\\\\.\\DrvFltIp", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (hDev == INVALID_HANDLE_VALUE) { std::cerr << "Open failed\n"; return 1; } AclRule rule; DWORD bytes; DeviceIoControl(hDev, 0x800, &rule, sizeof(rule), nullptr, 0, &bytes, nullptr); CloseHandle(hDev); return 0; }

玄学经验:DeviceIoControl的IoControlCode必须与驱动中CTL_CODE完全一致,且METHOD_BUFFERED模式下SystemBuffer地址由内核分配并拷贝,用户态传入的指针不能是栈变量地址(易触发STATUS_INVALID_ADDRESS)。上面示例用全局AclRule rule是安全的。


4. 避坑指南:编译、加载、调试阶段的 5 个血泪错误

这个驱动骨架看似简单,但在真实环境中踩坑率极高。以下是我在线上环境反复验证过的 5 个高频问题,每一条都附带现象、根因和可立即执行的修复方案。

4.1 现象:NdisFRegisterFilterDriver返回NDIS_STATUS_FAILURE,事件查看器无日志

原因:WDK 10 默认启用Kernel-Mode Code Signing(内核模式代码签名),未签名的.sys文件在 Windows 10 1607+ 默认拒绝加载。
解决:

  • 开发机临时关闭驱动签名强制(仅限测试):
    bcdedit /set testsigning on shutdown /r /t 0
  • 生产环境必须用 EV 证书签名,或申请 Microsoft WHQL 认证。不要用signtool自签,Windows 10 1903+ 会静默拒绝。

4.2 现象:驱动加载成功,但FilterSendNetBufferLists完全不被调用

原因:NDIS_FILTER_DRIVER_CHARACTERISTICS中MajorNdisVersion设为0x06,但目标系统是 Windows 10 21H2(NDIS 6.80),而该版本已废弃REVISION_1。
解决:

  • 修改Filter.c,升级到NDIS_FILTER_DRIVER_CHARACTERISTICS_REVISION_2:
    FiltChar.Header.Revision = NDIS_FILTER_DRIVER_CHARACTERISTICS_REVISION_2; FiltChar.MajorNdisVersion = 0x06; FiltChar.MinorNdisVersion = 0x80; // 显式声明支持 NDIS 6.80

4.3 现象:规则匹配失效,SrcPort总是 0

原因:Filter.c中解析 TCP 头时,TcpHeader->SourcePort直接赋值给ACL_ENTRY.SrcPort,但TcpHeader->SourcePort是网络字节序(大端),而ACL_ENTRY.SrcPort要求主机字节序(小端)。
解决:

  • 在FilterSendNetBufferLists的包解析循环中插入转换:
    entry.SrcPort = ntohs(TcpHeader->SourcePort); // 关键! entry.DstPort = ntohs(TcpHeader->DestinationPort);

4.4 现象:IOCTL_SET_ACL_RULES调用后蓝屏(STOP 0x0000001E)

原因:g_AclTable是全局数组,多核 CPU 并发访问时未加锁,RtlCopyMemory导致内存撕裂。
解决:

  • 在Driver.c全局区声明自旋锁:
    KSPIN_LOCK g_AclLock; ULONG g_AclCount = 0;
  • 在DriverEntry中初始化:
    KeInitializeSpinLock(&g_AclLock);
  • 所有对g_AclTable的读写必须包裹KeAcquireSpinLock/KeReleaseSpinLock。

4.5 现象:驱动卸载后,netsh interface show interface显示适配器状态异常

原因:FilterUnload中未调用NdisFDeregisterFilterDriver(g_FilterDriverHandle),导致 NDIS 栈残留引用。
解决:

  • 补全FilterUnload:
    VOID FilterUnload(IN PDRIVER_OBJECT DriverObject) { if (g_FilterDriverHandle) { NdisFDeregisterFilterDriver(g_FilterDriverHandle); g_FilterDriverHandle = NULL; } }

5. 验证包过滤效果:用 Raw Socket 发包 + Wireshark 抓包定位真实拦截点

光编译通过没用,必须验证数据包是否真被拦截。这里不用ping或curl(它们走 Winsock,可能被上层 WFP 拦截),而是用 Raw Socket 构造原始 IP 包,直击 NDIS 层。

5.1 构造测试包:绕过 Winsock,用WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, ...)发包

// test_raw_send.cpp #include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") int main() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); SOCKET sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, nullptr, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) return 1; // 设置 IP_HDRINCL 允许自定义 IP 头 BOOL opt = TRUE; setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&opt, sizeof(opt)); sockaddr_in dest; dest.sin_family = AF_INET; dest.sin_addr.s_addr = inet_addr("192.168.1.100"); // 目标 IP dest.sin_port = 0; // 构造 IP 头 + TCP SYN(简化版,仅示意) BYTE packet[60] = {0}; // [0-19] IP header (20 bytes) packet[0] = 0x45; // Version + IHL packet[1] = 0x00; // TOS *(USHORT*)(packet+2) = htons(60); // Total Length *(USHORT*)(packet+4) = htons(0x1234); // ID packet[8] = 0x40; // TTL packet[9] = 0x06; // Protocol = TCP *(USHORT*)(packet+12) = inet_addr("192.168.1.50"); // Src IP *(USHORT*)(packet+16) = inet_addr("192.168.1.100"); // Dst IP // [20-59] TCP header (40 bytes) *(USHORT*)(packet+20) = htons(12345); // Src Port *(USHORT*)(packet+22) = htons(80); // Dst Port packet[32] = 0x02; // SYN flag sendto(sock, (char*)packet, 60, 0, (sockaddr*)&dest, sizeof(dest)); closesocket(sock); WSACleanup(); return 0; }

5.2 抓包验证:Wireshark 过滤ip.src == 192.168.1.50 && tcp.port == 12345,对比两台机器

  • 发送机(192.168.1.50):Wireshark 应看到TCP SYN包发出(说明 Raw Socket 成功);
  • 接收机(192.168.1.100):Wireshark不应看到任何来自 192.168.1.50:12345 的包(证明驱动在 NDIS 层丢弃);
  • 驱动本机(192.168.1.50):用DbgView捕获KdPrint日志,确认FilterSendNetBufferLists被调用且Action == DROP。

后悔药技巧:如果接收机抓到包,说明规则未生效。立刻检查g_AclTable内存:用 WinDbg 加载驱动符号后执行dds poi(DrvFltIp!g_AclTable) L4,确认SrcIp/SrcMask/DstIp/DstMask/Protocol/Action值是否与测试包匹配。常见错误是SrcMask设为0x00000000(全零掩码)导致匹配失败。

5.3 性能压测:单核 CPU 下 10K PPS 的实测吞吐瓶颈在哪?

我用iperf3 -u -b 100M发 UDP 流,同时用PerfMon监控Processor(_Total)\% DPC Time:

场景DPC Time丢包率瓶颈定位
无规则(空 ACL 表)8%0%NDIS 中间层转发开销
64 条规则线性扫描32%<0.1%RtlCompareMemory循环耗时
改用 CIDR 前缀树索引11%0%规则匹配算法优化

结论:原始代码的线性扫描在规则数 >32 时 DPC 时间飙升。真正落地必须替换为 Radix Tree(前缀树),微软在ndisprot示例中有现成IPAddrToPrefix实现,可直接复用。


我当年在电力调度网闸项目里第一次把它跑通,是在一台 Windows 7 SP1 的工控机上,用串口线连着示波器看KeBugCheckEx是否触发——不是为了炫技,是因为现场不允许蓝屏。后来发现,最可靠的验证方式不是日志,而是拔掉网线再插上,看FilterRestart是否被调用;最省事的调试不是 WinDbg,而是把KdPrint替换成OutputDebugString,用 DbgView 实时捕获。这些细节不会写在任何文档里,但能让你少熬三天夜。

希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 8:06:04

SpringBoot+MyBatis健身俱乐部管理系统设计与实现

每天来馆里锻炼的会员要刷卡签到&#xff0c;私教课要排课、要预约&#xff0c;课时包什么时候用完、会员卡什么时候到期&#xff0c;还有一堆报表要统计&#xff0c;光靠前台小姐姐拿Excel表去记&#xff0c;早晚要出乱子。我接手的这个“基于Spring Boot的健身俱乐部管理系统…

作者头像 李华
网站建设 2026/10/9 8:05:50

多源Transformer整合非结构化数据的小微企业信贷评分模型实战

简介&#xff1a;这份PDF文档聚焦信贷风险评估领域&#xff0c;面向金融科技研究者、风控建模工程师及高校相关专业师生&#xff0c;系统讲解如何借助多源Transformer整合非结构化数据&#xff0c;构建小微企业评分模型。文档共42页&#xff0c;以单一PDF形式打包&#xff0c;大…

作者头像 李华
网站建设 2026/10/9 8:05:29

清华DeepSeek开源大模型本地部署与微调实战指南

简介&#xff1a;这份PDF资料聚焦清华大学团队对DeepSeek通用人工智能开源项目的系统解读&#xff0c;面向对自然语言处理、机器学习与推理模型感兴趣的研发工程师和技术爱好者。内容围绕DeepSeek-R1开源推理模型展开&#xff0c;涵盖智能对话、文本生成、语义理解、代码生成补…

作者头像 李华
网站建设 2026/10/9 8:05:28

思科校园网综合实验:VLAN划分、DHCP配置与RIPv2路由互通实践

简介&#xff1a;一份面向计算机网络课程实验与 Cisco 设备配置学习的综合性实验报告&#xff0c;适合高校网络工程、软件工程等专业学生及自学者参考。报告围绕校园网内外通信这一真实场景&#xff0c;完整记录了从拓扑设计、VLAN 划分、DHCP 配置、子网规划到 Ri/Rj 路由器接…

作者头像 李华
网站建设 2026/10/9 8:04:37

HCIP交换路由实战笔记:VLAN、STP与OSPF排障核心解析

简介&#xff1a;这是面向华为HCIP认证&#xff08;原HCNP R&S方向&#xff09;学习者打造的一份交换与路由技术笔记&#xff0c;内容围绕OSPF、BGP及华为设备操作展开&#xff0c;适合备考认证或希望系统梳理数通知识的网络工程师。笔记从OSI七层模型、TCP/IP协议栈、TCP与…

作者头像 李华
网站建设 2026/10/9 8:03:26

基于VIPER318的电机驱动双路隔离电源设计:15V与5V输出实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华