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

Hi,

I am trying to redirect DNS requests on a per-app basis. 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.

Do I have to somehow also hook incoming packets and restore the DNS server to the one the application expects?

Yes I’m afraid so. You may find that this restriction changes your proposed solution away from ALE_CONNECT_REDIRECT to DATAGRAM_DATA since you’ll need to be doing per packet processing in the receive path anyway

Thanks! I’m not familiar with that layer and can’t find much docs about it - can i also add a filter by appId to this layer (like i do with ALE_CONNECT_REDIRECT) so that i know that incoming packet is associated with a given app? or must i use different techniques to associate that flow with a given app so i know to rewrite the incoming packet?

Is there a valid reason for this?

The application id is an ALE level identifier, you’ll be unable to get this at the DATAGRAM_DATA / TRANSPORT / IP layers. You can however:

  1. Register a callout at an ALE layer that contains the app id
  2. Use fwpsflowassociatecontext to add the application id to a context
  3. Retreive said context in the DATAGRAM / other packet processing layers

J

Thanks man, i’ll run with that and see where i get, you’re a great help so far :slight_smile:

Hi,

I’ve been trying to get this to work, but i can’t seem to figure it out. I first tried to modify the destination IP via the CONNECT_REDIRECT layer and then restore the incoming source IP via the DATAGRAM_DATA layer, but that didn’t appear to work, the packet was still being rejected.

So i then tried to modify both the outgoing and incoming packet via the DATAGRAM_DATA layer, however i blue screen errors when rewriting the destination ip in the outgoing packet, any idea what i’m doing wrong? here’s the code (below). I admit i found the parameters for FwpsInjectTransportSendAsync a bit confusing, and was unsure exactly what to put in for the sendParams arg - though i think what i have looks right.

RtlIpv4StringToAddressExW(
    L"1.1.1.1",        // hard-coding the new (rewritten) dns server for now
    FALSE,
    &sin4.sin_addr,
    &sin4.sin_port);

RtlIpv4StringToAddressExW(
    L"8.8.8.8",        // hard-coding the original dns server for now
    FALSE,
    &origSin4.sin_addr,
    &origSin4.sin_port);

if ((Direction == FWP_DIRECTION_OUTBOUND) && (PacketInjectionState == FWPS_PACKET_NOT_INJECTED) && (RemotePort == 53) && (RemoteAddress == origSin4.sin_addr.S_un.S_addr))
{

    UINT32 IpHeaderSize = inMetaValues->ipHeaderSize;
    UINT32 TransportHeaderSize = inMetaValues->transportHeaderSize;
    UINT64 endpointHandle = inMetaValues->transportEndpointHandle;

    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))
    {
        return;
    }

    NdisAdvanceNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, FALSE, NULL);

    if (!NetBufferList)
    {
        return;
    }

    NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);

    PIPV4_HEADER IpHeader = NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), NULL, 1, 0);
    IpHeader->DestinationAddress = sin4.sin_addr.S_un.S_addr;
    UpdateIpv4HeaderChecksum(IpHeader, sizeof(IPV4_HEADER));

    FWPS_TRANSPORT_SEND_PARAMS sendParams = {
        .remoteAddress = (UCHAR*)IpHeader->DestinationAddress,
        .remoteScopeId = inMetaValues->remoteScopeId,
        .controlData = inMetaValues->controlData,
        .controlDataLength = inMetaValues->controlDataLength,
        .headerIncludeHeader = inMetaValues->headerIncludeHeader,
        .headerIncludeHeaderLength = inMetaValues->headerIncludeHeaderLength
    };

    Status = FwpsInjectTransportSendAsync(g_InjectionHandle, NULL, endpointHandle, 0, &sendParams, AF_INET, inMetaValues->compartmentId, NetBufferList, DriverDatagramDataInjectComplete, NULL);
    if (!NT_SUCCESS(Status))
    {
        FwpsFreeCloneNetBufferList(NetBufferList, 0);
    }

    classifyOut->actionType = FWP_ACTION_BLOCK;
    classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
}

Hi,

I’ve been trying to get this to work, but i can’t seem to figure it out. I first tried to modify the destination IP via the CONNECT_REDIRECT layer and then restore the incoming source IP via the DATAGRAM_DATA layer, but that didn’t appear to work, the packet was still being rejected.

So i then tried to modify both the outgoing and incoming packet via the DATAGRAM_DATA layer, however i blue screen errors when rewriting the destination ip in the outgoing packet, any idea what i’m doing wrong? here’s the code (below). I admit i found the parameters for FwpsInjectTransportSendAsync a bit confusing, and was unsure exactly what to put in for the sendParams arg - though i think what i have looks right.

RtlIpv4StringToAddressExW(
L"1.1.1.1", // hard-coding the new (rewritten) dns server for now
FALSE,
&sin4.sin_addr,
&sin4.sin_port);

