Tuesday, October 9, 2012

Simple TCP Forwarder in C#

When people ask: What would I use a TCP Forwarding tool for?
Normally the answer goes like "to eavesdrop someone's connection".

Most of our connections go over SSL (at least the most important ones) and the certificate would be invalidated in case a MITM would be on going.

There are some troubleshooting situations when one would use a TCP forwarding tool as a proxy from one box to another but on what basis this technique/tool is used can vary a lot.

There are many TCP forwarding tools available on the web. However, the truth is that no one wants to get a whole solution out of a compressed file, fire Visual Studio when accessing a computer via command line (read: reverse shell here?). On top of that, I wanted to have some fun, so I decided to write one.

And how complicated is to write a TCP Forwarding tool? Or a TCP proxy if you prefer, in C#?

It takes only 66 lines of code using plain Socket class. And it's fun!
using System;
using System.Net;
using System.Net.Sockets;

namespace BrunoGarcia.Net
{
    public class TcpForwarderSlim
    {
        private readonly Socket _mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        public void Start(IPEndPoint local, IPEndPoint remote)
        {
            _mainSocket.Bind(local);
            _mainSocket.Listen(10);

            while (true)
            {
                var source = _mainSocket.Accept();
                var destination = new TcpForwarderSlim();
                var state = new State(source, destination._mainSocket);
                destination.Connect(remote, source);
                source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
            }
        }

        private void Connect(EndPoint remoteEndpoint, Socket destination)
        {
            var state = new State(_mainSocket, destination);
            _mainSocket.Connect(remoteEndpoint);
            _mainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state);
        }

        private static void OnDataReceive(IAsyncResult result)
        {
            var state = (State)result.AsyncState;
            try
            {
                var bytesRead = state.SourceSocket.EndReceive(result);
                if (bytesRead > 0)
                {
                    state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None);
                    state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
                }
            }
            catch
            {
                state.DestinationSocket.Close();
                state.SourceSocket.Close();
            }
        }

        private class State
        {
            public Socket SourceSocket { get; private set; }
            public Socket DestinationSocket { get; private set; }
            public byte[] Buffer { get; private set; }

            public State(Socket source, Socket destination)
            {
                SourceSocket = source;
                DestinationSocket = destination;
                Buffer = new byte[8192];
            }
        }
    }
}

No rocket science here: using both Asynchronous (good old CLR APM in this case) and Synchronous Socket programming, few C# lines of code with one method exposed as an entry point taking the endpoints as parameter.

When I say asynchronous and synchronous, it's because the code has three synchronous methods from the Socket class been called. The first is the Socket.Accept() which blocks the thread until a connection is received. I chose this technique so that the Start method would never return and the main thread would handle the main socket.

The second synchronous method used is Socket.Send. This method also blocks the thread (in this case will be a thread from the ThreadPool due to async I/O that fired the receive callback). When one socket receives data, it forwards to the second socket in a synchronous manner, before asynchronously restarting to receive data.

In fact, a few tests I ran (where one socket is only flushing all buffer to a second socket) using BeginSend (asynchronous Socket.Send) performed slower then the synchronous Send.

Third, the Socket.Connect() which initiates the connection with the remote endpoint, where you want the data you send to the program to be forwarded to.

Once a connection is established, APM is used with BeginReceive/EndReceive to receive data. This means each pair of sockets will receive data using APM and use the same thread from the pool that called the callback to send the data to the other socket.

Let's run it!

Previously, I just wrote a class, right? That's far from having an executable.
As I mentioned before, my idea here was not to have yet another TCP Forwarding tool on Github, codeplex or codeproject, with dozens of files, so that we could forward some data.

So I propose a small change to the code above:
Let's add a static Main method to that class and build it as a command-line application:

        static void Main(string[] args)
        {
            new TcpForwarderSlim().Start(
                new IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1])),
                new IPEndPoint(IPAddress.Parse(args[2]), int.Parse(args[3])));
        }

After that we can compile it with:

csc /o+ /debug- /out:TcpForwarder.exe /t:exe TcpForwarderSlim.cs

