-
-
【WFP】域名过滤
-
发表于: 2024-6-18 11:26 3052
-
前言
近段时间做了一些与网络管控的需求。感谢@zx_838741 的入门贴:
WFP网络过滤驱动——限制网站访问
帖子中看到了这么一个思路:
就想着去实现一下。
原理
在FWPM_LAYER_DATAGRAM_DATA_V4层拦截dns流量,从而解析出域名。他在整个WFP生命周期中的位置是,如下所示:
1 2 3 4 5 6 7 8 9 10 11 | 客户端 - >服务器 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经典步骤,注册回调,添加回调,添加子层,添加过滤引擎;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 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,数据包出去的还是进来的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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流量中解析出域名
先上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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数据头:
头部详细说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 事务 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部分
1 2 3 4 5 6 | 03 www的长度 77 77 77 表示 www 06 kanxue的长度 6B 61 6E 78 75 65 表示kanxue 03 com的长度 63 6F 6D 表示com |
效果截图
其他
- 本章的方法是通过过滤dns请求的udp流程,解析出域名。后面还有可以过滤dns响应的udp数据,不仅可以解析出域名,还能解析出域名服务器返回的ip,这个后续有时间再搞吧
- 如果域名解析使用其他的怪招,比如加密等,这个方式还是获取不到域名的。
- 其他比较好用的方法:简单hook dns缓存
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-6-21 10:43
被编程两年半编辑
,原因:
赞赏
看原图
赞赏
雪币:
留言: