概述:一个完整的 WFP 驱动程序

相关文章推荐:

WFP 驱动

WFP,全称 Windows Filtering Platform 驱动框架。WFP 是在 WIN7 以上系统中 TDI 的替代框架,提供了更加强大的内核网络数据包的过滤,拦截,修改等诸多功能。

微软帮助手册开发 WFP 调用驱动程序的路线图 - Windows drivers | Microsoft Learn

代码

#include <ntddk.h>
#pragma warning(push)
#pragma warning(disable: 4201)       // unnamed struct/union
#pragma warning(disable: 4995)
#include <fwpsk.h>
#pragma warning(pop)
#include <ndis.h>
#include <fwpmk.h>
#include <limits.h>
#include <ws2ipdef.h>
#include <in6addr.h>
#include <ip2string.h>
#include <strsafe.h>
#define INITGUID
#include <guiddef.h>
#define bool BOOLEAN
#define true TRUE 
#define false FALSE
#define DEVICE_NAME L"\\Device\\WFP_TEST"
#define DEVICE_DOSNAME L"\\DosDevices\\WFP_TEST"
#define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
#define kfree(_p) ExFreePool(_p)
 
DEFINE_GUID // {6812FC83-7D3E-499a-A012-55E0D85F348B}
(
	GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
	0x6812fc83,
	0x7d3e,
	0x499a,
	0xa0, 0x12, 0x55, 0xe0, 0xd8, 0x5f, 0x34, 0x8b
);
 
PDEVICE_OBJECT  gDevObj;
 
HANDLE	gEngineHandle = 0;
HANDLE	gInjectHandle = 0;
//CalloutId
UINT32	gAleConnectCalloutId = 0;
//FilterId
UINT64	gAleConnectFilterId = 0;
 
/*
以下两个回调函数没啥用
*/
NTSTATUS NTAPI WallNotifyFn
(
	IN FWPS_CALLOUT_NOTIFY_TYPE  notifyType,
	IN const GUID* filterKey,
	IN const FWPS_FILTER* filter
)
{
	KdPrint(("NotifyFn\n"));
	return STATUS_SUCCESS;
}
 
VOID NTAPI WallFlowDeleteFn
(
	IN UINT16  layerId,
	IN UINT32  calloutId,
	IN UINT64  flowContext
)
{
	KdPrint(("FlowDeleteFn\n"));
	return;
}
 
//协议代码转为名称
char* ProtocolIdToName(UINT16 id)
{
	char* ProtocolName = kmalloc(16);
	RtlZeroMemory(ProtocolName, 16);
	switch (id)
	{
	case 1:
		strcpy_s(ProtocolName, 4 + 1, "ICMP");
		break;
	case 2:
		strcpy_s(ProtocolName, 4 + 1, "IGMP");
		break;
	case 6:
		strcpy_s(ProtocolName, 3 + 1, "TCP");
		break;
	case 17:
		strcpy_s(ProtocolName, 3 + 1, "UDP");
		break;
	case 27:
		strcpy_s(ProtocolName, 3 + 1, "RDP");
		break;
	default:
		strcpy_s(ProtocolName, 7 + 1, "UNKNOWN");
		break;
	}
	return ProtocolName;
}
 
