如何实现防火墙钩子驱动

Sample Image - FwHookDrv.jpg

介绍

也许,防火墙钩子驱动是在 Windows 系统下开发数据包过滤程序的方法中最没有文档可查的一个了。微软没有给出关于它的任何文档,你唯一能够找到点儿什么的地方是 DDK 头文件(ipFirewall.h)。事实上,当我安装了 Windows 2000 DDK,我对于发现这个 .h 文件(和它的内容)非常惊奇,因为没有文档提到防火墙钩子的存在。在下一个版本的 DDK 中,微软加了一些关于它的文档:“这个方法存在但不建议使用”。

然而,因为它是个实现防火墙的简单方法,我认为了解防火墙钩子驱动如何工作将是很有趣的。

防火墙钩子驱动

我不明白为什么微软不建议使用防火墙钩子驱动的开发。对于开发一个完整的防火墙方案我的确不建议用它,但对于小程序,它可是个好的选择。基本上,防火墙钩子驱动能够做过滤钩子驱动(见我的文章《在Windows 2000/XP下开发防火墙》 )所做的相同工作,但限制更少。

可能你还记得,过滤钩子函数仅允许在系统中安装一个过滤函数。如果有程序已经使用了这个功能,你的程序就没用了。使用防火墙钩子驱动,你不会遇到这个问题。你可以安装你需要的所有过滤函数。每个过滤函数被赋予一个优先级,于是系统会一个接一个的(以优先级顺序)调用过滤函数知道一个函数返回“DROP PACKET(丢弃包)”。如果所有的函数都返回“ALLOW PACKET(允许包)”,这个包就被允许通过了。你可以把它想象称一个过滤函数链。当其中一个返回了“DROP PACKET”链就被切断了。链中每个函数的顺序是由它的优先级值数给出的。

Chained function figure

在上图中,我表示了以下过程:

  1. 你的主机收到了一个包。IP 驱动拥有以优先级排列的过滤函数列表(具有更高优先级的函数是 Filter Function 1)。
  2. 首先,IP 驱动将包传递给优先级最高的过滤函数并等待返回值。
  3. Filter function 1 返回“ALLOW PACKET”。
  4. 因为 Filter function 1 允许了包,IP 驱动将包传递给了下一个过滤函数:Filter function 2。
  5. 在图示情况下,Filter function 2 返回了“DROP PACKET”。所以,IP 驱动丢弃了包而不在继续调用下一个过滤函数。

你会发现的过滤钩子驱动的另一个问题是对于包的发送,你不能访问包中的数据内容。但是,用防火墙钩子驱动你可以访问所有的数据。防火墙钩子过滤函数接收到的数据结构比过滤钩子驱动的更复杂。它更接近于 NDIS 驱动中包的结构,整个的包是由一个缓冲链组成的。但是请耐心点,你将在后面看到更多。

像过滤钩子驱动一样,防火墙钩子驱动只是个用来安装回调函数的内核模式驱动(但防火墙钩子驱动在 IP 驱动中安装回调函数)。实际上,安装防火墙钩子驱动的过程与安装过滤钩子驱动类似。在 ipFirewall.h 文件中,你会看到下面的内容:

typedef
 struct
 _IP_SET_FIREWALL_HOOK_INFO 
{
// Packet filter callout. 
IPPacketFirewallPtr FirewallPtr;

// Priority of the hook
UINT Priority;

       // if TRUE then ADD else DELETE 
       BOOLEAN Add;
} IP_SET_FIREWALL_HOOK_INFO, *PIP_SET_FIREWALL_HOOK_INFO;

#define DD_IP_DEVICE_NAME L//Device//Ip
#define _IP_CTL_CODE(function, method, access) /
CTL_CODE(FSCTL_IP_BASE, function, method, access)
#define IOCTL_IP_SET_FIREWALL_HOOK /
_IP_CTL_CODE(12 , METHOD_BUFFERED, FILE_WRITE_ACCESS)

这几行代码告诉你了安装回调函数的大致方法。你只需用你的回调函数的数据填写 IP_SET_FIREWALL_HOOK_INFO 结构并安装它发送 IOCTL IOCTL_IP_SET_FIREWALL_HOOK 到 IP 设备。简单,如果你与驱动程序打过交道或是曾做过那个有文档的过滤钩子函数。这个结构的一个重要参数是优先级域。每个优先级域包含了该过滤函数的优先级,其值越来越大。

PDEVICE_OBJECT ipDeviceObject=NULL; 
IP_SET_FIREWALL_HOOK_INFO filterData;

//.....

