防火墙的数据包拦截方式小结由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“拦截器小结”。
防火墙的数据包拦截方式小结
网络防火墙都是基于数据包的拦截技术之上的。在 Windows 下,数据包的拦截方式有很多种,其原理和实现方式也千差万别。总的来说,可分为“用户级”和“内核级”数据包拦截两大类。
用户级下的数据包拦截方式有:
* Winsock Layered Service Provider(LSP)。
* Win2K 包过滤接口(Win2K Packet Filtering Interface)。* 替换 Winsock 动态链接库(Winsock Replacement DLL)。
内核级下的数据包拦截方式有:
* TDI过滤驱动程序(TDI-Filter Driver)。
* NDIS中间层驱动程序(NDIS Intermediate Driver)。* Win2K Filter-Hook Driver。* Win2K Firewall-Hook Driver。* NDIS-Hook Driver。
在这么多种方式面前,我们该如何决定采用哪一种作为自己项目的实现技术?这需要对每一种 方式都有一个大致的了解,并清楚它们各自的优缺点。技术方案的盲目选用往往会带来一些技术 风险。以自己为例,我需要在截包的同时得到当前进程文件名,也就是说,需向用户报告当前是 哪个应用程序要访问网络。在选用 Win2K Filter-Hook Driver 这一方案之后(很多小型开源项
目都采用这一方案),便开始编码。但之后发现 Win2K Filter-Hook Driver 的截包上下文处于内
核进程中,即 IRQL >= DISPATCH_LEVEL,根本无法知道当前应用程序的名字。相比之下,TDI-Filter Driver 和 NDIS-Hook Driver 则可以得知这些信息。其中 TDI-Filter Driver 比 NDIS-Hook Driver 更能准确地获知当前应用程序文件名,后者的接收数据包和少数发送数据
包的场景仍然处于内核进程中。
下面列出了各种截包方式的特点:
* Winsock Layered Service Provider(LSP)该方式也称为 SPI(Service Provider Interface)截包技术。SPI是由 Winsock2 提供的一个
接口,它需要用户机上安装有 Winsock 2.0。Winsock2 SPI 工作在 API 之下的 Driver 之上,可以截获所有基于 Socket 的网络数据包。
优点:
* 以DLL形式存在,编程方便,调试简单。* 数据封包比较完整,未做切片,便于做内容过滤。
缺点: * 拦截不够严密,对于不用 Socket 的网络通讯则无法拦截(如 ICMP),木马病毒很容易绕过。
* Win2K Packet Filtering Interface
这是 Win2K 中一组 API 提供的功能(PfCreateInterface, PfAddFiltersToInterface,...)。
优点:
* 接口简单,实现起来没什么难度。
缺点:
* 功能过于简单,只能提供IP和端口的过滤,可能无法满足防火墙的复杂需求。* 处于 API 层,木马病毒容易绕过。* 只能在 Win2K 以上(含)系统中使用。
* Winsock Replacement DLL 这种方法通过替换系统 Winsock 库的部分导出函数,实现数据报的监听和拦截。
缺点:
* 由于工作在 Winsock 层,所以木马病毒容易绕过。
* TDI-Filter Driver
TDI 的全称是 Transport Driver Interface。传输层过滤驱动程序通过创建一个或多个设备对象
直接挂接到一个现有的驱动程序之上。当有应用程序或其它驱动程序调用这个设备对象时,会首
先映射到过滤驱动程序上,然后由过滤驱动程序再传递给原来的设备对象。
优点:
* 能获取到当前进程的详细信息,这对开发防火墙尤其有用。
缺点:
* 该驱动位于 tcpip.sys 之上,所以没有机会得到那些由 tcpip.sys 直接处理的包,比如ICMP。
* TDI驱动需要重启系统方能生效。
* NDIS Intermediate Driver
也称之为 IM Driver。它位于协议层驱动和小端口驱动之间,它主要是在网络层和链路层之间对
所有的数据包进行检查,因而具有强大的过滤功能。它能截获所有的数据包。
可参考DDK中附带的例子 Pathru。
优点:
* 功能非常强大,应用面广泛,不仅仅是防火墙,还可以用来实现VPN,NAT 和 VLan 等。
缺点:
* 编程复杂,难度较大。
* 中间层驱动的概念是在 WinNT SP4 之后才有的,因此 Win9X 无法使用。* 不容易安装,自动化安装太困难。
* Win2K Filter-Hook Driver 这是从 Win2K 开始提供的一种机制,该机制主要利用 ipfiltdrv.sys 所提供的功能来拦截网络
数据包。Filter-Hook Driver 的结构非常简单,易于实现。但是正因为其结构过于简单,并且
依赖于 ipfiltdrv.sys,微软并不推荐使用。
可参考 CodeProject 上的例子:http://www.daodoc.com/KB/IP/drvfltip.aspx
优点:
* 结构简单,易于实现。
* 能截获所有的IP包(包括ICMP包)。
缺点:
* 工作于内核进程中,无法取得当前应用程序进程的信息。
* 虽能截获所有IP包,但无法取得数据包的以太帧(Ethernet Frame)。* 只能在 Win2K 以上(含)系统中使用。
* Win2K Firewall-Hook Driver 这是一种和 Win2k Filter-Hook Driver 差不多的机制,所不同的是,Firewall-Hook Driver 能在 IP Driver 上挂接多个回调函数,所以和前者相比,它引起冲突的可能性更小一些。
可参考 CodeProject 上的例子:http://www.daodoc.com/KB/IP/FwHookDrv.aspx
这种方式的优缺点和 Win2K Filter-Hook Driver 基本相同。
* NDIS-Hook Driver
这是一种要重点讲述的截包方式。它是目前大多数网络防火墙所使用的方法。这种方式的做法
是安装钩子到 ndis.sys 中,替换其中的某些关键函数,从而达到截包的目的。在下一节中我们将详细地介绍它的实现方法。
优点:
* 安装简单,可即时安装和卸载驱动,无需重启系统。
* 能截获所有的IP包,同时能取得数据包的以太帧(Ethernet Frame)。* 安全性高,木马病毒不容易穿透。
* 在大多数情况下,能获取到当前应用程序的进程信息。* 能在 Win98 以上(含)系统中使用。
缺点:
* 接收数据包、或偶尔发送数据包时,驱动工作在内核进程中,无法获得应用程序进程信息。
◎ NDIS-Hook 技术
微软和 3COM 公司在1989年制定了一套开发 Windows 下网络驱动程序的标准,称为 NDIS。
NDIS 的全称是 Network Driver Interface Specification。NDIS为网络驱动的开发提供了一套
标准的接口,使得网络驱动程序的跨平台性更好。
NDIS提供以下几个层次的接口:
1.NDIS 小端口驱动(NDIS Miniport Driver)。这也就是我们常说的网卡驱动。
2.NDIS 协议驱动(NDIS Protocol Driver)。用来实现某个具体的协议栈,如 TCP/IP 协议栈,并向上层导出 TDI 接口。3.NDIS 中间层驱动(NDIS Intermediate Driver)。
这是位于小端口驱动和协议驱动之间的驱动。
NDIS为了给出上述三种接口,提供了一个系统的、完整的 Wrapper。这个 Wrapper 即 ndis.sys。
上面提到的 Miniport Driver、Protocol Driver、Intermediate Driver 均属于插入到这个 Wrapper 中的“模块”,它们调用 Wrapper 提供的函数,同时也向 Wrapper 注册回调函数。
在简单了解了NDIS的机制之后,不难得知,网络防火墙只需要将自己的函数挂钩(Hook)到 ndis.sys
中即可截获网络数据包。NDIS-Hook 技术有两种实现方案:
1.修改 ndis.sys 的 Export Table。
在 Win32 下,可执行文件(EXE/DLL/SYS)都遵从PE格式。所有提供接口的驱动都有 Export Table,因此只要修改 ndis.sys 的 Export Table,就可实现对关键函数的挂接。在实现步骤中,首先
需要得到 ndis.sys 的内存基址,再根据PE格式得到DOS头部结构(IMAGE_DOS_HEADER),进一步得
到NT头部结构(IMAGE_NT_HEADER),最后从头部结构中查得 Export Table 的地址。
由于协议驱动程序(NDIS Protocol Driver)在系统启动时会调用 NdisRegisterProtocol()来向
系统注册协议,因此这种方法关键在于修改 ndis.sys 所提供的 NdisRegisterProtocol、NdisDeRegisterProtocol、NdisOpenAdapter、NdisCloseAdapter、NdisSend 这几个函数的地址。
对于处于系统核心的 ndis.sys 而言,要修改它的内存区域,只有驱动程序才能做到,所以我们
必须编写驱动程序来达到这个目的。
该方案的缺点是加载或卸载驱动后无法立即生效,必须重启系统。且挂钩方法较为复杂。早期凡
使用 NDIS-Hook 的防火墙都采用这一方法,包括著名的费尔防火墙的早期版本(v2.1)。
直到 2004 年,www.daodoc.com 上一名黑客公布了一种全新的 NDIS-Hook 技术(即下文即将提
到的第2种方法),诸多防火墙产品才都悄悄对自己的核心技术进行了升级。由于新的挂钩技术更
好,故本文不打算详述修改 Export Table 这一方法的具体细节。
2.向系统注册假协议(Bogus Protocol)。NDIS提供了一个API: NdisRegisterProtocol(),这个API的职责是向系统注册一个协议(如TCPIP),并将该协议作为一个链表节点插入到“协议链表”的头部,最后返回该链表头节点(即新节点)的地址。正常情况下,只有NDIS协议驱动程序(NDIS Protocol Driver)才会调用这个API。
既然如此,如果我们也调用 NdisRegisterProtocol()向系统注册一个新的协议,我们也就能轻
易地得到“协议链表”的首地址,通过走访这个链表,就能修改其中的某些关键信息,比如关键
函数的地址。修改完毕后,再调用 NdisDeRegisterProtocol()注销掉新协议。这看似一切都没
发生,但事实上目的已经达到了。这个新协议我们称之为假协议(Bogus Protocol)。
通过这种方法,我们可以不用重启系统就能轻松挂接截包函数。当今大多数网络防火墙都采用了
这一方法。近来网上又有人提出了获取协议链表首地址的新的怪异途径,比如获取 tcpip.sys 中全局变量 _ARPHandle 值的方法。不管怎样,相比之下,注册假协议仍不失为一种经典且简单的方法。
本文将详细叙述第2种方案的内部原理和实现细节,即通过注册假协议获取协议链表首地址,遍历
链表并修改其中的函数地址,挂钩自己的函数,从而实现网络截包。在这么做之前,需要先对NDIS
内部维护的几个结构有清楚的认识。另外,由于历史原因,NDIS存在诸多并不完全向下兼容的版本,不同的版本中关键数据域的偏移地址也不尽相同。微软并没有以文档形式提供这些变化的列表。本
文稍后给出这些变化。
* NDIS_PROTOCOL_BLOCK 和 NDIS_OPEN_BLOCK
在NDIS中,所有已注册的协议是通过一个单向的协议链表来维护的。这个单向链表保存了所有已注册的协议,每个协议对应一个节点。链表节点由 NDIS_PROTOCOL_BLOCK 结构来描述,在这个结构中保存
了注册协议驱动时所指定的各种信息,如支持协议即插即用的回调函数地址等。同时,每个协议驱动
还对应一个 NDIS_OPEN_BLOCK 节点结构的单向链表来维护其所绑定的网卡信息,协议驱动发送和接收
数据包的回调函数地址就保存在这个结构中,是我们要重点修改的对象。
协议与网卡绑定的示意图如下: ┌───┐
│ Head │
└─┬─┘
↓
┌──────────────┐ ┌───────────┐
│ TCPIP Protocol Block ├──→│ RTL8168 Open Block │
└──────┬───────┘ └─────┬─────┘
↓ ↓
┌──────────────┐ ┌───────────┐
│ TCPIP_WANARP Protocol Block│ │ Wirele Open Block │
└──────┬───────┘ └─────┬─────┘
↓ ↓ ┌───┐ ┌───┐
│ NULL │ │ NULL │
└───┘ └───┘ * 得到 NDIS_PROTOCOL_BLOCK 链表的首地址
上文已提到,通过向系统注册假协议,我们即可得到协议链表的首地址。从DDK中可查到 NdisRegisterProtocol()的原型:
EXPORT VOID NdisRegisterProtocol(OUT PNDIS_STATUS Status, OUT PNDIS_HANDLE NdisProtocolHandle, IN PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics, IN UINT CharacteristicsLength);
可以看出,我们在调用它时需要传入一个结构 NDIS_PROTOCOL_CHARACTERISTICS,这个结构是我们
在注册协议时必须填写的一张表格,这个表格描述了协议的相关信息。不过既然我们注册的是一个
假协议,所以可以尽量简单地填写它。
NDIS_STATUS
DummyNdisProtocolReceive(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_HANDLE MacReceiveContext, IN PVOID HeaderBuffer, IN UINT HeaderBufferSize, IN PVOID LookAheadBuffer, IN UINT LookAheadBufferSize, IN UINT PacketSize){ return NDIS_STATUS_NOT_ACCEPTED;}
NDIS_HANDLE
RegisterBogusNdisProtocol(void){ NTSTATUS Status = STATUS_SUCCESS;NDIS_HANDLE hBogusProtocol = NULL;NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol;NDIS_STRING ProtocolName;NdisZeroMemory(&BogusProtocol, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));BogusProtocol.MajorNdisVersion = 0x04;BogusProtocol.MinorNdisVersion = 0x0;
NdisInitUnicodeString(&ProtocolName, L“BogusProtocol”);BogusProtocol.Name = ProtocolName;BogusProtocol.ReceiveHandler = DummyNdisProtocolReceive;NdisRegisterProtocol(&Status, &hBogusProtocol, &BogusProtocol, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if(Status == STATUS_SUCCESS)return hBogusProtocol;else return NULL;}
函数 RegisterBogusNDISProtocol()的返回值即是我们想要的协议链表首地址。不过须注意的是,在函数挂钩完成后,应调用 NdisDeregisterProtocol()将假协议注销。另外,在遍历协议链表
进行函数挂钩时,应从首节点的下一个节点开始,因为首节点是我们的假协议节点。
* 修改原有函数地址值实现函数挂钩
上文已提到了和NDIS相关的三个结构: NDIS_PROTOCOL_BLOCK, NDIS_OPEN_BLOCK, NDIS_PROTOCOL_CHARACTERISTICS。
那么我们要替换的函数在哪儿呢?答案是在 NDIS_OPEN_BLOCK 和 NDIS_PROTOCOL_CHARACTERISTICS
这两个结构中,而且重点是前者,因为前者是协议驱动和网卡绑定的纽带。现在的主流网卡都只 调用 NDIS_OPEN_BLOCK 中的收发函数进行发送和接收数据包。但据试验,虚拟机 VMware 有时会
调用 NDIS_PROTOCOL_CHARACTERISTICS 中的函数进行数据包收发。所以为了严谨,我们应该对两
个结构中的函数进行替换。关于这两个结构的定义,读者可以自行查阅DDK文档和头文件。
下面给出示意性代码。简单起见,下列代码均假设当前NDIS的版本为5.0。
BOOLEAN InstallHook(void){ NDIS_STATUS nStatus;NDIS_HANDLE hBogusProtocol = NULL;BYTE *pProtocolChain;
// Get the addre of the first NDIS_PROTOCOL_BLOCK node.hBogusProtocol = RegisterBogusNDISProtocol();if(hBogusProtocol == NULL)return FALSE;pProtocolChain =(BYTE*)hBogusProtocol;while(TRUE){ // Get the addre of the next node.DWORD dwOffset = 0x10;// for NDIS 5.0 pProtocolChain =((BYTE **)(pProtocolChain + dwOffset))[0];if(!pProtocolChain)break;
HookNdisProtocolBlock(pProtocolChain);}
NdisDeregisterProtocol(&nStatus, hBogusProtocol);return TRUE;} void
HookNdisProtocolBlock(IN BYTE *pProtocolBlock){ PNDIS_PROTOCOL_CHARACTERISTICS pProtoChar;PNDIS_OPEN_BLOCK pOpenBlock;
pProtoChar =(PNDIS_PROTOCOL_CHARACTERISTICS)(pProtocolBlock + 0x14);HookNdisProc(MyReceive,(PVOID *)&pProtoChar->ReceiveHandler);HookNdisProc(MyReceivePacket,(PVOID *)&pProtoChar->ReceivePacketHandler);HookNdisProc(MyBindAdapter,(PVOID *)&pProtoChar->BindAdapterHandler);
pOpenBlock =((PNDIS_OPEN_BLOCK *)pProtocolBlock)[0];while(pOpenBlock){ HookNdisProc(MySend,(PVOID *)&pOpenBlock->SendHandler);HookNdisProc(MyReceive,(PVOID *)&pOpenBlock->ReceiveHandler);HookNdisProc(MyReceivePacket,(PVOID *)&pOpenBlock->ReceivePacketHandler);HookNdisProc(MySendPackets,(PVOID *)&pOpenBlock->SendPacketsHandler);
pOpenBlock = pOpenBlock->ProtocolNextOpen;} } void HookNdisProc(IN PVOID pMyProc, IN PVOID *ppOrgProc){ // TODO: Save the addre of the original proc.*ppOrgProc = pMyProc;}
InstallHook()首先得到协议链表的首地址,接着遍历链表,针对系统中的每个(第一个除外)NDIS_PROTOCOL_BLOCK 调用 HookNdisProtocolBlock()函数。HookNdisProtocolBlock()对 NDIS_PROTOCOL_BLOCK 中 NDIS_PROTOCOL_CHARACTERISTICS 和
NDIS_OPEN_BLOCK 链表的每个节点进行函数挂接。
HookNdisProc()用于替换函数地址。给出的代码中它只是简单地替换函数地址,在实际应用中,它还应当保存原始函数的地址值,以供新的函数调用。
* 关键数据域在不同NDIS版本中的差异
由于 NDIS-Hook 并非受微软官方支持的技术,所以相关文档非常缺乏。不仅如此,操作系统的 每次升级,都会同时升级NDIS,而NDIS中的某些数据结构并没有保持向下兼容。最需要注意的是 NDIS_PROTOCOL_BLOCK。
在 Win9x/Me/NT 的DDK中,NDIS_PROTOCOL_BLOCK 都有明确的定义,但在 Win2K/XP 的DDK中,并没有该结构的详细定义,也就是说该结构在 Win2K 以后(含)的系统中是非公开的。因此开发
人员只能利用各种调试工具来发掘该结构的详细定义。也正是因为如此,NDIS-Hook 方法对平台的依赖性比较大,需要在程序中判断不同的操作系统版本而使用不同的结构定义。
NDIS_PROTOCOL_BLOCK 的定义可大致认为是这个样子:
typedef struct _NDIS_PROTOCOL_BLOCK { PNDIS_OPEN_BLOCK OpenQueue;REFERENCE Ref;UINT Length;NDIS_PROTOCOL_CHARACTERISTICS ProtocolChars;struct _NDIS_PROTOCOL_BLOCK* NextProtocol;ULONG MaxPatternSize;//...} NDIS_PROTOCOL_BLOCK, *PNDIS_PROTOCOL_BLOCK;
其中 OpenQueue 为 PNDIS_OPEN_BLOCK 链表的首节点地址,NextProtocol 指向下一个
NDIS_PROTOCOL_BLOCK 节点。
在不同的NDIS版本中,该结构中的某些域的偏移地址是不同的,现列于下:
┌───────┬───────────┬───────────┐
│ NDIS Version │ ProtocolChars offset │ NextProtocol offset │
├───────┼───────────┼───────────┤
│ 3.XX │ 0x14 │ 0x04 │
│ 4.XX │ 0x14 │ 0x60 │
│ 4.01 │ 0x14 │ 0x8C │
│ 5.XX │ 0x14 │ 0x10 │
└───────┴───────────┴───────────┘ * 如何在驱动中得到当前NDIS版本? 有两种方法可得到当前NDIS版本。一种是先取得当前操作系统的版本信息,在根据操作系统 的版本得到NDIS的版本。操作系统版本和NDIS版本有一个映射关系,读者可在DDK帮助中查到。
┌───────┬───────┐
│ OS Version │ NDIS Version │
├───────┼───────┤
│ Win95 │ 3.1 │
│ Win95 OSR2 │ 4.0 │
│ Win98 │ 4.1 │
│ Win98 SE │ 5.0 │
│ WinMe │ 5.0 │
│ WinNT 3.5 │ 3.0 │
│ WinNT 4.0 │ 4.0 │
│ WinNT 4.0 SP3│ 4.1 │
│ Win2K │ 5.0 │
│ WinXP │ 5.1 │
│ WinVista │ 6.0 │
└───────┴───────┘
还有一种方法,通过调用 NdisReadConfiguration()直接获取NDIS版本。代码如下:
BOOLEAN GetNdisVersion(OUT DWORD *pMajorVersion, OUT DWORD *pMinorVersion){ NDIS_STATUS nStatus;NDIS_STRING VersionStr = NDIS_STRING_CONST(“NdisVersion”);PNDIS_CONFIGURATION_PARAMETER ReturnedValue;BOOLEAN bResult;NdisReadConfiguration(&nStatus, &ReturnedValue, NULL, &VersionStr, NdisParameterInteger);
bResult =((nStatus == NDIS_STATUS_SUCCESS)? TRUE : FALSE);if(bResult){ //
// The returned value has the NDIS version of the form // 0xMMMMmmmm, where MMMM is major version and mmmm is minor // version so 0x00050000 is 5.0 //
DWORD dwVersion = ReturnedValue->ParameterData.IntegerData;if(pMajorVersion)*pMajorVersion = dwVersion >> 16;if(pMinorVersion)*pMinorVersion = dwVersion & 0xFFFF;}
return bResult;}
须注意的是,GetNdisVersion()必须在 PASSIVE_LEVEL 下运行。所以此函数适合于在 驱动的 DriverEntry()中调用,因为 DriverEntry()一定是处于 PASSIVE_LEVEL 的。
基于数据包捕获与分析的个人防火墙论文本套设计论文描述及运行界面展示摘 要数据包过滤是一个用软件或硬件设备对向网络上传或从网络下载的数据流进行有选择的控制过程。数......
数据包须知1.价格问题:数据包里的价格为销售价。数据包里excel形式的表格为成本单。注意:鞋子重量稍重 有些可能超重需要补价邮费请注意!2.缺断货信息问题:请以软件更新为准或咨......
防火墙技术: 包过滤:电路级网关、应用网关、代理服务器、状态检测、自适应代理型防火墙 电路级网关用来监控受信任的客户或服务器与不受信任的主机间的TCP握手信息,这样来决定......
“用领导方式转变加快发展方式转变”解放思想大讨论活动第一阶段小结根据市委的统一部署和局党委的计划安排,我局认真组织开展了“用领导方式转变加快发展方式转变”解放思想......
计算机网络实验(实习)报告Ⅰ.实验(实习)名称 :数据包捕获与协议分析实验(实习)日期专业姓名:学号:(或使用青岛农业大学实验报告纸)1、实验目的(1)掌握网络协议分析工具Ethereal的使用方法......