//最重要的过滤函数
void NTAPI WallALEConnectClassify
(
	IN const FWPS_INCOMING_VALUES0* inFixedValues,//此结构包含筛选层上每个数据字段的值  
	//incomingValue Index] => Index 的值是枚举类型 FWPS_FIELDS_ALE_AUTH_CONNECT_V4,
	//枚举名字说明了该值的类型例如: FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS 就是本地 IP	
 
	IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,//此结构包含筛选层上每个元数据字段的值。
	IN OUT void* layerData,//指向结构的指针,该结构描述正在过滤的层上的原始数据。此参数可能为 NULL,
	//这取决于所筛选的层和调用 classfyFn0 标注函数的条件。
	//对于流层,此参数指向 FWPS_stream_Callout_IO_PACKET 0 结构。
	//对于所有其他层,如果 NetBufferList 结构不是 NULL,则该参数指向该结构。
	IN const void* classifyContext,//由过滤器引擎指向与标注驱动程序关联的上下文数据的指针 没有找出该指针类型
	IN const FWPS_FILTER* filter,//指向 FWPS_FILTER1 结构的指针。此结构描述指定筛选器操作的标注的筛选器。
	IN UINT64 flowContext,//包含与数据流关联的上下文的 UINT 64 类型变量。如果没有与数据流相关联的上下文,则此参数为零。
	//如果在不支持数据流的过滤层将标注添加到筛选引擎中,则 clamfyFn1 回调函数应忽略此参数
	OUT FWPS_CLASSIFY_OUT* classifyOut //指向 FWPS_GARGY_OUT 0 结构的指针,该结构接收 clamfyFn1 回调函数返回给调用方的任何数据
)
{
 
	char* ProtocolName = NULL;
	DWORD LocalIp, RemoteIP;
	PWCHAR Path = NULL;
 
 
	do
	{
		Path = ExAllocatePoolWithTag(NonPagedPool, inMetaValues->processPath->size + 2, 'NET');
		if (NULL == Path)
		{
			break;
		}
 
		RtlZeroMemory(Path, inMetaValues->processPath->size + 2);
		wcscpy_s(Path, inMetaValues->processPath->size / 2, (WCHAR*)inMetaValues->processPath->data);
 
		LocalIp = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_ADDRESS].value.uint32;
		RemoteIP = inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_ADDRESS].value.uint32;
		ProtocolName = ProtocolIdToName(inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_PROTOCOL].value.uint16);
 
 
		DbgPrint("进程 ID : %d", (DWORD)(inMetaValues->processId));
		DbgPrint("Path =%S", Path);
		DbgPrint("本地地址 =%u.%u.%u.%u ", (LocalIp >> 24) & 0xFF, (LocalIp >> 16) & 0xFF, (LocalIp >> 8) & 0xFF, LocalIp & 0xFF);
		DbgPrint("端口%d\n", inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_LOCAL_PORT].value.uint16);
 
		DbgPrint("远程地址 =%u.%u.%u.%u ", (RemoteIP >> 24) & 0xFF, (RemoteIP >> 16) & 0xFF, (RemoteIP >> 8) & 0xFF, RemoteIP & 0xFF);
		DbgPrint("端口%d\n", inFixedValues->incomingValue[FWPS_FIELD_ALE_AUTH_CONNECT_V4_IP_REMOTE_PORT].value.uint16);
		DbgPrint("协议名: %s\n", ProtocolName);
 
 
		kfree(ProtocolName);
	} while (0);
 
 
	if (NULL != Path)
	{
		ExFreePoolWithTag(Path, 'NET');
	}
	classifyOut->actionType = FWP_ACTION_PERMIT;//允许连接
 
	return;
}
 