Even though I used C# compiler vesion: 4.0.30319.17020, code will compile just fine even with version 2.0 of the .Net Framework. The generated assembly has size: 5,632 bytes.

Let's try it out by browsing xkcd. We get their IP address and setup the tunnel:

C:\>ping xkcd.com
Pinging xkcd.com [107.6.106.82] with 32 bytes of data:

C:\>TcpForwarder.exe 127.0.0.1 12345 107.6.106.82 80

Viewing xkcd via TCP Tunnel
Great comics by the way! As usual.

Notice the address bar contains localhost:12345, which makes sense considering we set up the tunnel as: 127.0.0.1 port 12345 as local endpoint. On the bottom of the screenshot there's the Firefox extension DNS Flusher bar that shows ::1 which is the loopback address in IPv6.

If you think I might have a copy of xkcd comics number 1118 on my hard drive (the whole page, actually) and a web server binding port 12345, that's not the case. :)

Now, we have a class file with 73 lines of code (after adding the static Main method) but there's still some manual job in order to get the tunnel working. So let's try to automate this a bit more. Perhaps scripting the whole thing?!

The code can be a lot smaller if using minification. Got a nice hint on Stack overflow using Visual Studio find and replace with Regex: :Wh+

We create a script, let's say buildTcpForwarder.cmd, not forgetting to escape the > sign with ^ so that the interpreter ignores it. Note I have the path to the C# compiler (csc.exe) on my PATH environment variable

echo using System; using System.Net; using System.Net.Sockets; namespace BrunoGarcia.Net { public class TcpForwarderSlim { private readonly Socket MainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); static void Main(string[] args) { new TcpForwarderSlim().Start( new IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1])), new IPEndPoint(IPAddress.Parse(args[2]), int.Parse(args[3]))); } public void Start(IPEndPoint local, IPEndPoint remote) { MainSocket.Bind(local); MainSocket.Listen(5); while (true) { var source = MainSocket.Accept(); var destination = new TcpForwarderSlim(); var state = new State(source, destination.MainSocket); destination.Connect(remote, source); source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); } } private void Connect(EndPoint remoteEndpoint, Socket destination) { var state = new State(MainSocket, destination); MainSocket.Connect(remoteEndpoint); MainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnDataReceive, state); } private static void OnDataReceive(IAsyncResult result) { var state = (State)result.AsyncState; try { var bytesRead = state.SourceSocket.EndReceive(result); if (bytesRead ^> 0) { state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None); state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); } } catch { state.DestinationSocket.Close(); state.SourceSocket.Close(); } } private class State { public Socket SourceSocket { get; private set; } public Socket DestinationSocket { get; private set; } public byte[] Buffer { get; private set; } public State(Socket source, Socket destination) { SourceSocket = source; DestinationSocket = destination; Buffer = new byte[8192]; } } } } > source.cs

csc /o+ /debug- /out:TcpForwarder.exe /t:exe source.cs

TcpForwarder.exe %1 %2 %3 %4

Now just call the script:

C:\buildTcpForwarder.cmd 127.0.0.1 12345 107.6.106.82 80


C:\>echo using System; using System.Net; using System.Net.Sockets; na
mespace BrunoGarcia.Net { public class TcpForwarderSlim { private readonly Sock
et MainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, Protoc
olType.Tcp); static void Main(string[] args) { new TcpForwarderSlim().Start( new
 IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1])), new IPEndPoint(IPAddr
ess.Parse(args[2]), int.Parse(args[3]))); } public void Start(IPEndPoint local,
IPEndPoint remote) { MainSocket.Bind(local); MainSocket.Listen(5); while (true)
{ var source = MainSocket.Accept(); var destination = new TcpForwarderSlim(); va
r state = new State(source, destination.MainSocket); destination.Connect(remote,
 source); source.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataRec
eive, state); } } private void Connect(EndPoint remoteEndpoint, Socket destinati
on) { var state = new State(MainSocket, destination); MainSocket.Connect(remoteE
ndpoint); MainSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFl
ags.None, OnDataReceive, state); } private static void OnDataReceive(IAsyncResul
t result) { var state = (State)result.AsyncState; try { var bytesRead = state.So
urceSocket.EndReceive(result); if (bytesRead > 0) { state.DestinationSocket.Send
(state.Buffer, bytesRead, SocketFlags.None); state.SourceSocket.BeginReceive(sta
te.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state); } } catch { state.D
estinationSocket.Close(); state.SourceSocket.Close(); } } private class State {
public Socket SourceSocket { get; private set; } public Socket DestinationSocket
 { get; private set; } public byte[] Buffer { get; private set; } public State(S
ocket source, Socket destination) { SourceSocket = source; DestinationSocket = d
estination; Buffer = new byte[8192]; } } } }  1>source.cs

