Example #1
0
        private void RunDiversion()
        {
            byte[] packet = new byte[65536];

            WINDIVERT_IPHDR *  ipV4Header = null;
            WINDIVERT_IPV6HDR *ipV6Header = null;
            WINDIVERT_TCPHDR * tcpHeader  = null;

            uint recvLength = 0;

            WINDIVERT_ADDRESS addr = new WINDIVERT_ADDRESS();

            NativeOverlapped recvOverlapped;

            IntPtr recvEvent      = IntPtr.Zero;
            uint   recvAsyncIoLen = 0;

            bool isLocalIpv4    = false;
            bool modifiedPacket = false;

            byte *payloadBufferPtr    = null;
            uint  payloadBufferLength = 0;

            while (m_running)
            {
                payloadBufferPtr    = null;
                payloadBufferLength = 0;

                recvLength = 0;
                addr.Reset();
                modifiedPacket = false;
                isLocalIpv4    = false;
                recvAsyncIoLen = 0;

                recvOverlapped = new NativeOverlapped();

                recvEvent = WinApiHelpers.CreateEvent(IntPtr.Zero, false, false, IntPtr.Zero);

                if (recvEvent == IntPtr.Zero)
                {
                    LoggerProxy.Default.Warn("Failed to initialize receive IO event.");
                    continue;
                }

                recvOverlapped.EventHandle = recvEvent;

                fixed(byte *inBuf = packet)
                {
                    if (!WinDivertMethods.WinDivertRecvEx(m_diversionHandle, packet, (uint)packet.Length, 0, ref addr, ref recvLength, ref recvOverlapped))
                    {
                        var error = Marshal.GetLastWin32Error();

                        // 997 == ERROR_IO_PENDING
                        if (error != 997)
                        {
                            LoggerProxy.Default.Warn(string.Format("Unknown IO error ID {0}while awaiting overlapped result.", error));
                            WinApiHelpers.CloseHandle(recvEvent);
                            continue;
                        }

                        // 258 == WAIT_TIMEOUT
                        while (WinApiHelpers.WaitForSingleObject(recvEvent, 1000) == 258)
                        {
                            ;
                        }

                        if (!WinApiHelpers.GetOverlappedResult(m_diversionHandle, ref recvOverlapped, ref recvAsyncIoLen, false))
                        {
                            LoggerProxy.Default.Warn("Failed to get overlapped result.");
                            WinApiHelpers.CloseHandle(recvEvent);
                            continue;
                        }

                        recvLength = recvAsyncIoLen;
                        WinApiHelpers.CloseHandle(recvEvent);
                    }

                    if (addr.Direction == WinDivertConstants.WINDIVERT_DIRECTION_OUTBOUND)
                    {
                        WinDivertMethods.WinDivertHelperParsePacket(inBuf, recvLength, &ipV4Header, &ipV6Header, null, null, &tcpHeader, null, &payloadBufferPtr, &payloadBufferLength);

                        if (tcpHeader != null && tcpHeader->Syn > 0)
                        {
                            // Brand new outbound connection. Grab the PID of the process holding
                            // this port and map it.
                            if (ipV4Header != null)
                            {
                                m_v4portInfo[tcpHeader->SrcPort] = GetLocalPacketInfo(tcpHeader->SrcPortNw, ipV4Header->SrcAddr);

                                if (m_v4portInfo[tcpHeader->SrcPort]?.OwnerPid == m_thisPid)
                                {
                                    // This is our process.
                                    Volatile.Write(ref m_v4ShouldFilter[tcpHeader->SrcPort], false);
                                }
                                else
                                {
                                    if (m_v4portInfo[tcpHeader->SrcPort] == null || m_v4portInfo[tcpHeader->SrcPort].OwnerPid == 4 || m_v4portInfo[tcpHeader->SrcPort].OwnerPid == 0)
                                    {
                                        // System process. Don't bother.
                                        Volatile.Write(ref m_v4ShouldFilter[tcpHeader->SrcPort], false);
                                    }
                                    else
                                    {
                                        var procPath = m_v4portInfo[tcpHeader->SrcPort] == null ? string.Empty : m_v4portInfo[tcpHeader->SrcPort].OwnerProcessPath;

                                        if (procPath.Length <= 0)
                                        {
                                            // This is something we couldn't get a handle on. Since
                                            // we can't do that that's probably a bad sign (SYSTEM
                                            // process maybe?), don't filter it.
                                            Volatile.Write(ref m_v4ShouldFilter[tcpHeader->SrcPort], true);
                                        }
                                        else
                                        {
                                            // If no firewall callback is available, just default to true, meaning we will force
                                            // this connection through the filter.
                                            var result = ConfirmDenyFirewallAccess?.Invoke(procPath);
                                            Volatile.Write(ref m_v4ShouldFilter[tcpHeader->SrcPort], result.HasValue ? result.Value : true);
                                        }
                                    }
                                }
                            }

                            if (ipV6Header != null)
                            {
                                m_v6portInfo[tcpHeader->SrcPort] = GetLocalPacketInfo(tcpHeader->SrcPortNw, ipV6Header->SrcAddr);

                                if (m_v6portInfo[tcpHeader->SrcPort]?.OwnerPid == m_thisPid)
                                {
                                    // This is our process.
                                    Volatile.Write(ref m_v6ShouldFilter[tcpHeader->SrcPort], false);
                                }
                                else
                                {
                                    if (m_v6portInfo[tcpHeader->SrcPort] == null || m_v6portInfo[tcpHeader->SrcPort].OwnerPid == 6 || m_v6portInfo[tcpHeader->SrcPort].OwnerPid == 0)
                                    {
                                        // System process. Don't bother.
                                        Volatile.Write(ref m_v6ShouldFilter[tcpHeader->SrcPort], false);
                                    }
                                    else
                                    {
                                        var procPath = m_v6portInfo[tcpHeader->SrcPort] == null ? string.Empty : m_v6portInfo[tcpHeader->SrcPort].OwnerProcessPath;

                                        if (procPath.Length <= 0)
                                        {
                                            // This is something we couldn't get a handle on. Since
                                            // we can't do that that's probably a bad sign (SYSTEM
                                            // process maybe?), don't filter it.
                                            Volatile.Write(ref m_v6ShouldFilter[tcpHeader->SrcPort], false);
                                        }
                                        else
                                        {
                                            // If no firewall callback is available, just default to true, meaning we will force
                                            // this connection through the filter.
                                            var result = ConfirmDenyFirewallAccess?.Invoke(procPath);
                                            Volatile.Write(ref m_v6ShouldFilter[tcpHeader->SrcPort], result.HasValue ? result.Value : true);
                                        }
                                    }
                                }
                            }
                        }

                        // I put the checks for ipv4 and ipv6 as a double if statement rather than an
                        // else if because I'm not sure how that would affect dual-mode sockets.
                        // Perhaps it's possible for both headers to be defined. Probably not, but
                        // since I don't know, I err on the side of awesome, or uhh, something like that.

                        // We check local packets for TOR/SOCKS packets here. However, if we don't
                        // find something we want to block on local addresses, then we want to skip
                        // these for the rest of the filtering and just let them through.

                        if (ipV4Header != null && tcpHeader != null)
                        {
                            // Let's explain the weird arcane logic here. First, we check if the
                            // current flow should even be filtered. We do this, because there's a
                            // good chance that this flow belongs to our proxy's connections, which
                            // we never want to filter. If we didn't check this, then we would end up
                            // setting the isLocalIpv4 flag to true on every single one of our
                            // proxy's connections, and clients would never get packets ever because
                            // with that flag set, the direction of the packets wouldn't be sorted.
                            //
                            // So, we check this, ensure it's actually something we want to filter.
                            // Then, we check if the packet is destined for a local address. We set
                            // the flag accordingly, and if true, then we will allow these packets to
                            // go out uninterrupted.
                            //
                            // If false, who cares. Regardless of true or false, we check to see if
                            // this is a TOR/SOCKS4/5 proxy CONNECT, and drop it if it is.
                            //
                            // Also note, by letting local/private address destined packets go, we
                            // also solve the problem of private TLS connections using private TLS
                            // self signed certs, such as logging into one's router. If we didn't do
                            // this check and let these through, we would break such connections.

                            if (Volatile.Read(ref m_v4ShouldFilter[tcpHeader->SrcPort]))
                            {
                                isLocalIpv4 = ipV4Header->DstAddr.IsPrivateIpv4Address();

                                if (isLocalIpv4)
                                {
#if !ENGINE_NO_BLOCK_TOR
                                    byte[] payload = null;
                                    if (payloadBufferLength > 0)
                                    {
                                        payload = new byte[payloadBufferLength];
                                        Marshal.Copy((IntPtr)payloadBufferPtr, payload, 0, (int)payloadBufferLength);

                                        if (payload.IsSocksProxyConnect())
                                        {
                                            LoggerProxy.Default.Info("Blocking SOCKS proxy connect.");
                                            continue;
                                        }
                                    }
#endif
                                }
                            }
                        }

                        if (!isLocalIpv4)
                        {
                            if (ipV4Header != null && tcpHeader != null)
                            {
                                if (tcpHeader->SrcPort == m_v4HttpProxyPort || tcpHeader->SrcPort == m_v4HttpsProxyPort)
                                {
                                    modifiedPacket = true;

                                    // Means that the data is originating from our proxy in response
                                    // to a client's request, which means it was originally meant to
                                    // go somewhere else. We need to reorder the data such as the src
                                    // and destination ports and addresses and divert it back
                                    // inbound, so it appears to be an inbound response from the
                                    // original external server.
                                    //
                                    // In our case, this is very easy to figure out, because we are
                                    // not yet doing any port independent protocol mapping and thus
                                    // are only diverting port 80 traffic to m_httpListenerPort, and
                                    // port 443 traffic to m_httpsListenerPort. However, XXX TODO -
                                    // When we start doing these things, we'll need a mechanism by
                                    // which to store the original port before we changed it. This
                                    // would have to be part of a proper flow tracking system.

                                    tcpHeader->SrcPort = (tcpHeader->SrcPort == m_v4HttpProxyPort) ? s_httpStandardPort : s_httpsStandardPort;
                                    addr.Direction     = WinDivertConstants.WINDIVERT_DIRECTION_INBOUND;

                                    var dstIp = ipV4Header->DstAddr;
                                    ipV4Header->DstAddr = ipV4Header->SrcAddr;
                                    ipV4Header->SrcAddr = dstIp;
                                }
                                else if (tcpHeader->DstPort == s_httpStandardPort || tcpHeader->DstPort == s_httpsStandardPort)
                                {
                                    // This means outbound traffic has been captured that we know for
                                    // sure is not coming from our proxy in response to a client, but
                                    // we don't know that it isn't the upstream portion of our proxy
                                    // trying to fetch a response on behalf of a connected client.
                                    // So, we need to check if we have a cached result for
                                    // information about the binary generating the outbound traffic
                                    // for two reasons.
                                    //
                                    // First, we need to ensure that it's not us, obviously.
                                    // Secondly, we need to ensure that the binary has been granted
                                    // firewall access to generate outbound traffic.

                                    if (Volatile.Read(ref m_v4ShouldFilter[tcpHeader->SrcPort]))
                                    {
                                        modifiedPacket = true;

                                        // If the process was identified as a process that is
                                        // permitted to access the internet, and is not a system
                                        // process or ourselves, then we divert its packets back
                                        // inbound to the local machine, changing the destination
                                        // port appropriately.
                                        var dstAddress = ipV4Header->DstAddr;

                                        ipV4Header->DstAddr = ipV4Header->SrcAddr;
                                        ipV4Header->SrcAddr = dstAddress;

                                        addr.Direction = WinDivertConstants.WINDIVERT_DIRECTION_INBOUND;

                                        tcpHeader->DstPort = (tcpHeader->DstPort == s_httpStandardPort) ? m_v4HttpProxyPort : m_v4HttpsProxyPort;
                                    }
                                }
                            }

                            // The ipV6 version works exactly the same, just with larger storage for
                            // the larger addresses. Look at the ipv4 version notes for clarification
                            // on anything.
                            if (ipV6Header != null && tcpHeader != null)
                            {
                                if (tcpHeader->SrcPort == m_v6HttpProxyPort || tcpHeader->SrcPort == m_v6HttpsProxyPort)
                                {
                                    modifiedPacket = true;

                                    tcpHeader->SrcPort = (tcpHeader->SrcPort == m_v6HttpProxyPort) ? s_httpStandardPort : s_httpsStandardPort;
                                    addr.Direction     = WinDivertConstants.WINDIVERT_DIRECTION_INBOUND;

                                    var dstIp = ipV6Header->DstAddr;
                                    ipV6Header->DstAddr = ipV6Header->SrcAddr;
                                    ipV6Header->SrcAddr = dstIp;
                                }
                                else if (tcpHeader->DstPort == s_httpStandardPort || tcpHeader->DstPort == s_httpsStandardPort)
                                {
                                    if (Volatile.Read(ref m_v6ShouldFilter[tcpHeader->SrcPort]))
                                    {
                                        modifiedPacket = true;

                                        // If the process was identified as a process that is
                                        // permitted to access the internet, and is not a system
                                        // process or ourselves, then we divert its packets back
                                        // inbound to the local machine, changing the destination
                                        // port appropriately.
                                        var dstAddress = ipV6Header->DstAddr;

                                        ipV6Header->DstAddr = ipV6Header->SrcAddr;
                                        ipV6Header->SrcAddr = dstAddress;

                                        addr.Direction = WinDivertConstants.WINDIVERT_DIRECTION_INBOUND;

                                        tcpHeader->DstPort = (tcpHeader->DstPort == s_httpStandardPort) ? m_v6HttpProxyPort : m_v6HttpsProxyPort;
                                    }
                                }
                            }
                        } // if(!isLocalIpv4)
                    }     // if (addr.Direction == WINDIVERT_DIRECTION_OUTBOUND)

                    if (modifiedPacket)
                    {
                        WinDivertMethods.WinDivertHelperCalcChecksums(packet, recvLength, 0);
                    }
                    else
                    {
                        WinDivertMethods.WinDivertHelperCalcChecksums(packet, recvLength, WinDivertHelpers.WINDIVERT_HELPER_NO_REPLACE);
                    }

                    WinDivertMethods.WinDivertSendEx(m_diversionHandle, packet, recvLength, 0, ref addr, IntPtr.Zero, IntPtr.Zero);
                } // fixed (byte* inBuf = packet)
            }     // while (m_running)
        }