NTSTATUS RegisterCalloutForLayer
(
	IN const GUID* layerKey,
	IN const GUID* calloutKey,
	IN FWPS_CALLOUT_CLASSIFY_FN classifyFn,
	IN FWPS_CALLOUT_NOTIFY_FN notifyFn,
	IN FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN flowDeleteNotifyFn,
	OUT UINT32* calloutId,   //注册 callout 时返回此 ID
	OUT UINT64* filterId
)
{
	NTSTATUS        status = STATUS_SUCCESS;
	FWPS_CALLOUT    sCallout = { 0 };
	FWPM_FILTER     mFilter = { 0 };
	FWPM_FILTER_CONDITION mFilter_condition[1] = { 0 };
	FWPM_CALLOUT    mCallout = { 0 };
	FWPM_DISPLAY_DATA mDispData = { 0 };
	BOOLEAN         bCalloutRegistered = FALSE;
 
	//这里未指定 sCallout.flags
	sCallout.calloutKey = *calloutKey;   //一个唯一的 GUID 值, 用来标记此 callout
	sCallout.classifyFn = classifyFn;    //每当回调处理网络数据时,过滤器引擎将调用此函数, 处理网络数据在这个回调函数里面处理.
	sCallout.flowDeleteFn = flowDeleteNotifyFn;  //每当终止由回调处理的数据流时,筛选器引擎将调用此函数
	sCallout.notifyFn = notifyFn;				 //卸载 callout 时, 会调用这个回调
 
	//要使用哪个设备对象注册
	status = FwpsCalloutRegister(gDevObj,	 //自己创建的设备对象
		&sCallout, //该结构体有用的是指定了 classifyFn 回调, 处理连接时的数据就会用到这个回调函数.
		calloutId  //返回的 calloutID , 取消注册 callout 会用到这个值
	);
	if (!NT_SUCCESS(status))
		goto exit;
 
	bCalloutRegistered = TRUE;
 
 
	mDispData.name = L"WFP TEST";        //可选的名字
	mDispData.description = L"yxp's WFP TEST";   //可选的描述
	//你感兴趣的内容
	mCallout.aPPLicableLayer = *layerKey;  //过滤层标识符   此过滤层允许授权对传出 tcp 连接的连接请求,
	//以及基于发送的第一个数据包授权传出非 tcp 通信量
 
	//你感兴趣的内容的 GUID
	mCallout.calloutKey = *calloutKey;   //该 GUID 值必须与 FwpsCalloutRegister 注册时的 GUID 值相同.
	mCallout.displayData = mDispData;
 
	//向过滤引擎添加 callout
	status = FwpmCalloutAdd(gEngineHandle,
		&mCallout,  //flags 没有设置
		NULL,  //安全描述符  可以为 NULL
		NULL); //返回一个 ID  与 FwpsCalloutRegister 函数返回的 ID 是相同的.
	if (!NT_SUCCESS(status))
		goto exit;
 
 
	//FWPM_action0 结构指定在所有筛选条件都为真时所采取的操作
	mFilter.action.calloutKey = *calloutKey;
	//交给 callout 处理  由 callout 来决定返回阻止(block)或者允许(permit)
	mFilter.action.type = FWP_ACTION_CALLOUT_TERMINATING;
 
	//描述
	mFilter.displayData.name = L"WFP TEST";
	mFilter.displayData.description = L"yxp's WFP TEST";
 
 
	mFilter.layerKey = *layerKey;	//这个值和 FwpmCalloutAdd 里面使用的值一样 过滤层标识符   此过滤层允许授权对传出 tcp 连接的连接请求,
	//以及基于发送的第一个数据包授权传出非 tcp 通信量  
 
	//过滤条件数
	mFilter.numFilterConditions = 0;
	//过滤条件
	mFilter.filterCondition = mFilter_condition;
 
	mFilter.subLayerKey = FWPM_SUBLAYER_UNIVERSAL;  //此子层承载所有未分配给任何其他子层的筛选器
 
	//BFE 将根据过滤条件自动分配权重
	mFilter.weight.type = FWP_EMPTY;
 
	//添加一个新的过滤器对象到系统中
	status = FwpmFilterAdd(gEngineHandle, &mFilter, NULL, filterId);
	if (!NT_SUCCESS(status))
		goto exit;
exit:
	if (!NT_SUCCESS(status))
	{
		if (bCalloutRegistered)
		{
			FwpsCalloutUnregisterById(*calloutId);
		}
	}
	return status;
}
 
