[2022]终端安全: 一文从浅入深介绍windows WFP框架 huoji 流量监测,windows,WFP,DNS 2022-12-10 1124 次浏览 88 次点赞 Windows 中的 WFP 框架是一个用于网络流量管理的工具,它可以帮助用户控制数据包在网络中的流动,从而提高网络安全性和性能。本文将深入介绍 Windows 中的 WFP 框架,并通过一个简单的实例(基于 WFP 框架的 DNS 流量解析工具)让大家更好地理解它. ### WFP 框架 WFP 框架是 Windows Filtering Platform 的简称,即 Windows 过滤平台。它是一个用于网络流量管理的工具,可以让用户定义数据包进出网络的规则,从而控制数据包在网络中的流动。WFP 框架可以实现以下功能: 1. 防火墙:WFP 框架可以帮助用户构建一个防火墙,用于拦截来自不受信任的来源的数据包,并阻止它们进入网络 2. 网络流量监控:WFP 框架可以帮助用户监控网络流量,让用户可以了解网络上发生的一切事情。 3. 应用程序控制:WFP 框架可以帮助用户控制应用程序访问网络的权限,从而提高网络安全性。 ### hello world 以下代码是最小的实现代码,请注意: 1. 他不完整,比如缺少一些全局变量+没有错误异常处理 2. 没有卸载函数 不过,作为教学用途,这段代码已经足够了.作者认为,新人不需要看太多代码,过多的代码会让人感到困惑。所以,上面的代码已经是经过精简的版本了 ```cpp 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: ```cpp DEFINE_GUID(GUID_WFP_PROVIDER, 0x19b351df, 0x1055, 0x46e1, 0x93, 0x42, 0xff, 0xfe, 0x4a, 0xd0, 0xdd, 0xc1); ``` 然后,在 `MiniInstall` 函数中,我们初始化了一些变量,包括 WFP 会话,WFP 子层和 WFP 提供程序信息: ```cpp FWPM_SESSION0 wfpSession = {0}; FWPM_SUBLAYER0 wfpFirewallSubLayer = {0}; FWPM_PROVIDER0 ProviderInfo = {0}; ``` 接着,我们设置会话标志,并打开 WFP 引擎: ```cpp wfpSession.flags = FWPM_SESSION_FLAG_DYNAMIC; auto ntStatus = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &wfpSession, &gEngineHandle); ``` 在打开 WFP 引擎后,我们开始事务,并添加一个新的 WFP 提供程序: ```cpp 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; } ``` 然后,我们添加一个子层,并为子层添加一些回调函数: ```cpp 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; } ``` 最后,我们添加过滤器,并提交事务: ```cpp 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函数在上面的代码中 ```cpp mCallout.calloutKey = *calloutKey; //callout的GUID mCallout.applicableLayer = *layerKey; //层的GUID if (NT_SUCCESS(ntStatus = FwpmCalloutAdd0(EngineHandle, &mCallout, NULL, NULL)) == false) { break; } ``` 比如 为了拦截DNS流量.本例子中提供的layer Guid是: ```cpp FWPM_LAYER_DATAGRAM_DATA_V4 ``` 他在整个WFP生命周期中的位置是: ```cpp 客户端 (发送方) 执行活动打开 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过滤(用于监听握手) ```cpp 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 这是本层的函数原型: ```cpp // 新的握手,注意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 到了本层 我们应该重点关注几个信息: > 1. 进程id只有本层拿得到 2. flowHandle是贯穿整个WFP框架的.微软不会和minifilter一样给你个函数啥的自己获取.而是要你自己通过这个flowhandle获取一些信息 3. 注意IRQL.如果你的ctx结构体太大.如果太大需要用连续内存.exallocpool不保证一定连续(具体看微软文档).所以使用MmAllocateContiguousMemory ```cpp const auto flowHandle = inMetaValues->flowHandle; const auto processId = inMetaValues->processId; ``` 之后我们就可以获取一些基本的信息.包括ip地址、端口号、协议、方向等 ```cpp 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( 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函数中 ```cpp auto ntStatus = FwpsFlowAssociateContext0( flowHandle, FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4, AleConnectCalloutIdV4, reinterpret_cast(netWorkCtx)); if (NT_SUCCESS(ntStatus) == false) { break; } //TODO: 一个flow handle与ctx的hash表或者链表,以后其他层要查 ``` 此外本层还可以决定需不需要终止链接: ```cpp if (classifyOut->rights & FWPS_RIGHT_ACTION_WRITE) { classifyOut->actionType = isNeedBlock ? FWP_ACTION_BLOCK : FWP_ACTION_CONTINUE; } ``` 那么 我们的流就已经创建好了 #### 流清理/链接断开 这个不算层,这个算之前的清理.作用是清理流 ```cpp 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 ```cpp if (inMetaValues == nullptr || inFixedValues == nullptr || isNeedCleanUp) { return; } const auto ctx = getCtxByflowHandle(inMetaValues->flowHandle); if (ctx == nullptr) { return; } ``` 然后我们就可以取数据了 ```cpp const auto streamPacket = reinterpret_cast(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 进行拷贝: ```cpp streamBuffer = reinterpret_cast( 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:.. } ``` 有了网络流量包你可以拿来做任何你想做的,比如匹配到恶意代码后,终止网络连接: ```cpp 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是这样获取的: ```cpp 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; } ``` 然后你需要判断一下方向: ```cpp const auto direction = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION] .value.uint8; ``` 如果是输出方向.则需要计算出UDP头长度: ```cpp if (direction == FWP_DIRECTION_OUTBOUND) { UdpHeaderLength = inMetaValues->transportHeaderSize; if (UdpHeaderLength == 0 || UdpContentLength < UdpHeaderLength) { break; } UdpContentLength -= UdpHeaderLength; isOutBound = true; } ``` 然后通过NdisGetDataBuffer获取包的buffer: ```cpp UdpContent = Tools::AllocPoolMemory(UdpContentLength + UdpHeaderLength, NETWORK_TAG); if (UdpContent == nullptr) { break; } ndisBuffer = NdisGetDataBuffer( NetBuffer, UdpContentLength + UdpHeaderLength, UdpContent, 1, 0); if (ndisBuffer == nullptr) { break; } ``` 之后同理,可以做一些操作了: ```cpp 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的情况下 代码如下: ```cpp if (ctx->Ipv4_remotePort == 53) { NetWorkResolver::ResolveDnsPacket(buffer, bufferSize); } ``` #### 分析DNS包结构 这边主要说的是请求包.而且不考虑question count大于1的情况: ![](https://key08.com/usr/uploads/2022/12/2369672689.png) 比如查询www.baidu.com 除去包头,只看body的情况下: ```cpp 03(www的长度) 77 77 77(w的ascii码) 0x5(baidu的长度) ..... ``` #### 编写dns解包的代码 整个函数的执行过程简要地描述如下: 1. 检查 DNS 数据包的大小,如果小于 DNS 头部的大小,则退出函数。 2. 检查 DNS 头部中的标志位 IsResponse,如果为 true,则表示数据包是一个响应数据包,函数也退出。 3. 计算出 DNS 数据部分的大小,如果 DNS 数据部分的大小大于整个数据包的大小,则退出函数。 4. 为 DNS 数据部分分配内存,并解析出 DNS 查询请求包中的域名。 5. 检查域名是否提取成功,如果提取成功,则调用 DebugPrint 函数打印域名。 6. 释放分配的内存,并退出函数 对应的完整解包代码如下: ```cpp 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(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 头部的大小,则退出函数 ```cpp if (packetSize < sizeof(DNS_HEADER)) { return; } ``` 接下来,函数检查 DNS 头部中的标志位 `IsResponse`,如果为 true,则表示数据包是一个响应数据包,函数也退出: ```cpp DNS_HEADER* dnsHeader = (DNS_HEADER*)packet; if (dnsHeader->IsResponse) { return; } ``` 接下来,函数计算出 DNS 数据部分的大小,如果 DNS 数据部分的大小大于整个数据包的大小,则退出函数: ```cpp size_t dnsDataLength = packetSize - sizeof(DNS_HEADER); // Check if DNS data length is greater than packet size if (dnsDataLength >= packetSize) { return; } ``` 接下来,函数为 DNS 数据部分分配内存,并解析出 DNS 查询请求包中的域名: ```cpp // resolver DNS question packet and get domain Name char* dnsData = (char*)packet + sizeof(DNS_HEADER); char* domainName = reinterpret_cast(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 函数打印域名: ```cpp DebugPrint("ResolverDnsPacket: %s \n", domainName); ``` 本段代码有一点点需要改进的地方.自行改进 不要复制粘贴 不出意外 应该就看得到DNS包了: ![](https://key08.com/usr/uploads/2022/12/849295799.png) ### 卸载 卸载无非使用FwpsCalloutUnregisterById0、FwpmProviderDeleteByKey0、FwpmEngineClose0这几个函数.不再过多介绍 主要的问题是流的删除 **如果你不删除流就卸载了,驱动的object还会在内存中,你将无法再次启动驱动** 删除流的函数是FwpsFlowRemoveContext0,**但是他会抽风.网上有两个是16年几个老外问的问题,FwpsFlowRemoveContext0会抽风,但是没人答复要怎么办** 这边建议卸载不掉等1秒再尝试卸载,因为是驱动的卸载函数,irql不会大于APC 可以使用KeDelayExecutionThread或者用信号量等待. 当然我没实际遇到过这个问题. ### 值得注意的 一个坑,网上介绍WFP的时候几乎没看到提及的: https://github.com/kahotv/PrintCallouts/blob/edae8d00f350d700adffba572e9161ee37636c73/src/driver/callouts.cpp#L116 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 88
还不快抢沙发