首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
编程技术
发新帖
9
1
【WFP】域名过滤
发表于: 2024-6-18 11:26
4990
【WFP】域名过滤
编程两年半
2024-6-18 11:26
4990
# 前言 近段时间做了一些与网络管控的需求。感谢@zx_838741 的入门贴: [WFP网络过滤驱动——限制网站访问](https://bbs.kanxue.com/thread-265597.htm) 帖子中看到了这么一个思路: 就想着去实现一下。 # 原理 在**FWPM_LAYER_DATAGRAM_DATA_V4**层拦截dns流量,从而解析出域名。他在整个WFP生命周期中的位置是,如下所示: ``` 客户端->服务器 bind: FWPM_LAYER_ALE_RESOURCE_ASSIGNMENT_V4 send: FWPM_LAYER_ALE_AUTH_CONNECT_V4 Data: 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_ALE_AUTH_RECV_ACCEPT_V4 Data:FWPM_LAYER_DATAGRAM_DATA_V4 //// 这里 ``` # 代码 ### 0:WFP经典步骤,注册回调,添加回调,添加子层,添加过滤引擎; ``` NTSTATUS RegisterNetworkFilterUDP(PDEVICE_OBJECT DeviceObject) { NTSTATUS status = STATUS_SUCCESS; // open filter engine session status = FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, NULL, &EngHandle); if (!NT_SUCCESS(status)) { DbgPrint("[*] failed to open filter engine\n"); return status; } // register callout in filter engine FWPS_CALLOUT callout = {}; callout.calloutKey = EXAMPLE_CALLOUT_UDP_GUID; callout.flags = 0; callout.classifyFn = ClassifyCallback; callout.notifyFn = NotifyCallback; callout.flowDeleteFn = nullptr; status = FwpsCalloutRegister(DeviceObject, &callout, &CalloutId); if (!NT_SUCCESS(status)) { DbgPrint("[*] failed to register callout in filter engine\n"); return status; } // add callout to the system FWPM_CALLOUT calloutm = { }; calloutm.flags = 0; calloutm.displayData.name = L"example callout udp"; calloutm.displayData.description = L"example PoC callout for udp "; calloutm.calloutKey = EXAMPLE_CALLOUT_UDP_GUID; calloutm.applicableLayer = FWPM_LAYER_DATAGRAM_DATA_V4; //dns流量拦截层 status = FwpmCalloutAdd(EngHandle, &calloutm, NULL, &SystemCalloutId); if (!NT_SUCCESS(status)) { DbgPrint("[*] failed to add callout to the system \n"); return status; } // create a sublayer to group filters (not actually required FWPM_SUBLAYER sublayer = {}; sublayer.displayData.name = L"PoC sublayer example filters"; sublayer.displayData.name = L"PoC sublayer examle filters"; sublayer.subLayerKey = EXAMPLE_FILTERS_SUBLAYER_GUID; sublayer.weight = 65535; status = FwpmSubLayerAdd(EngHandle, &sublayer, NULL); if (!NT_SUCCESS(status)) { DbgPrint("[*] failed to create a sublayer\n"); return status; } // add a filter that references our callout with no conditions UINT64 weightValue = 0xFFFFFFFFFFFFFFFF; FWP_VALUE weight = {}; weight.type = FWP_UINT64; weight.uint64 = &weightValue; // process every packet , no conditions FWPM_FILTER_CONDITION conditions[1] = { 0 }; \ FWPM_FILTER filter = {}; filter.displayData.name = L"example filter callout udp"; filter.displayData.name = L"example filter calout udp"; filter.layerKey = FWPM_LAYER_DATAGRAM_DATA_V4; //dns流量拦截层 filter.subLayerKey = EXAMPLE_FILTERS_SUBLAYER_GUID; filter.weight = weight; filter.numFilterConditions = 0; filter.filterCondition = conditions; filter.action.type = FWP_ACTION_CALLOUT_INSPECTION; filter.action.calloutKey = EXAMPLE_CALLOUT_UDP_GUID; return FwpmFilterAdd(EngHandle, &filter, NULL, &FilterId); } ``` ### 1:ClassifyCallback回调中获取dns流量 没啥好说的,就是使用NdisGetDataBuffer获取dns流量存入。 回调前面有三个判断:是不是UDP包,端口是不是53,数据包出去的还是进来的 ``` VOID ClassifyCallback( const FWPS_INCOMING_VALUES* inFixedValues, const FWPS_INCOMING_METADATA_VALUES* inMetaValues, void* layerData, const void* classifyContext, const FWPS_FILTER* filter, UINT64 flowContext, FWPS_CLASSIFY_OUT* classifyOut ) { classifyOut->actionType = FWP_ACTION_PERMIT; if (inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_IP_PROTOCOL].value.uint8 != IPPROTO_UDP || inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_IP_REMOTE_PORT].value.uint16 != 53 || inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION].value.uint32 != FWP_DIRECTION_OUTBOUND) { return; } ULONG UdpHeaderLength = 0; BOOL isOutBound = FALSE; PNET_BUFFER pNetBuffer = NET_BUFFER_LIST_FIRST_NB((PNET_BUFFER_LIST)layerData); if (pNetBuffer == NULL) { goto end; } ULONG UdpContentLength = NET_BUFFER_DATA_LENGTH(pNetBuffer); if (UdpContentLength == 0) { goto end; } UINT8 direction =inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION].value.uint8; if (direction == FWP_DIRECTION_OUTBOUND) { UdpHeaderLength = inMetaValues->transportHeaderSize; if (UdpHeaderLength == 0 || UdpContentLength < UdpHeaderLength) { goto end; } UdpContentLength -= UdpHeaderLength; isOutBound = TRUE; } else { goto end; } PVOID UdpContent = ExAllocatePoolWithTag(NonPagedPool, UdpContentLength + UdpHeaderLength, 'Tsnd'); if (UdpContent == NULL) { goto end; } PVOID ndisBuffer = NdisGetDataBuffer(pNetBuffer, UdpContentLength + UdpHeaderLength, UdpContent, 1, 0); if (ndisBuffer == nullptr) { goto end; } ResolveDnsPacket(ndisBuffer, UdpContentLength + UdpHeaderLength, UdpHeaderLength); end: return; } ``` ### 2. 从DNS流量中解析出域名 先上代码 ``` VOID ResolveDnsPacket(void* packet, size_t packetSize , size_t udpHeaderLen){ PHYSICAL_ADDRESS highestAcceptableWriteBufferAddr; highestAcceptableWriteBufferAddr.QuadPart = MAXULONG64; if (packetSize < sizeof(DNS_HEADER)) { return; } DNS_HEADER* dnsHeader = (DNS_HEADER*)packet; if (dnsHeader->IsResponse) { return; } size_t dnsDataLength = packetSize - sizeof(DNS_HEADER); if (dnsDataLength >= packetSize) { return; } char* dnsData = (char*)packet + sizeof(DNS_HEADER) + udpHeaderLen; char* domainName = reinterpret_cast<char*>(MmAllocateContiguousMemory(128, highestAcceptableWriteBufferAddr)); if (domainName == NULL) { return; } memset(domainName, '\0', 128); bool isSuccess = TRUE; size_t domainNameLength = 0; while (dnsDataLength > 0) { const char length = *dnsData; if (length == 0) { break; } if (length >= packetSize || length > 256) { break; } if (domainNameLength + 1 > 128) { isSuccess = FALSE; break; } char 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; DbgPrint("ResolverDnsPacket: %s \n", domainName); } MmFreeContiguousMemory(domainName); } ``` 在前面使用NdisGetDataBuffer获取了DNS数据包,让我们看看DNS数据包长啥样,从而进一步从数据中解析出域名。 ### 3. 从DNS流量分析 ##### 获取的DNS流量数据如下所示:  ##### 前8个字节是UDP报文头:   ##### 接着就是12个字节的DNS数据头:   头部详细说明: ``` 事务ID (Transaction ID): 8F FE 表示唯一的事务ID 标志 (Flags): 01 00 QR (1位): 0,表示这是一个查询。 Opcode (4位): 0,表示这是一个标准查询。 AA (1位): 0,表示不是授权回答。 TC (1位): 0,表示消息未被截断。 RD (1位): 1,表示请求递归查询。 RA (1位): 0,表示服务器不支持递归查询。 Z (3位): 000,保留位。 RCODE (4位): 0000,表示响应码为无错误。 问题数 (Number of Questions): 00 01 表示在查询部分中有1个问题。 回答数 (Number of Answer RRs): 00 00 表示回答部分中没有资源记录。 权威部分记录数 (Number of Authority RRs): 00 00 表示权威部分中没有资源记录。 附加部分记录数 (Number of Additional RRs): 00 00 表示附加部分中没有资源记录 ``` ##### 最后包含域名的body部分  ``` 03 www的长度 77 77 77 表示 www 06 kanxue的长度 6B 61 6E 78 75 65 表示kanxue 03 com的长度 63 6F 6D 表示com ``` # 效果截图  # 其他 1. 本章的方法是通过过滤dns请求的udp流程,解析出域名。后面还有可以过滤dns响应的udp数据,不仅可以解析出域名,还能解析出域名服务器返回的ip,这个后续有时间再搞吧 2. 如果域名解析使用其他的怪招,比如加密等,这个方式还是获取不到域名的。 3. 其他比较好用的方法:[简单hook dns缓存](https://bbs.kanxue.com/thread-265875.htm)
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2024-6-21 10:43 被编程两年半编辑 ,原因:
#系统内核
#驱动开发
#开源分享
#开发技巧
收藏
・
9
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
kagayaki
你的帖子非常有用,感谢分享!
2024-6-20 16:18
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
奶白
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
2
粉丝
0
关注
私信
奶白
2
楼
.
最后于
2025-2-25 16:23 被奶白编辑 ,原因:
2025-2-24 15:12
0
MC_1037
雪 币:
315
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
16
粉丝
0
关注
私信
MC_1037
3
楼
dns加密不好搞了吧
2026-8-19 14:04
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
编程两年半
3
发帖
22
回帖
0
RANK
关注
私信
他的文章
【WFP】域名过滤
4990
【qemu虚拟化随笔一】实验环境搭建
4300
【码夫山泉系列一】windows APC注入(支持x86和x64)
6878
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部