一、WDF驱动模型介绍:
WDF驱动模型是微软推出的驱动程序开发环境,是Vista及其以后OS的驱动模型。在visia之前win2000之后用的是WDM驱动模型。WDF是以WDM为基础进行了建模和封装,降低了开发难度。WDF将驱动程序与操作系统内核之间进行了分离,驱动程序与操作系统交互工作交给框架内封装的方法(函数)完成,这样驱动开发者只需专注处理硬件的行为即可。
二、WFP网络过滤平台介绍:
WFP网络过滤平台是一组 API 和系统服务,提供用于创建网络筛选应用程序的平台。 WFP API 允许开发人员编写与操作系统网络堆栈中多个层发生的数据包处理交互的代码。 可以在网络数据到达目标之前对其进行筛选和修改。WFP 旨在取代以前的数据包筛选技术,例如LSP、TDI(不能对包内容进行查看,只能对已知协议进行过滤)、NDIS Filter(需要与TDI配合才能查看进程信息)。
WFP有一个内置的过滤引擎(Filter Engine)提供支持。该框架包括用户层API和内核层API,应用层可以使用该框架完成简单过滤,涉及包内容修改等需要在内核完成。(例如开源项目windivert,本文代码主要借鉴该项目过滤思想)。
WFP在TCP/IP栈各个位置添加了垫片(Shim),使得整个网络数据从应用层生成到发送至网卡,或者从网卡接收数据到应用层收到数据,WFP都能够通过Shim拿到关于数据的信息以及相关数据。即在不同的分层(Layer)能够获取不同的数据那么,我们可以在感兴趣的位置,注册callout,进行过滤。
三、常见分层标识
FWPM_LAYER_OUTBOUND_IPPACKET_V4
四、示意图
五、开发流程
主要针对网络层的子层FWPM_LAYER_INBOUND_IPPACKET_V4和FWPM_LAYER_OUTBOUND_IPPACKET_V4进行数据拦截、修改和注入。主要流程为:
- WDF驱动模型初始化
- 注册WFP子层
- 打开过滤引擎
- 注册呼出函数
- 在呼出函数中对拦截到的数据进行解析,修改
- 将修改后的数据重新注入到原始路径
下面是各模块主要代码:
1、WDF驱动模型初始化
WDF_DRIVER_CONFIG_INIT(&config,
WDF_NO_EVENT_CALLBACK
);
config.DriverInitFlags |= WdfDriverInitNonPnpDriver;
config.EvtDriverUnload = DrEnc_unload;
// 创建驱动对象
status = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&config,
&driver
);
if (!NT_SUCCESS(status))
{
goto driver_entry_exit;
}
// 分配一个WDFDEVICE_INIT结构,驱动程序在创建一个新的控制设备对象时使用这个结构。
device_init = WdfControlDeviceInitAllocate(driver,
&SDDL_DEVOBJ_SYS_ALL_ADM_ALL);
if (device_init == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
KdPrint(("[DrEnc] failed to allocate WDF control device init structure\n"));
goto driver_entry_exit;
}
// 为指定的设备设置设备类型。
WdfDeviceInitSetDeviceType(device_init, FILE_DEVICE_NETWORK);
// 设置驱动程序如何访问包含在指定设备的读写请求中的数据缓冲区的方法或首选项。WdfDeviceIoDirect:直接I/O将用于访问数据缓冲区。
WdfDeviceInitSetIoType(device_init, WdfDeviceIoDirect);
// 将设备名称分配给设备的设备对象。
status = WdfDeviceInitAssignName(device_init, &device_name);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WDF device name\n"));
WdfDeviceInitFree(device_init);
goto driver_entry_exit;
}
WDF_OBJECT_ATTRIBUTES_INIT(&obj_attrs);
// 创建一个框架设备对象。
status = WdfDeviceCreate(&device_init, &obj_attrs, &device);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WDF control device\n"));
WdfDeviceInitFree(device_init);
goto driver_entry_exit;
}
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queue_config,
WdfIoQueueDispatchParallel);
queue_config.EvtIoRead = NULL;
queue_config.EvtIoWrite = NULL;
queue_config.EvtIoDeviceControl = DrEnc_ioctl;
WDF_OBJECT_ATTRIBUTES_INIT(&obj_attrs);
obj_attrs.ExecutionLevel = WdfExecutionLevelPassive;
obj_attrs.SynchronizationScope = WdfSynchronizationScopeNone;
// 为指定设备创建和配置I/O队列
status = WdfIoQueueCreate(device, &queue_config, &obj_attrs, &queue);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create default WDF queue\n"));
goto driver_entry_exit;
}
// 创建到指定设备的符号链接
status = WdfDeviceCreateSymbolicLink(device, &dos_device_name);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create device symbolic link\n"));
goto driver_entry_exit;
}
// 通知框架驱动程序已经完成了指定控制设备对象的初始化
WdfControlFinishInitializing(device);
2、注册WFP子层
// 创建一个句柄,该句柄可用于报文注入函数将报文或流数据注入到TCP/IP网络栈中,
// 也可用于FwpsQueryPacketInjectionState函数查询报文注入状态。
status = FwpsInjectionHandleCreate(AF_INET,
FWPS_INJECTION_TYPE_NETWORK | FWPS_INJECTION_TYPE_FORWARD,
&inject_handle_forward);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WFP forward packet injection handle, %ld\n", status));
goto driver_entry_exit;
}
status = FwpsInjectionHandleCreate(AF_INET,
FWPS_INJECTION_TYPE_NETWORK | FWPS_INJECTION_TYPE_FORWARD,
&inject_handle_in);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WFP inbound packet injection handle, %ld\n", status));
goto driver_entry_exit;
}
status = FwpsInjectionHandleCreate(AF_INET,
FWPS_INJECTION_TYPE_NETWORK | FWPS_INJECTION_TYPE_FORWARD,
&inject_handle_out);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WFP outbound packet injection handle, %ld\n", status));
goto driver_entry_exit;
}
// Create a NET_BUFFER_LIST pool handle.
RtlZeroMemory(&nbl_pool_params, sizeof(nbl_pool_params));
nbl_pool_params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
nbl_pool_params.Header.Revision =
NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
nbl_pool_params.Header.Size = sizeof(nbl_pool_params);
nbl_pool_params.fAllocateNetBuffer = TRUE;
nbl_pool_params.PoolTag = WINDIVERT_TAG;
nbl_pool_params.DataSize = 0;
nbl_pool_handle = NdisAllocateNetBufferListPool(NULL, &nbl_pool_params);
if (nbl_pool_handle == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
KdPrint(("[DrEnc] failed to allocate net buffer list pool, %ld\n", status));
goto driver_entry_exit;
}
// Create a NET_BUFFER pool handle.
RtlZeroMemory(&nb_pool_params, sizeof(nb_pool_params));
nb_pool_params.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
nb_pool_params.Header.Revision = NET_BUFFER_POOL_PARAMETERS_REVISION_1;
nb_pool_params.Header.Size =
NDIS_SIZEOF_NET_BUFFER_POOL_PARAMETERS_REVISION_1;
nb_pool_params.PoolTag = WINDIVERT_TAG;
nb_pool_params.DataSize = 0;
nb_pool_handle = NdisAllocateNetBufferPool(NULL, &nb_pool_params);
if (nb_pool_handle == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
KdPrint(("[DrEnc] failed to allocate net buffer pool, %ld\n", status));
goto driver_entry_exit;
}
// Open a handle to the filter engine:
status = FwpmEngineOpen(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL,
&engine_handle);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WFP engine handle, %ld\n", status));
goto driver_entry_exit;
}
// Register WFP sub-layers:
status = FwpmTransactionBegin(engine_handle, 0);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to begin WFP transaction, %ld\n", status));
FwpmTransactionAbort(engine_handle);
goto driver_entry_exit;
}
status = DrEnc_install_provider();
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to install provider, %ld\n", status));
FwpmTransactionAbort(engine_handle);
goto driver_entry_exit;
}
status = DrEnc_install_sublayer(WINDIVERT_LAYER_INBOUND_NETWORK_IPV4);
if (!NT_SUCCESS(status))
{
driver_entry_sublayer_error:
KdPrint(("[DrEnc] failed to install WFP sub-layer, %ld\n", status));
FwpmTransactionAbort(engine_handle);
goto driver_entry_exit;
}
status = DrEnc_install_sublayer(WINDIVERT_LAYER_OUTBOUND_NETWORK_IPV4);
if (!NT_SUCCESS(status))
{
goto driver_entry_sublayer_error;
}
// 提交当前会话中的当前事务
status = FwpmTransactionCommit(engine_handle);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to commit WFP transaction, %ld\n", status));
FwpmTransactionAbort(engine_handle);
goto driver_entry_exit;
}
3、打开过滤引擎
RtlZeroMemory(&session, sizeof(session));
session.flags |= FWPM_SESSION_FLAG_DYNAMIC;
status = FwpmEngineOpen(NULL, RPC_C_AUTHN_DEFAULT, NULL, &session,
&g_engine_handle);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create WFP engine handle, %ld\n", status));
goto windivert_create_exit;
}
4、注册呼出函数
HANDLE engine;
FWPS_CALLOUT scallout;
FWPM_CALLOUT mcallout;
UINT32 callout_id;
GUID callout_guid, filter_guid;
FWPM_FILTER filter;
UINT32 priority;
UINT64 weight;
NTSTATUS status = STATUS_SUCCESS;
engine = g_engine_handle;
priority = 1;
weight = (UINT64)priority;
callout_guid = g_callout_guid[idx];
filter_guid = g_filter_guid[idx];
RtlZeroMemory(&scallout, sizeof(scallout));
scallout.calloutKey = callout_guid;
scallout.classifyFn = layer->classify;
scallout.notifyFn = windivert_notify;
scallout.flowDeleteFn = layer->flow_delete;
RtlZeroMemory(&mcallout, sizeof(mcallout));
mcallout.calloutKey = callout_guid;
mcallout.displayData.name = layer->callout_name;
mcallout.displayData.description = layer->callout_desc;
mcallout.applicableLayer = *(layer->layer_guid);
RtlZeroMemory(&filter, sizeof(filter));
filter.filterKey = filter_guid;
filter.layerKey = *(layer->layer_guid);
filter.displayData.name = layer->filter_name;
filter.displayData.description = layer->filter_desc;
filter.action.type = FWP_ACTION_CALLOUT_UNKNOWN;
filter.action.calloutKey = callout_guid;
filter.subLayerKey = *(layer->sublayer_guid);
filter.weight.type = FWP_UINT64;
filter.weight.uint64 = &weight;
//filter.rawContext = (UINT64)context;
status = FwpsCalloutRegister(WdfDeviceWdmGetDeviceObject(g_device),
&scallout, &callout_id);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to install WFP callout, %ld\n", status));
return status;
}
status = FwpmTransactionBegin(engine, 0);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to begin WFP transaction, %ld\n", status));
goto windivert_install_callout_error;
}
status = FwpmCalloutAdd(engine, &mcallout, NULL, NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to add WFP callout, %ld\n", status));
goto windivert_install_callout_error;
}
status = FwpmFilterAdd(engine, &filter, NULL, NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to add WFP filter, %ld\n", status));
goto windivert_install_callout_error;
}
status = FwpmTransactionCommit(engine);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to commit WFP transaction, %ld\n", status));
goto windivert_install_callout_error;
}
g_nCalloutNum++;
return STATUS_SUCCESS;
windivert_install_callout_error:
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to DrEnc_install_callouts, %ld\n", status));
}
FwpmTransactionAbort(engine);
FwpsCalloutUnregisterByKey(&callout_guid);
return status;
5、在呼出函数中对拦截到的数据进行解析,修改
PNET_BUFFER_LIST buffers;
PNET_BUFFER buffer, buffer_itr;
ULONG packet_len;
ULONG packet_size;
packet_t work;
UINT8* work_data;
WINDIVERT_PACKET info;
NTSTATUS status;
FWPS_PACKET_INJECTION_STATE packet_state;
HANDLE packet_context;
LONGLONG timestamp;
BOOL match;
INT32 packet_priority;
BOOL impostor;
UINT64 flags;
NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO checksums;
BOOL sniffed, ip_checksum, tcp_checksum, udp_checksum;
result->actionType = FWP_ACTION_CONTINUE;
flags = 0;
buffers = (PNET_BUFFER_LIST)data;
buffer = NET_BUFFER_LIST_FIRST_NB(buffers);
if (NET_BUFFER_LIST_NEXT_NBL(buffers) != NULL)
{
// This is a fragment group. This can be ignored since each fragment
// should have already been indicated.
return;
}
if (outbound)
{
packet_state = FwpsQueryPacketInjectionState(inject_handle_out,
buffers, &packet_context);
}
else
{
packet_state = FwpsQueryPacketInjectionState(inject_handle_in,
buffers, &packet_context);
}
impostor = FALSE;
if (packet_state == FWPS_PACKET_INJECTED_BY_SELF ||
packet_state == FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF)
{
packet_priority = (UINT32)(ULONG_PTR)packet_context;
if (packet_priority <= priority)
{
//WdfObjectDereference(object);
return;
}
}
else if (packet_state == FWPS_PACKET_INJECTED_BY_OTHER)
{
// This is a packet injected by another driver, possibly an older
// version of WinDivert. To prevent block-clone-reinject infinite
// loops, we mark this packet as an "impostor".
impostor = TRUE;
}
// Get the timestamp.
timestamp = KeQueryPerformanceCounter(NULL).QuadPart;
// Retreat the NET_BUFFER to the IP header, if necessary.
// If (advance != 0) then this must be in the inbound path, and the
// NET_BUFFER_LIST must contain exactly one NET_BUFFER.
if (advance != 0)
{
status = NdisRetreatNetBufferDataStart(buffer, advance, 0, NULL);
if (!NT_SUCCESS(status))
{
//WdfObjectDereference(object);
return;
}
}
sniffed = ((flags & WINDIVERT_FLAG_SNIFF) != 0);
buffer_itr = buffer;
while (buffer_itr != NULL)
{
match = FALSE;
packet_len = NET_BUFFER_DATA_LENGTH(buffer_itr);
if (packet_len > WINDIVERT_MTU_MAX)
{
// Cannot handle oversized packet
break;
}
packet_size = WINDIVERT_PACKET_SIZE(WINDIVERT_DATA_NETWORK,
packet_len);
work = (packet_t)windivert_malloc(packet_size, FALSE);
if (work == NULL)
{
goto next_buffer;
}
work->packet_len = (UINT32)packet_len;
work_data = WINDIVERT_LAYER_DATA_PTR(work);
RtlCopyMemory(work_data, network_data, sizeof(WINDIVERT_DATA_NETWORK));
work_data = WINDIVERT_PACKET_DATA_PTR(WINDIVERT_DATA_NETWORK, work);
if (!windivert_copy_data(buffer_itr, work_data, packet_len))
{
windivert_free(work);
goto next_buffer;
}
if (WinDivertHelperParsePacketEx(work_data, packet_len, &info) == FALSE)
{
KdPrint(("[DrEnc] WinDivertHelperParsePacketEx failed\n"));
windivert_free(work);
goto next_buffer;
}
if (info.PayloadLength <= 0)
{
//KdPrint(("[DrEnc] WinDivertHelperParsePacketEx failed\n"));
windivert_free(work);
goto next_buffer;
}
if (info.Protocol == IPPROTO_TCP || info.Protocol == IPPROTO_UDP)
{
if (outbound && MatchPolicy(&info))
{
//KdPrint(("[DrEnc] outbound\n"));
match = ChangeMatchData(&info);
}
else if (!outbound && MatchPolicy(&info))
{
//KdPrint(("[DrEnc] inbound\n"));
match = ChangeMatchData(&info);
}
}
if (match)
{
checksums.Value = NET_BUFFER_LIST_INFO(buffers,
TcpIpChecksumNetBufferListInfo);
if (outbound)
{
ip_checksum = (checksums.Transmit.IpHeaderChecksum == 0);
tcp_checksum = (checksums.Transmit.TcpChecksum == 0);
udp_checksum = (checksums.Transmit.UdpChecksum == 0);
}
else
{
ip_checksum = (checksums.Receive.IpChecksumSucceeded == 0);
tcp_checksum = (checksums.Receive.TcpChecksumSucceeded == 0);
udp_checksum = (checksums.Receive.UdpChecksumSucceeded == 0);
}
work->sniffed = (sniffed ? 1 : 0);
work->outbound = (outbound ? 1 : 0);
work->loopback = (loopback ? 1 : 0);
work->impostor = (impostor ? 1 : 0);
work->ipv6 = 0;
work->ip_checksum = (ip_checksum ? 1 : 0);
work->tcp_checksum = (tcp_checksum ? 1 : 0);
work->udp_checksum = (udp_checksum ? 1 : 0);
work->icmp_checksum = 1;
work->match = match;
work->packet_size = packet_size;
work->priority = priority;
work->timestamp = timestamp;
WinDivertHelperCalcChecksums(work_data, packet_len, NULL, 0);
windivert_inject_packet(work);
}
else
{
//WdfWorkItemEnqueue(work);
windivert_free(work);
}
next_buffer:
buffer_itr = NET_BUFFER_NEXT_NB(buffer_itr);
}
if (advance != 0)
{
// Advance the NET_BUFFER to its original position. Note that we can
// do this here, since if (advance != 0) then there is only one
// NET_BUFFER in the NET_BUFFER_LIST, meaning that STEPS (1) and (3)
// will be empty.
NdisAdvanceNetBufferDataStart(buffer, advance, FALSE, NULL);
}
if (match)
{
result->actionType = FWP_ACTION_BLOCK;
result->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
result->rights &= ~FWPS_RIGHT_ACTION_WRITE;
}
6、将修改后的数据重新注入到原始路径
NTSTATUS status = 0;
UINT8* packet_data;
UINT32 packet_len;
UINT64 checksums;
PWINDIVERT_DATA_NETWORK network_data;
PMDL mdl;
PNET_BUFFER_LIST buffers;
HANDLE handle;
UINT32 priority;
//if (packet->layer != WINDIVERT_LAYER_NETWORK &&
// packet->layer != WINDIVERT_LAYER_NETWORK_FORWARD)
//{
// KdPrint(("[DrEnc] packet->layer != WINDIVERT_LAYER_NETWORK\n"));
// windivert_free_packet(packet);
// return STATUS_INVALID_PARAMETER;
//}
network_data = (PWINDIVERT_DATA_NETWORK)WINDIVERT_LAYER_DATA_PTR(packet);
packet_data = WINDIVERT_PACKET_DATA_PTR(WINDIVERT_DATA_NETWORK, packet);
packet_len = packet->packet_len;
// Fix checksums:
checksums =
(packet->ip_checksum == 0 ? 0 : WINDIVERT_HELPER_NO_IP_CHECKSUM) |
(packet->tcp_checksum == 0 ? 0 : WINDIVERT_HELPER_NO_TCP_CHECKSUM) |
(packet->udp_checksum == 0 ? 0 : WINDIVERT_HELPER_NO_UDP_CHECKSUM) |
(packet->icmp_checksum == 0 ? 0 : WINDIVERT_HELPER_NO_ICMP_CHECKSUM |
WINDIVERT_HELPER_NO_ICMPV6_CHECKSUM);
//WinDivertHelperCalcChecksums(packet_data, packet_len, NULL, checksums);
if (WinDivertHelperCalcChecksums(packet_data, packet_len, NULL, checksums) == FALSE)
{
KdPrint(("[DrEnc] CalcChecksums failed\n"));
return STATUS_INVALID_PARAMETER;
}
// Decrement TTL for impostor packets:
if (packet->impostor != 0 &&
!WinDivertHelperDecrementTTL(packet_data, packet_len))
{
status = STATUS_HOPLIMIT_EXCEEDED;
KdPrint(("[DrEnc] failed to inject ttl-exceeded impostor packet , %ld\n", status));
windivert_free_packet(packet);
return status;
}
// Inject packet:
mdl = IoAllocateMdl(packet_data, packet_len, FALSE, FALSE, NULL);
if (mdl == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
KdPrint(("[DrEnc] failed to allocate MDL for injected packet , %ld\n", status));
windivert_free_packet(packet);
return status;
}
MmBuildMdlForNonPagedPool(mdl);
status = FwpsAllocateNetBufferAndNetBufferList(nbl_pool_handle, 0, 0,
mdl, 0, packet_len, &buffers);
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to create NET_BUFFER_LIST for injected packet , %ld\n", status));
IoFreeMdl(mdl);
windivert_free_packet(packet);
return status;
}
priority = packet->priority;
if (packet->layer == WINDIVERT_LAYER_NETWORK_FORWARD)
{
handle = inject_handle_forward;
status = FwpsInjectForwardAsync(handle, (HANDLE)priority, 0,
(packet->ipv6 ? AF_INET6 : AF_INET), UNSPECIFIED_COMPARTMENT_ID,
network_data->IfIdx, buffers, windivert_inject_complete,
(HANDLE)packet);
}
else if (packet->outbound)
{
handle = inject_handle_out;
status = FwpsInjectNetworkSendAsync(handle, (HANDLE)priority, 0,
UNSPECIFIED_COMPARTMENT_ID, buffers, windivert_inject_complete,
(HANDLE)packet);
}
else
{
handle = inject_handle_in;
status = FwpsInjectNetworkReceiveAsync(handle, (HANDLE)priority, 0,
UNSPECIFIED_COMPARTMENT_ID, network_data->IfIdx,
network_data->SubIfIdx, buffers, windivert_inject_complete,
(HANDLE)packet);
}
if (!NT_SUCCESS(status))
{
KdPrint(("[DrEnc] failed to inject (packet=%p) , %ld\n", packet, status));
FwpsFreeNetBufferList(buffers);
IoFreeMdl(mdl);
windivert_free_packet(packet);
}
//KdPrint(("[DrEnc] windivert_inject_packet success\n"));
return status;
以上代码仅为作此开发的同仁提供思路及方向,作者开发的实现也是将windivert开源项目下大功夫研究之后,取其精华部分从零开始重构得来。希望能为致力于此道者提供帮助。