Windows 中的 WFP 框架是一个用于网络流量管理的工具,它可以帮助用户控制数据包在网络中的流动,从而提高网络安全性和性能。
本文将深入介绍 Windows 中的 WFP 框架,并通过一个简单的实例(基于 WFP 框架的 DNS 流量解析工具)让大家更好地理解它。
WFP 框架
WFP 框架是Windows Filtering Platform
的简称,即 Windows 过滤平台。
它是一个用于网络流量管理的工具,可以让用户定义数据包进出网络的规则,从而控制数据包在网络中的流动。
WFP 框架可以实现以下功能:
防火墙:
WFP 框架可以帮助用户构建一个防火墙,用于拦截来自不受信任的来源的数据包,并阻止它们进入网络网络流量监控:
WFP 框架可以帮助用户监控网络流量,让用户可以了解网络上发生的一切事情。应用程序控制:
WFP 框架可以帮助用户控制应用程序访问网络的权限,从而提高网络安全性。
hello world
以下代码是最小的实现代码,请注意:
他不完整,比如缺少一些全局变量+没有错误异常处理
没有卸载函数
不过,作为教学用途,这段代码已经足够了。
作者认为,新人不需要看太多代码,过多的代码会让人感到困惑。
所以,上面的代码已经是经过精简的版本了
auto MiniInstall() -> bool {
FWPM_SESSION0 wfpSession = {0}; // WFP 会话
FWPM_SUBLAYER0 wfpFirewallSubLayer = {0}; // WFP 子层
FWPM_PROVIDER0 ProviderInfo = {0}; // WFP 提供程序信息
bool inTransaction = false; // 是否在事务中
wfpSession.flags = FWPM_SESSION_FLAG_DYNAMIC; // 设置会话标志
auto ntStatus = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &wfpSession,
&gEngineHandle); // 打开 WFP 引擎
do {
if (NT_SUCCESS(ntStatus) == false) { // 检查是否成功打开引擎
break;
}
if (NT_SUCCESS(ntStatus = FwpmTransactionBegin0(gEngineHandle, 0)) ==
false) { // 开始事务
break;
}
inTransaction = true;
ProviderInfo.displayData.name =
L"My First Wfp Provider by huoji"; // 设置提供程序名称
ProviderInfo.displayData.description =
L"Huoji Wfp Provider"; // 设置提供程序描述
ProviderInfo.providerKey = GUID_WFP_PROVIDER; // 设置提供程序的 GUID
ProviderInfo.serviceName = L"duck"; // 设置提供程序的服务名
if (NT_SUCCESS(ntStatus = FwpmProviderAdd0(gEngineHandle, &ProviderInfo,
NULL)) ==
false) { // 添加 WFP 提供程序
break;
}
wfpFirewallSubLayer.subLayerKey =
GUID_SUBLAYER; // 2e207682-d95f-4525-b966-969f26587f03
wfpFirewallSubLayer.displayData.name =
L"First Wfp Transport Wfp Sub-Layer"; // 设置子层名称
wfpFirewallSubLayer.displayData.description =
L"Sub-Layer for use by First Wfp callouts "; // 设置子层描述
wfpFirewallSubLayer.flags = 0; // 设置子层标志
wfpFirewallSubLayer.weight = 0; // 设置子层权重
if (NT_SUCCESS(ntStatus = FwpmSubLayerAdd0(
gEngineHandle, &wfpFirewallSubLayer, NULL)) ==
false) { // 添加 WFP 子层
break;
}
FWPS_CALLOUT0 sCallout = {0};
FWPM_CALLOUT0 mCallout = {0};
sCallout.classifyFn = (FWPS_CALLOUT_CLASSIFY_FN0)
NewESTConnect; // 第一次握手的回调.后面会细说
sCallout.notifyFn = (FWPS_CALLOUT_NOTIFY_FN0)
ConnectNotify; // 通知回调 不用管.后面会细说
sCallout.flowDeleteFn = (FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0)
ConnectFlowDelete; // 流删除回调
const auto calloutType = FWP_ACTION_CALLOUT_INSPECTION; // 回调类型
if (NT_SUCCESS(ntStatus = FwpmCalloutAdd0(gEngineHandle, &mCallout,
NULL, NULL)) ==
false) { // 添加 WFP 回调函数
break;
}
FWPM_FILTER0 Filter = {0};
Filter.layerKey = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4; // 设置过滤器的层
Filter.displayData.name =
L"Transport First ALE Classify"; // 设置过滤器名称
Filter.displayData.description =
L"Intercepts inbound or outbound connect attempts"; // 设置过滤器描述
Filter.action.type = calloutType; // 设置回调类型
Filter.action.calloutKey = SF_EST_CALLOUT_GUID; // 设置回调函数的 GUID
Filter.subLayerKey = SF_SUBLAYER; // 设置过滤器所属的子层
Filter.weight.type = FWP_EMPTY; // 设置过滤器权重
if (NT_SUCCESS(ntStatus = FwpmFilterAdd0(gEngineHandle, &Filter, NULL,
NULL)) ==
false) { // 添加 WFP 过滤器
break;
}
if (NT_SUCCESS(ntStatus = FwpmTransactionCommit0(gEngineHandle)) ==
false) { // 提交事务
break;
}
inTransaction = false;
} while (false);
// 错误处理代码...cleanup的代码..
}
代码拆解:
首先,我们定义了一个 WFP 提供程序的 GUID:
DEFINE_GUID(GUID_WFP_PROVIDER, 0x19b351df, 0x1055, 0x46e1, 0x93, 0x42, 0xff,
0xfe, 0x4a, 0xd0, 0xdd, 0xc1);
然后,在MiniInstall
函数中,我们初始化了一些变量,包括 WFP 会话,WFP 子层和 WFP 提供程序信息:
FWPM_SESSION0 wfpSession = {0};
FWPM_SUBLAYER0 wfpFirewallSubLayer = {0};
FWPM_PROVIDER0 ProviderInfo = {0};
接着,我们设置会话标志,并打开 WFP 引擎:
wfpSession.flags = FWPM_SESSION_FLAG_DYNAMIC;
auto ntStatus = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &wfpSession,
&gEngineHandle);
在打开 WFP 引擎后,我们开始事务,并添加一个新的 WFP 提供程序:
if (NT_SUCCESS(ntStatus = FwpmTransactionBegin0(gEngineHandle, 0)) ==
false) {
break;
}
inTransaction = true;
ProviderInfo.displayData.name = L"My First Wfp Provider by huoji";
ProviderInfo.displayData.description = L"Huoji Wfp Provider";
ProviderInfo.providerKey = GUID_WFP_PROVIDER;
ProviderInfo.serviceName = L"duck";
if (NT_SUCCESS(ntStatus = FwpmProviderAdd0(gEngineHandle, &ProviderInfo,
NULL)) == false) {
break;
}
然后,我们添加一个子层,并为子层添加一些回调函数:
wfpFirewallSubLayer.subLayerKey =
GUID_SUBLAYER;
wfpFirewallSubLayer.displayData.name =
L"First Wfp Transport Wfp Sub-Layer";
wfpFirewallSubLayer.displayData.description =
L"Sub-Layer for use by First Wfp callouts";
wfpFirewallSubLayer.flags = 0;
wfpFirewallSubLayer.weight = 0;
if (NT_SUCCCCESS(ntStatus = FwpmSubLayerAdd0(
gEngineHandle, &wfpFirewallSubLayer, NULL)) ==
false) {
break;
}
FWPS_CALLOUT0 sCallout = {0};
FWPM_CALLOUT0 mCallout = {0};
sCallout.classifyFn = (FWPS_CALLOUT_CLASSIFY_FN0)
NewESTConnect;
sCallout.notifyFn = (FWPS_CALLOUT_NOTIFY_FN0)
ConnectNotify;
sCallout.flowDeleteFn = (FWPS_CALLOUT_FLOW_DELETE_NOTIFY_FN0)
ConnectFlowDelete;
const auto calloutType = FWP_ACTION_CALLOUT_INSPECTION;
if (NT_SUCCESS(ntStatus = FwpmCalloutAdd0(gEngineHandle, &mCallout,
NULL, NULL)) == false) {
break;
}
最后,我们添加过滤器,并提交事务:
FWPM_FILTER0 Filter = {0};
Filter.layerKey = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4;
Filter.displayData.name = L"Transport First ALE Classify";
Filter.displayData.description =
L"Intercepts inbound or outbound connect attempts";
Filter.action.type = calloutType;
Filter.action.calloutKey = SF_EST_CALLOUT_GUID;
Filter.subLayerKey = SF_SUBLAYER;
Filter.weight.type = FWP_EMPTY;
if (NT_SUCCESS(ntStatus = FwpmFilterAdd0(gEngineHandle, &Filter, NULL, NULL)) == false) {
break;
}
if (NT_SUCCESS(ntStatus = FwpmTransactionCommit0(gEngineHandle)) ==
false) {
break;
}
inTransaction = false;
恭喜你第一个hello world
就这样完成了
Callout层
在Windows Filtering Platform (WFP)
中,Callout 层是用来拦截和修改网络流量的一个重要机制。它提供了一种机制,可以在 WFP 拦截流量时,调用用户自定义的回调函数,以实现自定义的过滤逻辑。
注册layer函数在上面的代码中
mCallout.calloutKey = *calloutKey; //callout的GUID
mCallout.applicableLayer = *layerKey; //层的GUID
if (NT_SUCCESS(ntStatus = FwpmCalloutAdd0(EngineHandle, &mCallout, NULL,
NULL)) == false) {
break;
}
比如 为了拦截DNS流量.本例子中提供的layer Guid是:
FWPM_LAYER_DATAGRAM_DATA_V4
他在整个WFP生命周期中的位置是:
客户端 (发送方) 执行活动打开
bind: FWPM_LAYER_ALE_BIND_REDIRECT_V4 (Windows 7 / Windows Server 2008 R2 仅)
bind:FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4
sendto:FWPM_LAYER_ALE_CONNECT_REDIRECT_V4 (Windows 7/Windows Server 2008 R2 仅)
sendto:FWPM_LAYER_ALE_AUTH_CONNECT_V4
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
数据:FWPM_LAYER_DATAGRAM_DATA_V4 <-这里
UDP 消息:FWPM_LAYER_OUTBOUND_TRANSPORT_V4
IP 数据报:FWPM_LAYER_OUTBOUND_IPPACKET_V4
服务器
IP 数据报:FWPM_LAYER_INBOUND_IPPACKET_V4
UDP 消息:FWPM_LAYER_INBOUND_TRANSPORT_V4
UDP 消息:FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
数据:FWPM_LAYER_DATAGRAM_DATA_V4 <-这里
而为了建立进程与报文的关联,必须得在FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
这个层建立EST的Callout过滤(用于监听握手)
DEFINE_GUID(SF_EST_CALLOUT_GUID, 0x95ECC39E, 0xC9F7, 0x436A, 0xA6, 0x61, 0x54,
0x40, 0x1A, 0xDF, 0x0D, 0x0D);
让我们逐个实现关键的层,最终目的是解析DNS流量并且判断是哪个进程发的。
TCP/UDP 握手监听FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
在WFP中UDP也有握手,也有timeout,这个是微软为了方便处理而虚拟出来的东西。
layerGuid是FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4
这是本层的函数原型:
// 新的握手,注意UDP是windows自己抽象出来的一层吊东西.UDP不应该有连接的
// 之前的AuthConnectIpv4没办法抓UDP的
auto NewESTConnect(const FWPS_INCOMING_VALUES0* inFixedValues,
const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
void* layerData, const FWPS_FILTER1* filter,
uint64_t flowContext, FWPS_CLASSIFY_OUT0* classifyOut)
-> void
通常情况下,在本层中的作用是建立ctx,给后续层用.微软用的是链表.我这边用hash表,hash表你需要自己自定义,这边不做过多介绍。
https://github.com/0x9dec1980/Windows-samples/blob/554b4fa23c8e10edbe55aa3e6a64ae73a636e3b8/WDK-Samples/WDK%207.1.0%20(7600.16385.1)/src/network/trans/ddproxy/sys/DD_proxy.c#L197
到了本层 我们应该重点关注几个信息:
进程id只有本层拿得到
flowHandle是贯穿整个WFP框架的.微软不会和minifilter一样给你个函数啥的自己获取.而是要你自己通过这个flowhandle获取一些信息
注意IRQL.如果你的ctx结构体太大.如果太大需要用连续内存.exallocpool不保证一定连续(具体看微软文档).所以使用
MmAllocateContiguousMemory
const auto flowHandle = inMetaValues->flowHandle;
const auto processId = inMetaValues->processId;
之后我们就可以获取一些基本的信息.包括ip地址、端口号、协议、方向等
const auto RemoteIp =
inFixedValues
->incomingValue
[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS]
.value.uint32;
const auto LocalIp =
inFixedValues
->incomingValue
[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS]
.value.uint32;
const auto RemotePort =
inFixedValues
->incomingValue
[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT]
.value.uint16;
const auto LocalPort =
inFixedValues
->incomingValue
[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT]
.value.uint16;
IPPROTO Protocol = static_cast<IPPROTO>(
inFixedValues
->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_PROTOCOL]
.value.uint32);
const auto Direction =
inFixedValues
->incomingValue[FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION]
.value.uint8;
.......
再然后 通过FwpsFlowAssociateContext0
绑定流。这样这个流就会贯穿整个生命周期。当结束时候会自己走到之前设置的 follow delete 函数中。
auto ntStatus = FwpsFlowAssociateContext0(
flowHandle, FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4,
AleConnectCalloutIdV4, reinterpret_cast<uint64_t>(netWorkCtx));
if (NT_SUCCESS(ntStatus) == false) {
break;
}
//TODO: 一个flow handle与ctx的hash表或者链表,以后其他层要查
此外本层还可以决定需不需要终止链接:
if (classifyOut->rights & FWPS_RIGHT_ACTION_WRITE) {
classifyOut->actionType =
isNeedBlock ? FWP_ACTION_BLOCK : FWP_ACTION_CONTINUE;
}
那么,我们的流就已经创建好了。
流清理/链接断开
这个不算层,这个算之前的清理,作用是清理流。
auto ConnectFlowDelete(uint16_t layerId, uint32_t calloutId,
uint64_t flowContext) -> void {
// 流删除
auto netWorkCtx = reinterpret_cast<_NetWorkCtx*>(flowContext);
if (netWorkCtx != nullptr && isNeedCleanUp == false) {
DebugPrint("[%s]ctx flow delete remote ip: %d.%d.%d.%d Port: %d \n",
__FUNCTION__, netWorkCtx->Ipv4_remoteIp & 0xff,
(netWorkCtx->Ipv4_remoteIp >> 8) & 0xff,
(netWorkCtx->Ipv4_remoteIp >> 16) & 0xff,
(netWorkCtx->Ipv4_remoteIp >> 24) & 0xff,
netWorkCtx->Ipv4_remotePort);
freeCtx(netWorkCtx);
MmFreeContiguousMemory(netWorkCtx);
}
}
也可以当做 TCP/UDP握手断开
TCP 数据传输层 FWPM_LAYER_STREAM_V4
本层用于监控TCP的数据传输
一开始,请先设置type为FWP_ACTION_PERMIT
。classifyOut->actionType = FWP_ACTION_PERMIT
;
关于这个:
在 WFP 中,过滤器的操作类型可以是FWP_ACTION_BLOCK
或者FWP_ACTION_PERMIT
等。
当 WFP 拦截到一个网络流量时,会根据过滤器的操作类型来决定对这个流量的处理方式。
如果过滤器的操作类型是FWP_ACTION_PERMIT
,则 WFP 会允许这个流量通过;
如果过滤器的操作类型是FWP_ACTION_BLOCK
,则 WFP 会拦截这个流量,并阻止它的传输。
本层中需要用flow handle去查之前连接绑定的ctx
if (inMetaValues == nullptr || inFixedValues == nullptr || isNeedCleanUp) {
return;
}
const auto ctx = getCtxByflowHandle(inMetaValues->flowHandle);
if (ctx == nullptr) {
return;
}
然后我们就可以取数据了
const auto streamPacket =
reinterpret_cast<FWPS_STREAM_CALLOUT_IO_PACKET*>(layerData);
if (streamPacket == nullptr || streamPacket->streamData == nullptr) {
break;
}
if (streamPacket->streamData->dataLength == 0 ||
ctx->Ipv4_remoteIp == ctx->Ipv4_localIp) {
break;
}
这段代码中,使用了一个const auto streamPacket
变量,并将layerData
强制转换成FWPS_STREAM_CALLOUT_IO_PACKET
类型。
FWPS_STREAM_CALLOUT_IO_PACKET
是 WFP 中的一种数据结构,它用来表示流量的相关信息。在这里,streamPacket
变量保存了一个指向该类型的指针,用来操作这条流量。
获取到streampacket
后,就能拷贝到缓冲区中。读取网络流量了,这边使用FwpsCopyStreamDataToBuffer
进行拷贝:
streamBuffer = reinterpret_cast<BYTE*>(
Tools::AllocContiguousMemory(streamPacket->streamData->dataLength));
if (streamBuffer == nullptr) {
break;
}
const bool isOutBound = direction == FWP_DIRECTION_OUTBOUND;
size_t byte_copied = 0;
FwpsCopyStreamDataToBuffer(streamPacket->streamData, streamBuffer,
streamPacket->streamData->dataLength,
&byte_copied);
if (byte_copied == streamPacket->streamData->dataLength) {
//TODO:..
}
有了网络流量包,你可以拿来做任何你想做的,比如匹配到恶意代码后,终止网络连接:
if (isNeedBlock) {
if (classifyOut->rights & FWPS_RIGHT_ACTION_WRITE) {
classifyOut->actionType = FWP_ACTION_BLOCK;
}
}
这里不是我们的重点,因为本篇文章主要介绍DNS流量,是UDP。
UDP数据传输层FWPM_LAYER_DATAGRAM_DATA_V4
本层跟TCP差不多,但是跟TCP层的不同是流量的获取。
udp的netbuffer
是这样获取的:
const auto NetBufferList = (PNET_BUFFER_LIST)layerData;
if (NetBufferList == nullptr) {
break;
}
const auto NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);
if (NetBuffer == nullptr) {
break;
}
auto UdpContentLength = NET_BUFFER_DATA_LENGTH(NetBuffer);
if (UdpContentLength == 0) {
break;
}
然后你需要判断一下方向:
const auto direction =
inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION]
.value.uint8;
如果是输出方向,则需要计算出UDP头长度:
if (direction == FWP_DIRECTION_OUTBOUND) {
UdpHeaderLength = inMetaValues->transportHeaderSize;
if (UdpHeaderLength == 0 || UdpContentLength < UdpHeaderLength) {
break;
}
UdpContentLength -= UdpHeaderLength;
isOutBound = true;
}
然后通过NdisGetDataBuffer
获取包的buffer:
UdpContent = Tools::AllocPoolMemory(UdpContentLength + UdpHeaderLength,
NETWORK_TAG);
if (UdpContent == nullptr) {
break;
}
ndisBuffer = NdisGetDataBuffer(
NetBuffer, UdpContentLength + UdpHeaderLength, UdpContent, 1, 0);
if (ndisBuffer == nullptr) {
break;
}
之后同理,可以做一些操作了:
const bool isNeedBlock =
isOutBound
? OnUdpPacketSend(
ctx, (void*)((uint64_t)ndisBuffer + UdpHeaderLength),
UdpContentLength)
: OnUdpPacketRecv(
ctx, (void*)((uint64_t)ndisBuffer + UdpHeaderLength),
UdpContentLength);
if (isNeedBlock && (classifyOut->rights & FWPS_RIGHT_ACTION_WRITE)) {
classifyOut->actionType = FWP_ACTION_BLOCK;
}
解析DNS并且拿到域名
DNS端口是53,在不考虑其他DNS的情况比如Doh的情况下,代码如下:
if (ctx->Ipv4_remotePort == 53) {
NetWorkResolver::ResolveDnsPacket(buffer, bufferSize);
}
分析DNS包结构
这边主要说的是请求包,而且不考虑question count
大于1的情况:
比如查询www.baidu.com
除去包头,只看body的情况下:
03(www的长度) 77 77 77(w的ascii码) 0x5(baidu的长度) .....
编写dns解包的代码
整个函数的执行过程简要地描述如下:
检查 DNS 数据包的大小,如果小于 DNS 头部的大小,则退出函数。
检查 DNS 头部中的标志位
IsResponse
,如果为 true,则表示数据包是一个响应数据包,函数也退出。计算出 DNS 数据部分的大小,如果 DNS 数据部分的大小大于整个数据包的大小,则退出函数。
为 DNS 数据部分分配内存,并解析出 DNS 查询请求包中的域名。
检查域名是否提取成功,如果提取成功,则调用
DebugPrint
函数打印域名。释放分配的内存,并退出函数
对应的完整解包代码如下:
auto ResolveDnsPacket(void* packet, size_t packetSize) -> void {
if (packetSize < sizeof(DNS_HEADER)) {
return;
}
DNS_HEADER* dnsHeader = (DNS_HEADER*)packet;
if (dnsHeader->IsResponse) {
return;
}
size_t dnsDataLength = packetSize - sizeof(DNS_HEADER);
// Check if DNS data length is greater than packet size
if (dnsDataLength >= packetSize) {
return;
}
// resolver DNS question packet and get domain Name
char* dnsData = (char*)packet + sizeof(DNS_HEADER);
char* domainName =
reinterpret_cast<char*>(Tools::AllocContiguousMemory(allocDomainNameLength));
if (domainName == nullptr) {
return;
}
bool isSuccess = true;
size_t domainNameLength = 0;
while (dnsDataLength > 0) {
const auto length = *dnsData;
if (length == 0) {
break;
}
if (length >= packetSize || length > 256) {
break;
}
// Check if domain name length is greater than domainName array
// length
if (domainNameLength + 1 > allocDomainNameLength) {
isSuccess = false;
break;
}
auto domainNameStr = *(dnsData + 1);
// 检查第一个字符是否是可读字符
if (isprint(domainNameStr) == false) {
isSuccess = false;
break;
}
if (domainNameLength != 0) {
domainName[domainNameLength] = '.';
domainNameLength++;
}
memcpy(domainName + domainNameLength, dnsData + 1, length);
domainNameLength += length;
dnsDataLength -= *dnsData + 1;
dnsData += *dnsData + 1;
}
if (isSuccess) {
// TODO: OnDnsQueryEvent;
DebugPrint("ResolverDnsPacket: %s \n", domainName);
}
MmFreeContiguousMemory(domainName);
}
函数首先检查 DNS 数据包的大小,如果小于 DNS 头部的大小,则退出函数
if (packetSize < sizeof(DNS_HEADER)) {
return;
}
接下来,函数检查 DNS 头部中的标志位IsResponse
,如果为 true,则表示数据包是一个响应数据包,函数也退出:
DNS_HEADER* dnsHeader = (DNS_HEADER*)packet;
if (dnsHeader->IsResponse) {
return;
}
接下来,函数计算出 DNS 数据部分的大小,如果 DNS 数据部分的大小大于整个数据包的大小,则退出函数:
size_t dnsDataLength = packetSize - sizeof(DNS_HEADER);
// Check if DNS data length is greater than packet size
if (dnsDataLength >= packetSize) {
return;
}
接下来,函数为 DNS 数据部分分配内存,并解析出 DNS 查询请求包中的域名:
// resolver DNS question packet and get domain Name
char* dnsData = (char*)packet + sizeof(DNS_HEADER);
char* domainName =
reinterpret_cast<char*>(Tools::AllocContiguousMemory(allocDomainNameLength));
if (domainName == nullptr) {
return;
}
bool isSuccess = true;
size_t domainNameLength = 0;
while (dnsDataLength > 0) {
const auto length = *dnsData;
if (length == 0) {
break;
}
if (length >= packetSize || length > 256) {
break;
}
// Check if domain name length is greater than domainName array
// length
if (domainNameLength + 1 > allocDomainNameLength) {
isSuccess = false;
break;
}
auto domainNameStr = *(dnsData + 1);
// 检查第一个字符是否是可读字符
if (isprint(domainNameStr) == false) {
isSuccess = false;
break;
接着,函数检查域名是否提取成功,如果提取成功,则调用DebugPrint
函数打印域名:
DebugPrint("ResolverDnsPacket: %s \n", domainName);
本段代码有一点点需要改进的地方,自行改进,不要复制粘贴,不出意外应该就看得到DNS包了:
卸载
卸载无非使用FwpsCalloutUnregisterById0
、FwpmProviderDeleteByKey0
、FwpmEngineClose0
这几个函数,不再过多介绍,主要的问题是流的删除。
如果你不删除流就卸载了,驱动的object还会在内存中,你将无法再次启动驱动
删除流的函数是FwpsFlowRemoveContext0
,
但是他会抽风。网上有两个是16年几个老外问的问题,FwpsFlowRemoveContext0
会抽风,但是没人答复要怎么办。
这边建议卸载不掉等1秒再尝试卸载,因为是驱动的卸载函数,irql不会大于APC 可以使用KeDelayExecutionThread或者用信号量等待。当然我没实际遇到过这个问题。
值得注意的
一个坑,网上介绍WFP的时候几乎没看到提及的:
https://github.com/kahotv/PrintCallouts/blob/edae8d00f350d700adffba572e9161ee37636c73/src/driver/callouts.cpp#L116