NTSTATUS WallRegisterCallouts()
{
	NTSTATUS    status = STATUS_SUCCESS;
	BOOLEAN     bInTransaction = FALSE;
	BOOLEAN     bEngineOpened = FALSE;
	FWPM_SESSION session = { 0 };
 
	//置此标志时,会话结束时将自动删除在会话期间添加的任何对象
	session.flags = FWPM_SESSION_FLAG_DYNAMIC;
 
	//开启 WFP 引擎
	status = FwpmEngineOpen(NULL,
		RPC_C_AUTHN_WINNT,
		NULL,
		&session,
		&gEngineHandle);
	if (!NT_SUCCESS(status))
		goto exit;
	bEngineOpened = TRUE;
	// 在当前会话中开始显式事务
	status = FwpmTransactionBegin(gEngineHandle, 0);
	if (!NT_SUCCESS(status))
		goto exit;
	bInTransaction = TRUE;
	//注册回调函数
	status = RegisterCalloutForLayer(
		&FWPM_LAYER_ALE_AUTH_CONNECT_V4,  //此过滤层允许授权对传出 tcp 连接的连接请求,
		//以及基于发送的第一个数据包授权传出非 tcp 通信量
		&GUID_ALE_AUTH_CONNECT_CALLOUT_V4,
		WallALEConnectClassify,
		WallNotifyFn,
		WallFlowDeleteFn,
		&gAleConnectCalloutId,
		&gAleConnectFilterId);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("RegisterCalloutForLayer 失败!\n");
		goto exit;
	}
	//确认所有内容并提交,让回调函数正式发挥作用
	status = FwpmTransactionCommit(gEngineHandle);
	if (!NT_SUCCESS(status))
		goto exit;
	bInTransaction = FALSE;
exit:
	if (!NT_SUCCESS(status))
	{
		if (bInTransaction)
		{
			FwpmTransactionAbort(gEngineHandle);
		}
		if (bEngineOpened)
		{
			FwpmEngineClose(gEngineHandle);
			gEngineHandle = 0;
		}
	}
	return status;
}
 
NTSTATUS WallUnRegisterCallouts()
{
	if (gEngineHandle != 0)
	{
		//删除 FilterId
		FwpmFilterDeleTEById(gEngineHandle, gAleConnectFilterId);
		//删除 CalloutId
		FwpmCalloutDeleteById(gEngineHandle, gAleConnectCalloutId);
		//清空 FilterId
		gAleConnectFilterId = 0;
		//反注册 CalloutId
		FwpsCalloutUnregisterById(gAleConnectCalloutId);
		//清空 CalloutId
		gAleConnectCalloutId = 0;
		//关闭引擎
		FwpmEngineClose(gEngineHandle);
		gEngineHandle = 0;
	}
	return STATUS_SUCCESS;
}
 
VOID DriverUnload(PDRIVER_OBJECT driverObject)
{
	NTSTATUS status;
	UNICODE_STRING  deviceDosName = { 0 };
	status = WallUnRegisterCallouts();
	if (!NT_SUCCESS(status))
	{
		DbgPrint("WallUnRegisterCallouts 失败\n");
		return;
	}
	RtlInitUnicodeString(&deviceDosName, DEVICE_DOSNAME);
	IoDeleteSymbolicLink(&deviceDosName);
	if (gDevObj)
	{
		IoDeleteDevice(gDevObj);
		gDevObj = NULL;
	}
	DbgPrint("驱动卸载成功\n");
}
 
NTSTATUS DriverEntry(PDRIVER_OBJECT driverObject, PUNICODE_STRING registryPath)
{
	UNICODE_STRING  deviceName = { 0 };
	UNICODE_STRING  deviceDosName = { 0 };
	NTSTATUS status = STATUS_SUCCESS;
	driverObject->DriverUnload = DriverUnload;
	RtlInitUnicodeString(&deviceName, DEVICE_NAME);
	status = IoCreateDevice(driverObject,
		0,
		&deviceName,
		FILE_DEVICE_NETWORK,
		0,
		FALSE,
		&gDevObj);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("创建设备对象失败\n");
		return STATUS_UNSUCCESSFUL;
	}
	RtlInitUnicodeString(&deviceDosName, DEVICE_DOSNAME);
	status = IoCreateSymbolicLink(&deviceDosName, &deviceName);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("创建符号链接失败\n");
		return STATUS_UNSUCCESSFUL;
	}
	status = WallRegisterCallouts();
	if (!NT_SUCCESS(status))
	{
		DbgPrint("WallRegisterCallouts 失败!\n");
		return STATUS_UNSUCCESSFUL;
	}
	DbgPrint("过滤驱动加载成功\n");
	return status;
}