RtlIpv4StringToAddressExW(
    L"8.8.8.8",        // hard-coding the original dns server for now
    FALSE,
    &origSin4.sin_addr,
    &origSin4.sin_port);

if ((Direction == FWP_DIRECTION_OUTBOUND) && (PacketInjectionState == FWPS_PACKET_NOT_INJECTED) && (RemotePort == 53) && (RemoteAddress == origSin4.sin_addr.S_un.S_addr))
{

UINT32 IpHeaderSize = inMetaValues->ipHeaderSize;
UINT32 TransportHeaderSize = inMetaValues->transportHeaderSize;
UINT64 endpointHandle = inMetaValues->transportEndpointHandle;

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))
{
    return;
}

NdisAdvanceNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, FALSE, NULL);

if (!NetBufferList)
{
    return;
}

NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);

PIPV4_HEADER IpHeader = NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), NULL, 1, 0);
IpHeader->DestinationAddress = sin4.sin_addr.S_un.S_addr;
UpdateIpv4HeaderChecksum(IpHeader, sizeof(IPV4_HEADER));

FWPS_TRANSPORT_SEND_PARAMS sendParams = {
    .remoteAddress = (UCHAR*)IpHeader->DestinationAddress,
    .remoteScopeId = inMetaValues->remoteScopeId,
    .controlData = inMetaValues->controlData,
    .controlDataLength = inMetaValues->controlDataLength,
    .headerIncludeHeader = inMetaValues->headerIncludeHeader,
    .headerIncludeHeaderLength = inMetaValues->headerIncludeHeaderLength
};

Status = FwpsInjectTransportSendAsync(g_InjectionHandle, NULL, endpointHandle, 0, &sendParams, AF_INET, inMetaValues->compartmentId, NetBufferList, DriverDatagramDataInjectComplete, NULL);
if (!NT_SUCCESS(Status))
{
    FwpsFreeCloneNetBufferList(NetBufferList, 0);
}

classifyOut->actionType = FWP_ACTION_BLOCK;
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;

}

Hi,

I’ve been trying to get this to work, but i can’t seem to figure it out. I first tried to modify the destination IP via the CONNECT_REDIRECT layer and then restore the incoming source IP via the DATAGRAM_DATA layer, but that didn’t appear to work, the packet was still being rejected.

So i then tried to modify both the outgoing and incoming packet via the DATAGRAM_DATA layer, however i blue screen errors when rewriting the destination ip in the outgoing packet, any idea what i’m doing wrong? here’s the code (below). I admit i found the parameters for FwpsInjectTransportSendAsync a bit confusing, and was unsure exactly what to put in for the sendParams arg - though i think what i have looks right.

RtlIpv4StringToAddressExW(
L"1.1.1.1", // hard-coding the new (rewritten) dns server for now
FALSE,
&sin4.sin_addr,
&sin4.sin_port);

RtlIpv4StringToAddressExW(
    L"8.8.8.8",        // hard-coding the original dns server for now
    FALSE,
    &origSin4.sin_addr,
    &origSin4.sin_port);
if ((Direction == FWP_DIRECTION_OUTBOUND) && (PacketInjectionState == FWPS_PACKET_NOT_INJECTED) && (RemotePort == 53) && (RemoteAddress == origSin4.sin_addr.S_un.S_addr))
{

    UINT32 IpHeaderSize = inMetaValues->ipHeaderSize;
    UINT32 TransportHeaderSize = inMetaValues->transportHeaderSize;
    UINT64 endpointHandle = inMetaValues->transportEndpointHandle;

    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))
    {
        return;
    }

    NdisAdvanceNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, FALSE, NULL);

    if (!NetBufferList)
    {
        return;
    }

    NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);

    PIPV4_HEADER IpHeader = NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), NULL, 1, 0);
    IpHeader->DestinationAddress = sin4.sin_addr.S_un.S_addr;
    UpdateIpv4HeaderChecksum(IpHeader, sizeof(IPV4_HEADER));

    FWPS_TRANSPORT_SEND_PARAMS sendParams = {
        .remoteAddress = (UCHAR*)IpHeader->DestinationAddress,
        .remoteScopeId = inMetaValues->remoteScopeId,
        .controlData = inMetaValues->controlData,
        .controlDataLength = inMetaValues->controlDataLength,
        .headerIncludeHeader = inMetaValues->headerIncludeHeader,
        .headerIncludeHeaderLength = inMetaValues->headerIncludeHeaderLength
    };

    Status = FwpsInjectTransportSendAsync(g_InjectionHandle, NULL, endpointHandle, 0, &sendParams, AF_INET, inMetaValues->compartmentId, NetBufferList, DriverDatagramDataInjectComplete, NULL);
    if (!NT_SUCCESS(Status))
    {
        FwpsFreeCloneNetBufferList(NetBufferList, 0);
    }

    classifyOut->actionType = FWP_ACTION_BLOCK;
    classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
}

