windowswinapiwindows-firewallwfp

Redirect per-app DNS requests at the ALE_CONNECT_REDIRECT_V4 layer using WFP


I am trying to redirect DNS requests on a per-app basis using WFP (Windows Filtering Platform). I want to redirect to a public DNS server - not a local proxy. I have a callout driver at the ALE_CONNECT_REDIRECT_V4 layer. When I trace DNS requests at this layer, i can see them going out just fine.

However, when i rewrite the DNS server ip (using INETADDR_SET_ADDRESS) to another public server such as 1.1.1.1 (i'm only rewriting to public servers, not a local proxy) I see the rewritten DNS requests leaving on wireshark and also their responses coming in, successfully -- yet the application whose DNS i'm rewriting does not receive those DNS responses - it fails to resolve the hostname.

I have disabled the DNS cache so that the DNS requests come directly from the application, rather than the svchost.exe process.

Why is this? Do I have to somehow also hook incoming packets and restore the DNS server to the one the application expects? I'm at a loss.


Solution

  • After trying this out for myself, yes, for DNS traffic over UDP you have to intercept the inbound datagrams and modify the source address using the clone-drop-reinject method. DNS traffic over TCP is unaffected since it is a connection-based protocol whereas UDP is connectionless.

    Full minimal working driver code: https://pastebin.com/tCHqNnJH

    Relevant extract:

    void UpdateIpv4HeaderChecksum(PIPV4_HEADER IpHeader, UINT32 IpHeaderSize)
    {
        UINT32 Checksum = 0;
        UINT32 WordCount = IpHeaderSize / sizeof(UINT16);
        UINT16* Header = (UINT16*)IpHeader;
    
        IpHeader->Checksum = 0;
    
        for (UINT8 WordIndex = 0; WordIndex < WordCount; WordIndex++)
        {
            Checksum += Header[WordIndex];
        }
    
        Checksum = (Checksum & 0x0000ffff) + (Checksum >> 16);
        Checksum += (Checksum >> 16);
    
        IpHeader->Checksum = (UINT16)~Checksum;
    }
    
    void NTAPI DriverDatagramDataInjectComplete(
        _In_ void* context,
        _Inout_ NET_BUFFER_LIST* netBufferList,
        _In_ BOOLEAN dispatchLevel
    )
    {
        UNREFERENCED_PARAMETER(context);
        UNREFERENCED_PARAMETER(dispatchLevel);
    
        if (!NT_SUCCESS(netBufferList->Status))
        {
            DoTraceMessage(Default, "DriverDatagramDataInjectComplete() Status=%!STATUS!", netBufferList->Status);
        }
    
        FwpsFreeCloneNetBufferList(netBufferList, 0);
    }
    
    void NTAPI DriverDatagramDataClassify(
        _In_ const FWPS_INCOMING_VALUES* inFixedValues,
        _In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues,
        _Inout_opt_ void* layerData,
        _In_opt_ const void* classifyContext,
        _In_ const FWPS_FILTER* filter,
        _In_ UINT64 flowContext,
        _Inout_ FWPS_CLASSIFY_OUT* classifyOut
    )
    {
        UNREFERENCED_PARAMETER(layerData);
        UNREFERENCED_PARAMETER(classifyContext);
        UNREFERENCED_PARAMETER(filter);
        UNREFERENCED_PARAMETER(flowContext);
    
        UINT32 RemoteAddress = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_IP_REMOTE_ADDRESS].value.uint32;
        UINT16 RemotePort = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_IP_REMOTE_PORT].value.uint16;
        IF_INDEX InterfaceIndex = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_INTERFACE_INDEX].value.uint32;
        IF_INDEX SubInterfaceIndex = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_SUB_INTERFACE_INDEX].value.uint32;
        FWP_DIRECTION Direction = inFixedValues->incomingValue[FWPS_FIELD_DATAGRAM_DATA_V4_DIRECTION].value.uint32;
    
        FWPS_PACKET_INJECTION_STATE PacketInjectionState = FwpsQueryPacketInjectionState(g_InjectionHandle, layerData, NULL);
    
        classifyOut->actionType = FWP_ACTION_PERMIT;
        classifyOut->rights |= FWPS_RIGHT_ACTION_WRITE;
    
        if ((Direction == FWP_DIRECTION_INBOUND) && (PacketInjectionState == FWPS_PACKET_NOT_INJECTED) && (RemotePort == DNS_PORT) && (RemoteAddress == DNS_HOST_REDIRECT))
        {
            UINT32 IpHeaderSize = inMetaValues->ipHeaderSize;
            UINT32 TransportHeaderSize = inMetaValues->transportHeaderSize;
    
            PNET_BUFFER NetBuffer = NET_BUFFER_LIST_FIRST_NB((PNET_BUFFER_LIST)layerData);
            NdisRetreatNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, 0, NULL);
    
            PNET_BUFFER_LIST NetBufferList = NULL;
            NTSTATUS Status = FwpsAllocateCloneNetBufferList(layerData, NULL, NULL, 0, &NetBufferList);
            if (!NT_SUCCESS(Status))
            {
                DoTraceMessage(Default, "FwpsAllocateCloneNetBufferList() Status=%!STATUS!", Status);
            }
    
            NdisAdvanceNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, FALSE, NULL);
    
            if (!NetBufferList)
            {
                return;
            }
    
            DoTraceMessage(Default, "Modify DNS response");
    
            NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);
    
            PIPV4_HEADER IpHeader = NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), NULL, 1, 0);
            IpHeader->SourceAddress = DNS_HOST_ORIGINAL;
            UpdateIpv4HeaderChecksum(IpHeader, sizeof(IPV4_HEADER));
    
            Status = FwpsInjectTransportReceiveAsync(g_InjectionHandle, NULL, NULL, 0, AF_INET, inMetaValues->compartmentId, InterfaceIndex, SubInterfaceIndex, NetBufferList, DriverDatagramDataInjectComplete, NULL);
            if (!NT_SUCCESS(Status))
            {
                DoTraceMessage(Default, "FwpsInjectTransportReceiveAsync() Status=%!STATUS!", Status);
                FwpsFreeCloneNetBufferList(NetBufferList, 0);
            }
    
            classifyOut->actionType = FWP_ACTION_BLOCK;
            classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
            classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
        }
    }