0%

使用IPFilter实现数据包过滤(1)

各种方案简介

在XP系统下,实现网络过滤可以使用 TDI IPHOOK NDIS5 几种方案。
其中 TDI 过滤可以获取到IP数据包与进程之间的关系,而 IPHOOK 和 NDIS5 两种无法获知进程名。
在 NDIS5 中,因为是最底层,可以抓取到所有类型的数据包,前边两种只能抓取IP数据包。

在VISTA及以上的系统中,实现网络过滤可以使用 WFP NDIS6 几种方案,虽然 TDI 和 NDIS5 仍然
有效,但是微软已经不提倡再使用。其中 WFP 过滤对 TDI 进行了封装,可以获取到IP数据包与进程
之间的关系。NDIS6 对 NDIS5 进行了封装,可以抓取到所有类型的数据包。

以上几种方案中,以 NDIS 难度和复杂度最高,因为所有的细节操作,都需要自行处理,比如自行组装
数据包,自行进行转发数据。所以只要有一个处理不当,就会导致所有用 NDIS 的同类程序受到影响。

IPHOOK框架使用

这是最简单的框架,所有相关信息在 pfhook.h 头文件中定义,向 "\\Device\\IPFILTERDRIVER" 设备
发送 IOCTL_PF_SET_EXTENSION_POINTER 控制码来进行注册,其输入参数为处理过滤数据的回调函数

1
2
3
4
5
6
7
8
typedef PF_FORWARD_ACTION (*PacketFilterExtensionPtr)(
IN unsigned char *PacketHeader,
IN unsigned char *Packet,
IN unsigned int PacketLength,
IN unsigned int RecvInterfaceIndex,
IN unsigned int SendInterfaceIndex,
IN IPAddr RecvLinkNextHop,
IN IPAddr SendLinkNextHop);

以下为注册IPHOOK框架的代码,注意由于只能存在一个过滤函数,所以需要先设置过滤函数为 NULL 进行
清除操作,然后再注册我们的过滤函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
NTSTATUS SetFilterFunction(PacketFilterExtensionPtr FilterFun)
{
PIRP pIrp = NULL;
KEVENT Event = { 0 };
IO_STATUS_BLOCK IoStatus = { 0 };
NTSTATUS Status = STATUS_SUCCESS;
PFILE_OBJECT FileObject = NULL;
PDEVICE_OBJECT DeviceObject = NULL;
PF_SET_EXTENSION_HOOK_INFO HookInfo = { 0 };
UNICODE_STRING IpFilterDriver = RTL_CONSTANT_STRING(L"\\Device\\IPFILTERDRIVER");
// 获取IP过滤设备对象
Status = IoGetDeviceObjectPointer(
&IpFilterDriver, FILE_ALL_ACCESS, &FileObject, &DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
// 创建IRP请求
HookInfo.ExtensionPointer = FilterFun;
KeInitializeEvent(&Event, NotificationEvent, FALSE);
pIrp = IoBuildDeviceIoControlRequest(
IOCTL_PF_SET_EXTENSION_POINTER, DeviceObject,
(PVOID)&HookInfo, sizeof(PF_SET_EXTENSION_HOOK_INFO),
NULL, 0, FALSE, &Event, &IoStatus);
if (pIrp == NULL)
{
ObDereferenceObject(FileObject);
return STATUS_INSUFFICIENT_RESOURCES;
}
// 发送此IRP到IP过滤驱动
Status = IoCallDriver(DeviceObject, pIrp);
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
}
ObDereferenceObject(FileObject);
return IoStatus.Status;
}

以下为处理过滤数据回调函数的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PF_FORWARD_ACTION FilterPackets(
unsigned char *PacketHeader,
unsigned char *Packet,
unsigned int PacketLength,
unsigned int RecvInterfaceIndex,
unsigned int SendInterfaceIndex,
IPAddr RecvLinkNextHop,
IPAddr SendLinkNextHop)
{
// 相关数据结构体指针
PIP_HEADER pIpHeader = (PIP_HEADER)PacketHeader;
PTCP_HEADER pTcpHeader = (PTCP_HEADER)Packet;
PUDP_HEADER pUdpHeader = (PUDP_HEADER)Packet;
PKGFLT_STATUS fStatus = STATUS_PASS;
UNREFERENCED_PARAMETER(PacketLength);
UNREFERENCED_PARAMETER(RecvLinkNextHop);
UNREFERENCED_PARAMETER(SendLinkNextHop);
if (PacketHeader == NULL) return PF_FORWARD;
// 接收无效的时候表示发包
(RecvInterfaceIndex == INVALID_PF_IF_INDEX);
// 发送无效的时候表示收包
(SendInterfaceIndex == INVALID_PF_IF_INDEX);
// 检测是否拦截数据包
if (fStatus == STATUS_DROP) return PF_DROP;
return PF_FORWARD;
}

因为全都是已经组装好的数据,不需要额外的再做其他处理,所以稳定性极高。

防火墙策略的设计

除了数据包信息的处理,我们还需要配置用以拦截数据包的策略。
防火墙策略包含 规则 协议 源IP 源端口 目的IP 目的端口 进程名 几项基本配置,
更复杂的策略可以进行深层协议识别,比如 DNS HTTP SSL 等,甚至对 数据关键字 进行过滤。

以 IPHOOK 为基础,我们设计的策略如下

名称 说明 示例
Rule 规则 0(放行)1(拦截)
Protocol 协议 0(ANY)1(ICMP)6(TCP)17(UDP)
SourceIP 源IP段 192.168.3.100-192.168.3.120
SourcePort 源端口段 30500-30540
DestIP 目的IP段 192.168.1.55-192.168.1.90
DestPort 目的端口段 5000-5080

发给驱动要先进行数据处理,取值为区间的参数,拆分为为 Begin 和 End 两个数据,并转换字符为整数,
为了方便进行策略比对,要先用 ntohl 和 ntohs 转换IP和端口的数据为 host 类型,结构体定义如下

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _POLICY_INFO {
UCHAR Rule; // 规则 0:允许 1:阻止
UCHAR Protocol; // 协议 0:任何 1:ICMP 6:TCP 17:UDP
UINT32 SrcIPBegin; // 源起始IP
UINT32 SrcIPEnd; // 源终止IP
USHORT SrcPortBegin; // 源起始Port
USHORT SrcPortEnd; // 源终止Port
UINT32 DstIPBegin; // 目的起始IP
UINT32 DstIPEnd; // 目的终止IP
USHORT DstPortBegin; // 目的起始Port
USHORT DstPortEnd; // 目的终止Port
} POLICY_INFO, *PPOLICY_INFO;

多个策略之间的协同处理,采用自上而下的方式,依照发送到驱动的先后顺序进行匹配,始终以靠后的策略
的匹配结果为结果,比如同时满足两条策略,前边的结果是拦截,后边的结果是放行,最终结果是放行。