Again, is there a valid reason why you want to make this work?

@MBond2 sure :slight_smile: I have some apps whose traffic is rrouted differently (some go via VPN, some go via physical interface) - and i want different DNS servers for each class of app. Apps that go via VPN use special smart DNS servers, apps that bypass VPN should use the existing system-configured DNS.

I’ve been trying to get this to work, but i can’t seem to figure it out. I first tried to modify the destination IP via the CONNECT_REDIRECT layer and then restore the incoming source IP via the DATAGRAM_DATA layer, but that didn’t appear to work, the packet was still being rejected.

I can categorically tell you that this approach works when implemented correctly.

however i blue screen errors when rewriting the destination ip in the outgoing packet, any idea what i’m doing wrong?

Taking what you said literally. Is your IPHeader variable NULL? Are you aware how much you should be advancing the NB? (https://docs.microsoft.com/en-us/windows-hardware/drivers/network/data-offset-positions?redirectedfrom=MSDN)

Also, you had two NetBufferList variables. This is at best confusing and at worst wrong.

@Jason_Stephenson Ah. So i shouldn’t need to rewrite both the outgoing and incoming packets at the DATAGRAM_DATA layer, i should be able to just rely on the CONNECT_REDIRECT layer to rewrite the outgoing destination IP, and then only rewrite the incoming source ip for the incoming packet?

I swear i’ve been doing just that, and the incoming rewrite appears to succeed but the dns client still rejects it.

What i want is:

  • Most apps send their traffic over the VPN
  • Some apps (specified in a list) send their traffic outside the VPN - achieved by rewriting the source ip in the BIND_REDIRECT layer
  • For the apps that send their traffic outside the VPN i also want to change the DNS servers they use, so they use DNS servers i specify

I have the first two working FINE, i can have some apps on the VPN and some apps outside the VPN - but i cannot seem to get the bypass apps using their own DNS servers

What i tried:

  • in the CONNECT_REDIRECT layer I rewrite the destination IP to the DNS server i want
  • in the DATAGRAM_DATA layer (incoming) i rewrite the source IP to the old DNS server

DNS client just rejects the packets. Could this be due to the fact that i have a VPN interface? is that somehow interfering with things?

Because the above approach wasn’t working, i switched to using per-packet rewriting in both directions at the DATAGRAM_DATA layer; but at this point i’m a bit stuck. But if you’re really saying that rewriting at the CONNECT_REDIRECT layer should work (even with my VPN interface), i should re-explore that approach rather than continuing to investigate per-packet rewriting at the DATAGRAM_DATA layer?

is the VPN interface an interface that Windows knows about?

@MBond2 yes it knows about it :slight_smile: But for some reason, the approach originally rrecommended does not appear to work - rewriting destination ip for the dns request using connect_redirect and then rewriting the incoming using per-packet rewriting at DATAGRAM_DATA just does not seem to work.

I’m using the exact approach recommended here: https://stackoverflow.com/a/62998891/66725

Well, I don’t know anything about this article, but consider what you are tring to do. Standard DNS operates on tiers of cached data. Some server is registered as authoritive for a certian domain, and all the other servers in the chain between the client and that server (including the local OS) are designed to cache an authoritive reponse until its TTL expires.

Now you want to split the DNS namespace between some apps that see space A, and other apps on the same OS that see space B. And only some times do they see space B because it depends on the link state of an interface (VPN). Now that’s going to be impossible to implement completely reliably in the general case

@MBond2 thanks :slight_smile: However i got this working on linux so i think i can get it working here too (i have also turned off dns caching)

@Jason_Stephenson you were absolutely correct the IpHeader was NULL! My next question is why is it NULL? You were also correct about the Retreat - i only needed to Retreat the size of the IpHeader as for outbound packets we get a pointer to the transport header – but i want to modify the ipheader. So i updated that code. IpHeader is still NULL though, any idea? the code is extraordinarily simple – i just want to modify the ipheader for an outbound UDP packet…

It’s been a while since I’ve been working with this in detail so I can’t immediately tell. Use ndiskd.nbl (https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-ndiskd-nbl) on the original NBL and cloned NBL and see if you can figure it out for yourself.

I also find the Retreat → Clone → Advance bit somewhat confusing but it might be ok, like I said… It’s been a while.

Well, maybe you can make it work. I’m still sure you don’t want to. Deliberatly breaking protocols is seldom good for anything

But, the next point is ‘I have done somthing on *nix, surely I can do the same on system Y’. Do you really think so? Perhaps you can make this work on TRAFX - a realtime OS designed to controll traffic lights. As a safety critical system, traffic lights have to work, and this OS has no DNS client at all, so let us all know how you go with that.

But seriously, why is this even being considered? split DNS is a problem that was solved at least 20 years ago