0%

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

各种方案简介

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

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

以上几种方案中,以 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

发给驱动要先进行数据处理,取值为区间的参数,拆分为为 BeginEnd 两个数据,并转换字符为整数,
为了方便进行策略比对,要先用 ntohlntohs 转换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;

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