// Init structure filterData.
FirewallPtr = filterFunction;
filterData.Priority = 1 ;
filterData.Add = TRUE;

//....

// Send the commando to ip driver
IoCallDriver(ipDeviceObject, irp);
如果你要卸载过滤函数,可以用同样的代码,只要把 filterData.Add 的值改成 FALSE 就行了。

过滤函数

防火墙钩子驱动的过滤函数比过滤钩子驱动的更复杂。因为没有关于该函数及其参数的文档,所以复杂度增加了。该函数具有以下特征:

FORWARD_ACTION cbFilterFunction(VOID **pData, 
UINT RecvInterfaceIndex,
UINT *pSendInterfaceIndex,
UCHAR *pDestinationType,
VOID *pContext, 
UINT ContextLength,
struct IPRcvBuf **pRcvBuf);

用耐心和调试方法(还有参数名字的解释:)),我得出了以下关于参数的信息:

pData *pData 指向一个带有包缓冲的 (struct IPRcvBuf *) 结构。
RecvInterfaceIndex 数据接收的接口。
pSendInterfaceIndex 指向无符号整型的指针,包含数据发送的索引值。虽然它是个指针,改变它的值并不能让包改变路径:(。
pDestinationType 指向无符号整型的指针,指出目标类型:本地网络,远程,广播,多播,等。
pContext 指向 FIREWALL_CONTEXT_T 结构的指针,使你可以像流入和流出的包一样取得包的信息。
ContextLength 指向的缓冲区的大小。其值总是 sizeof(FIREWALL_CONTEXT_T)。
pRcvBuf *pRcvBuf 总是指向 NULL。

这些信息可能会在将来的 Windows 版本中改变,因为没有官方的文档可用。我只能保证这是我在 Windows 2000 和 Windows XP 下测试得到的这些域的意义。

对于每个包,我们的函数会被调用,并且取决于它的返回值,包会被丢弃或是放行。在过滤函数中你可以返回的值有:

FORWARD 包被允许。
DROP 包被丢弃。
ICMP_ON_DROP 包被丢弃并有一个 ICMP 包被发送到远程主机。

释放缓冲

在防火墙钩子过滤函数中,你并不是像在过滤钩子驱动中那样直接接收带有包头部和内容的缓冲区。在一些测试后,我弄清了缓冲的内部结构。如我前面所说,发送/接收 包是被传递给 pData 参数的。*pData 指向一个 IPRcvBuf 结构:

struct
 IPRcvBuf 
{
// Point to the next buffer in the chain
struct IPRcvBuf *ipr_next;

// Always 0 
UINT ipr_owner;

// Buffer data
UCHAR *ipr_buffer;

// Buffer data size
UINT ipr_size;

// In my tests always a pointer to NULL.
// Maybe the system could use MDLs instead of IPRcvBuf structures (but
// i never have seen it).
PMDL ipr_pMdl;

// Always a pointer to NULL.
UINT *ipr_pClientCnt;

// Always a pointer to NULL.
UCHAR *ipr_RcvContext;

// Always 0. I suppose this field is a offset into buffer data
// but because I haven't a value different from 0, I can affirm it.
UINT ipr_RcvOffset;

// In Windows 2003 DDK the name of this field have changed to flags.
// In my tests I always get 0 value for local traffic and 2 for remote.
// It's the only thing I can tell you about this field.
       ULONG ipr_promiscuous;
};

按照我们的意图,只需要知道域 ipr_next, ipr_buffer 和 ipr_size。ipr_buffer 域包含包的 ipr_size 字节。然而,整个包不必在一个缓冲里,系统能够链接多个缓冲。正因如此,ipr_next 域被使用了。这个域指向了下一个带有包数据的结构。当数据结构中的 ipr_next 指向 NULL 时我们就得到了整个的数据包。所以,我们在防火墙钩子驱动中发现了链接缓冲结构正如在 NDIS 驱动中所见。在我的测试中,对于所有接收的包,函数只接收到了一个结构,在其缓冲中带有全部的数据。对发送的包,我发现若干个链接的缓冲,每个包含一种协议的信息。我的意思是,例如我发送一个 ICMP 包,就会有三个链接的缓冲:一个带有 IP 头部,一个带有 ICMP 头部,而另一个带有数据。然而,就像我们对 NDIS 驱动做的一样,我们不能依赖于系统如何填写这些缓冲。

在下面的图中,你可以看到在防火墙钩子驱动中如何构建数据包的例子:

Chained buffers figure

简单的,你可以由下面的代码看到如何从链接缓冲中得到一个带有数据包内容的正统缓冲:

char
 *pPacket = NULL;
int iBufferSize;
struct IPRcvBuf *pBuffer = (struct IPRcvBuf *) *pData;

// First, I calculate the total size of the packet
iBufferSize = buffer->ipr_size;
while (pBuffer->ipr_next != NULL)
{
pBuffer = pBuffer->ipr_next;
iBufferSize += pBuffer->ipr_size;
}

// Reserve memory to the lineal buffer.
pPacket = (char *) ExAllocatePool(NonPagedPool, iBufferSize);
if (pPacket != NULL)
{
unsigned int iOffset = 0 ;
pBuffer = (struct IPRcvBuf *) *pData;

// we are going to copy each buffer of the chain in the lineal buffer.
memcpy(pPacket, pBuffer->ipr_buffer, pBuffer->ipr_size);
while (pBuffer->ipr_next != NULL)
{
iOffset += pBuffer->ipr_size;
pBuffer = pBbuffer->ipr_next;
memcpy(pPacket + iOffset, pBuffer->ipr_buffer,
pBbuffer->ipr_size);
}
}

还有,对于所有好奇的人(在你问到我之前:P),你可以修改包的数据,风险自己承担。没有任何工具来做这类的软件,要做类似的东西,我建议你实现一个 NDIS IM 驱动或者 TDI 过滤驱动。我没有做太多测试但我不很信赖防火墙钩子驱动改变数据包内容的稳定性。为什么?因为我们不知道 IP 驱动怎样管理这些缓冲和修改它们要冒多大的风险。一句话,我建议:只许看,不许摸。

结合的时候到了!

好了,现在我们知道了过滤函数的语法和传给它的包的格式。现在,我们要知道的就剩如何把这两样东西结合成一个数据包过滤程序了。我用的方法是试着定义一个类似于过滤钩子驱动中用到的过滤函数,因为这样更容易理解。由于传给过滤函数的参数在这两个驱动中是不一样的,对于防火墙钩子,我实现了一个中间函数(实际的防火墙钩子过滤函数)来包裹过滤函数。我的意思是,在防火墙钩子过滤函数中,我用了一个函数来处理包,将它拷贝到一个正统的缓冲中,然后把它传递给过滤函数。有下面的代码,我想你能更好的理解它:

FORWARD_ACTION cbFilterFunction(VOID **pData,
UINT RecvInterfaceIndex,
UINT *pSendInterfaceIndex,
UCHAR *pDestinationType,
VOID *pContext,
UINT ContextLength,
struct IPRcvBuf **pRcvBuf)
{
FORWARD_ACTION result = FORWARD;
char *pPacket = NULL;
int iBufferSize;
struct IPRcvBuf *pBbuffer =(struct IPRcvBuf *) *pData;
PFIREWALL_CONTEXT_T fwContext = (PFIREWALL_CONTEXT_T)pContext;

IPHeader *pIpHeader;

// Convert chained buffer to lineal buffer as we see before.
// This won't be the fastest code but
// will help us to understand better the method.

// ...........

pIpHeader = (IPHeader *)pPacket;

// Call the real filter function and return result     
result = FilterPacket(pPacket,
// length in bytes = ipp->headerLength * (32 bits/8)
pPacket + (pIpHeader ->headerLength * 4 ),
iBufferSize - (pIpHeader ->headerLength * 4 ),
(fwContext != NULL) ? fwContext->Direction: 0 ,
RecvInterfaceIndex,
(pSendInterfaceIndex != NULL) ? *pSendInterfaceIndex : 0 );

return result;
}

代码

你能很快认出这篇文章的程序。是的,图形界面和我在过滤钩子驱动中用的完全一样。为什么?因为我写了个简单的数据包过滤程序用来测试我写的所有防火墙方法。这样,我有了一个通用的图形界面给它们,来提供相同的功能,但在底层,它们工作的不一样。我有这个程序的不同版本(只做最小的改动)来测试我的过滤钩子驱动,防火墙钩子驱动,LSP DLL,TDI 过滤驱动,NDIS 驱动……所以,我想这个方法是好理解的。几乎不更改图形界面,这样你只想了解使用的新方法。

像我其它的文章一样,这个程序只实现了包的过滤。很多人问我关于加入一些更多的功能,像包记录,安装为服务……但我想要按照提供方法而不是答案的思想。如果你想要加入一些这样的功能,我很高兴:)。你可以联系我,问所有你想要的问题。

结论

好了,一旦我写完了这篇文章,它就是你的了。我希望你能从中学到与我一样多的东西。Enjoy it!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值