Introduction
In this article, we will build something called a Portbinding Shell. What is a Portbinding Shell? Simply put, it is a cmd.exe shell that is bound to a port. When this program runs, it listens on a TCP port, e.g., 5555. You then PuTTY, Netcat, or Telnet to it from another PC, e.g.:
C:\telnet 192.168.0.1 5555
And immediately you get:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
You can now type any command and it will execute on the server. The C:\ prompt will appear after executing the first command. This runs on WinXP and Vista.
Using the Code
Create a Windows Application (not console application) and compile in Visual C# 2005 Express. Set the Form's opacity to 0. Insert a Form_Shown
event handler. The full source code is as below:
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;
using System.Diagnostics;
namespace PortbindingCmd
{
public partial class Form1 : Form
{
TcpListener tcpListener;
Socket socketForClient;
NetworkStream networkStream;
StreamWriter streamWriter;
StreamReader streamReader;
Process processCmd;
StringBuilder strInput;
public Form1()
{
InitializeComponent();
}
private void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
tcpListener = new TcpListener(System.Net.IPAddress.Any, 5555);
tcpListener.Start();
for(;;) RunServer();
}
private void RunServer()
{
socketForClient = tcpListener.AcceptSocket();
networkStream = new NetworkStream(socketForClient);
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
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();
strInput = new StringBuilder();
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\n");
processCmd.StandardInput.WriteLine(strInput);
if (strInput.ToString().LastIndexOf("terminate")>=0) StopServer();
if (strInput.ToString().LastIndexOf("exit") >= 0)
throw new ArgumentException();
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();
socketForClient.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) { }
}
}
}
}
PuTTY, Netcat, or Telnet to port 5555. You can then run any command from the command line. You can Telnet 127.0.0.1 if you don't have two PCs or a Virtual PC. You can disconnect and re-connect as many times as you like. To terminate the server, send the "terminate" command.
Points of Interest
Note that this is a Windows Application and not a Console Application. I chose Windows Application so that I can hide the server when it runs. A console application will pop up a DOS window.
Start a new Windows Application. Set the Form
's Opacity
property to 0. And also insert a Form_shown
event handler.
The form will flash briefly when run, that is why I set the Opacity
property to 0.
We will need these two libraries for using streams and for creating processes:
using System.IO;
using System.Diagnostics;
These are the sockets and streams that we need:
TcpListener tcpListener;
Socket socketForClient;
NetworkStream networkStream;
StreamWriter streamWriter;
StreamReader streamReader;
This object is for holding the cmd.exe process which we will be creating later:
Process processCmd;
This StringBuilder
will be used later, to store the commands that the client sends to the server:
StringBuilder strInput;
Note that we will need to insert a Form1_Shown
event handler:
private void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
tcpListener = new TcpListener(System.Net.IPAddress.Any, 5555);
tcpListener.Start();
for(;;) RunServer();
}
We could not use Form1_Load
because it will not hide our form. Set the Form
's Opacity
to 0 and insert this.Hide()
. The form will flash briefly before becoming invisible, that is why we need to set its Opacity
to 0. It will create a port on 5555 and wait for connections. It then calls the RunServer()
method:
private void RunServer()
{
...
}
The above is the main method for the server. Within it, the server waits for connections, and when a client connects, it creates a socket to communicate with the client:
socketForClient = tcpListener.AcceptSocket();
The next three lines creates the necessary streams so that the server is able to read from the socket and also write to the socket:
networkStream = new NetworkStream(socketForClient);
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
Next, we create the processes and its startup-info. The startup-info are the parameters that are used to run our process. The process is cmd.exe - the command line shell:
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();
strInput = new StringBuilder();
Note that we set CreateNoWindow
to true
so that the process will not pop-up a DOS window when run. ProcesssWindowStyle.Hidden
is optional, it makes no difference. UseShellExecute
must be set to false in order to redirect the processes' StandardOutput
, StandardInput
, and StandardError
. Once we redirect each input or output stream, pipes are created for each as shown in Fig. A below:
Read
| |
| | [StandardOut]
| |
Read --- cmd.exe -----Write
| | Write
| | [StandardIn] | |
| | | | [StandardError]
Write | |
Read
Fig. A - Linking three pipes to the cmd.exe process
However, redirecting the input and output streams is not enough. We need to handle those streams. For example, to handle the StandardOutput
and StandardError
streams, we declare a OutputDataReceived
event handler and write a method to handle it:
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) { }
}
}
Below is the main loop of the server. The try-catch
block is designed to close all cmd.exe processes, sockets, and streams cleanly whenever the client disconnects. When a client suddenly disconnects, it will throw an exception which is caught by the catch
-block. The catch
-block will call the Cleanup()
method to close all cmd.exe processes, streams, and sockets for re-use. The break
statement returns control to the for(;;)
loop in the Form_Shown
event handler, which then spawns another socket by calling RunServer()
. The client is able to re-connect again and again. Note that the program is also designed to deliberately throw an ArgumentException()
if the client sends the "exit" command. This is so that the catch
-block will catch it and Cleanup()
the cmd.exe processes, streams, and sockets for re-use.
The code in the try
-block is to read the data sent by the client and then inject it into the StandardIn
pipe of the cmd.exe process. This data is the command sent by the client. The cmd.exe process will then execute the command and then send its output to the StandardOut
pipe. This will generate an OutputDataReceived
event which will, in turn, call CmdOutputDataHandler
as discussed above. The strInput.Remove()
method is to empty the StringBuilder
before we accept new data from the client. If the client sends the "terminate" command, the program will call the StopServer()
method to stop the server and exit the program. Once the server is terminated, the client will not be able to re-connect.
while (true)
{
try
{
strInput.Append(streamReader.ReadLine());
strInput.Append("\n");
processCmd.StandardInput.WriteLine(strInput);
if (strInput.ToString().LastIndexOf("terminate")>=0) StopServer();
if (strInput.ToString().LastIndexOf("exit") >= 0)
throw new ArgumentException();
strInput.Remove(0,strInput.Length);
}
catch (Exception err)
{
Cleanup();
break;
}
}
Updates
At all times, the source code above is the latest code. The source code download is also the latest version. Last update was on August 27, 2007. See the comments on the header of the source code for update history.