C:\>csc /o+ /debug- /out:TcpForwarder.exe /t:exe source.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.17020
Copyright (C) Microsoft Corporation. All rights reserved.

C:\>TcpForwarder.exe 127.0.0.1 12345 107.6.106.82 80


The source file is generated, built and the tunnel starts with the parameters we passed to the script so we can check xkcd once more via TCP Forwarding.

17 comments:

  1. how to support https browser if listening ??

    ReplyDelete
  2. If you set the connecting socket to the target host port 443 (assuming default ssl port), forwarding will happen through the forwarder process, with SSL. As I mention in the post, the certificate will be invalidated, and that's a good thing, right? You're performing a MITM. Is this what you wanted to know?

    ReplyDelete
  3. oh I see
    this is like indy(internet direct) in delphi
    just need another procedure
    thank you

    ReplyDelete
  4. Sockets are not (always) getting closed when a remote node closes the connection.
    Tested it with a web server sending response with header "Connection: close" and closing the connection after the request.
    The proxy transferred the data and the browser got it all, but since the proxy did not close the socket, browser never finished and the "onload" event did not fire (plenty of js riding on this nowadays)...

    ReplyDelete
    Replies
    1. The only case the connection is closed there is if there's an exception. And that would trigger Close on 1 socket and (if this one doesn't throw) close the second. This is not production quality code, as I mentioned in the other reply, it's a PoC. I would not go for that "instance creating itself" approach either. That was just to leave this small as possible and show the idea behind it.

      Delete
  5. What is the life cycle of the pairs of "state" objects used to create the tunnel?
    When would they get garbage collected?

    ReplyDelete
    Replies
    1. both instances are passed in to the APM methods, which keep holding their references.. reason why u can cast the IAsyncResult back into that State.
      On Main there's a while(true), nothing get's out of scope there so memory will be cleared when you close the application (there's no way out of that while(true))
      It's a PoC

      Delete
  6. Hi.. how do you establish a reconnection in case of disconnect ?

    Lets say a client is connected but the connection gets broken, how do you connect back the client ?

    ReplyDelete
  7. Hi There,

    I've used this code, and it is work fine. However, I want to reverse the connection.

    As of now, I am able to remote my Hostname (www.kpa21.com) using my localhost with port. Now, I want is, reverse connection. I want to remote my localhost using my hostname (www.kpa21.com).

    note: My internet is dynamic type and i don't whan to use my router to port4ward, i want is like NGROK.

    thanks

    ReplyDelete
  8. How can i add custom headers to every request going through the local proxy?

    ReplyDelete
  9. Thank you!
    I'd like to have a possibility to establish a reverse connection, i.e. server connects to client and then client sends requests by this channel. Is it possible?

    ReplyDelete
  10. What if I wanted to bridge this as a client -> server model to encrpyt/compress network traffic?

    ReplyDelete
  11. Starting from something like this to encrypt traffic is probably not your best bet. Take a look at spiped instead: https://github.com/Tarsnap/spiped

    I'm not aware of solution that just get plugged in to the network and compresses any type of traffic. Compressing already compressed traffic will result in even larger data at the end so something to be careful with I guess.

    ReplyDelete
  12. can this be used for tunneling rdp ?

    ReplyDelete
  13. My god this really awesome work!! very helpful for me!!Can this be used for rdp tunneling? that would be awesome !!!

    ReplyDelete

Note: Only a member of this blog may post a comment.