Friday, December 26, 2008

Snooping on TCP connections - Putting the MAN in the Middle

It's quite often when debugging a networking application, that you have to look at the underlaying data. You might say, this is very easy with existing tools like tcpdump and windump. But let's say you have to do it on a remote server without admin rights. Suddenly the task is not so easy.

You can usually get away with telnet in case you need to simulate the client or more preferably netcat (where you can simulate the server as well). However sometimes you just want to watch the communication between two endpoints.

You basically need a tunnel, that will forward all communication and simultaneously write all the data to file / screen.

I wrote a little program for that, it's called middleman and it saves all communication in files. New file is created for every connection and raw data from both endpoints are written into this one file. If the protocol, you will be monitoring is hard to understand, you can modify the program to split the communication into two files, the program is quite simple, so it shouldn't be much of a problem:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

class Client
{
 int id;
 TcpClient local;
 TcpClient remote;
 NetworkStream localStream;
 NetworkStream remoteStream;
 FileStream file;

 public Client(TcpClient newLocal, string remoteAddress, int remotePort, int nid)
 {
  local = newLocal;
  localStream = local.GetStream();
  remote = new TcpClient(remoteAddress, remotePort);
  remoteStream = remote.GetStream();
  id = nid;

  file = new FileStream("session_" + Convert.ToString(nid) + ".txt", FileMode.Create);
 }

 public void process()
 {
  int time = 0;
  int i;
  Byte[] bytes = new Byte[4096];
  while(true)
  {
   if (localStream.DataAvailable)
   {
    if ( (i = localStream.Read(bytes, 0, bytes.Length)) != 0)
    {
     try 
     {
      if (remoteStream.CanWrite)
      {
       remoteStream.Write(bytes, 0, i);
       file.Write(bytes, 0, i);
      }
     }
     catch (Exception)
     {
      break;
     }
    }
    time = 0;
   }

   if (remoteStream.DataAvailable)
   {
    if ( (i = remoteStream.Read(bytes, 0, bytes.Length)) != 0)
    {
     try
     {
      if (localStream.CanWrite)
      {
       localStream.Write(bytes, 0, i);
       file.Write(bytes, 0, i);
      }
     }
     catch (Exception)
     {
      break;
     }
    }
    time = 0;
   }

   time++;

   Thread.Sleep(1);

   if (time > 5000)
    break;
  }
  remote.Close();
  local.Close();
  file.Close();
 }
}

class ManInTheMiddle
{
 static int Main(string[] args)
 {
  int localPort, remotePort;
  string localAddress, remoteAddress;

  switch (args.Length)
  {
   case 2 :
    remoteAddress = args[0];
    remotePort = localPort = Int32.Parse(args[1]);
    localAddress = "127.0.0.1";
    break;

   case 3:
    remoteAddress = args[0];
    remotePort = Int32.Parse(args[1]);
    localAddress = "127.0.0.1";
    localPort = Int32.Parse(args[2]);
    break;
   case 4:
    remoteAddress = args[0];
    remotePort = Int32.Parse(args[1]);
    localAddress = args[2];
    localPort = Int32.Parse(args[2]);
    break;
   default:
    Console.WriteLine("MiddleMan - Tool for snooping on TCP/IP connections");
    Console.WriteLine("");
    Console.WriteLine("Usage: middleman remote_server port");
    Console.WriteLine("Usage: middleman remote_server remote_port local_port");
    Console.WriteLine("Usage: middleman remote_server remote_port local_ip_address local_port");
    Console.WriteLine("");
    Console.WriteLine("");
    Console.WriteLine("MiddleMan starts listening as a server on local_ip_address (default 127.0.0.1)");
    Console.WriteLine("on local_port. All incoming connections to this server are forwarded");
    Console.WriteLine("to remote_server:remote_port and logged into a file.");
    Console.WriteLine("");
    Console.WriteLine("");
    return 0;
  }
  
  TcpListener server = new TcpListener(IPAddress.Parse(localAddress), localPort);
  server.Start();

  int id = 0;

  while(true)
  {
   Client client = new Client(server.AcceptTcpClient(), remoteAddress, remotePort, id++);
   Thread w = new Thread(new ThreadStart(client.process));
   w.Start();
  }
 }
}

The usage is quite simple, you specify the IP address or hostname, which you want to connect your tunnel to and the port

middleman www.google.com 80

From now on, all the communication that goes to localhost:80 will be forwarded to google:80 and will be simultaneously written to a file

In case you specify only one port (as in the example above), middleman will start listening on the same port as is the remote server listening on (in this case 80). If for whatever reason you can't use the same port on the local machine, you can start middleman like this:

middleman www.google.com 80 81

which means - forward all requests from localhost:81 to google.com:80

Simple demonstration video follows:

Download middleman.zip - 6kB

Obligatory thanks to Mark James for creating the amazing Silk icons.

1 comments:

Cody said...

Awesome :D
been looking for something like this, more of been trying to make something and having troubles :(

Hopefully I can adapt this to do what I want thanks alot.