#!/usr/bin/perl -w
#
#############################################################################
#  _____ _   _ _____ _______   ___  A (fast?) banner grabber, written in Perl
# |_   _| | | |_   _|_   _\ \ / / | ...and about as subtle as an elephant.
#   | | | |_| | | |   | |  \ V /|_| Author : David Ramsden
#   |_|  \___/  |_|   |_|   |_| (_) Website: http://david.hexstream.co.uk/
#  -------------------- Version 0.2
#############################################################################
#
### License (distributed under the zlib license) ###
# Copyright (c) 2006 David Ramsden
#
# This software is provided 'as-is', without any express or implied warranty.
# In no event will the authors be held liable for any damages arising from
# the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
#    1. The origin of this software must not be misrepresented; you must not
#	claim that you wrote the original software. If you use this software
#	in a product, an acknowledgment in the product documentation would be
#	appreciated but is not required.
#
#    2. Altered source versions must be plainly marked as such, and must not
#	be misrepresented as being the original software.
#
#    3. This notice may not be removed or altered from any source
#	distribution.
#############################################################################

use strict;
use POSIX;
use IO::Socket;
use Fcntl;

# Keep an eye on our children!
$SIG{CHLD} = \&reaper;

# Initialise random seed.
srand;

# Get command line options.
my $victim     = shift;
my $port_start = shift;
my $port_end   = shift;

# Check all command line options have been specified.
if (!defined($victim) || !defined($port_start) || !defined($port_end))
{
	print "Usage  : $0 <victim address> <port start> <port end>\n";
	print "Example: $0 127.0.0.1 1 1023\n";
	
	exit 1;
}

print <<EOF;
 _____ _   _ _____ _______   ___       *** Starting to grab banners! ***
|_   _| | | |_   _|_   _\\ \\ / / |  A (fast?) banner grabber, written in Perl
  | | | |_| | | |   | |  \\ V /|_|  ...and about as subtle as an elephant.
  |_|  \\___/  |_|   |_|   |_| (_)  Author: David Ramsden
--------------------- Version 0.2  Website: http://david.hexstream.co.uk/
EOF

# Mix up the order of the ports to connect to.
my @ports = &randomise_ports();

# Go through each port (now in a random order).
foreach my $port (@ports)
{
	print ".";

	# ./~ Fork bomb, fork bomb. You're my fork bomb. ./~
	if (my $pid = fork)
	{
		# Don't worry about waiting for the child.
		# This will create lots of connections which is fast
		# but we might end up with zombie processes.
	}
	elsif (defined $pid)
	{
		# Now we're forked, go and connect to the victim's port.
		&do_grab($victim, $port);
	}
	else
	{
		die "\n\nforking error: $!\n";
	}

	# Sleep for a very small, random, amount of time.
	# If we don't, it will only fork one process at a time.
	select undef, undef, undef, rand();
}

print "\n";
print "Note: There are probably still child processes working.\n";
print "      Check the process list before viewing results.\n";

exit 0;

## Create a random array of the port numbers.
sub randomise_ports()
{
	# Define local variables.
	my @ports = ();
	my @new = ();

	# First get the from and to ports in to an array.
	# Then shuffle the elements in to random positions.
	for(my $port = $port_start; $port <= $port_end; $port++) { push @ports, $port };
	while(@ports) {	push @new, splice(@ports, rand @ports, 1) };

	return @new;
}

## Connect to victim's port and grab the banner.
sub do_grab()
{
	# Keep an eye on our children!
	$SIG{CHLD} = \&reaper;

	# Define local variables.
	my $victim = shift;
	my $port   = shift;

	open LOG, ">>$victim.log.txt";

	# Initiate the connection.
	my $socket = new IO::Socket::INET (PeerAddr => $victim,
					   PeerPort => $port,
					   Proto    => 'tcp',
					   Timeout  => 4);

	# Check if the connection was established.
	if (!defined($socket))
	{
		print LOG "Port $port: $!\n";

		exit 1;
	}
	else
	{
		print LOG "Port $port connected.\n";
	}

	close LOG;

	# Set socket to nonblocking.
	&nonblock($socket);

	# If this child process is still running after 40 seconds
	# then "poke" the connection to get some form of response.
	# We send a HTTP HEAD request, in case it's a web server
	# waiting for a request. Otherwise we may or may not get
	# anything back. Hence the second alarm signal that will
	# kill the connection after another 5 seconds.
	$SIG{ALRM} = sub {
		alarm(0);
		print $socket "HEAD / HTTP/1.0\n\n";
		$SIG{ALRM} = sub {
			close $socket;
			exit 0;
		};
		alarm(5);
	};
	alarm(40);

	# Grab the banner.
	my $rx = 0;
	my $data = "";
	my $banner = "";
	while(1)
	{
		# Read socket.
		$socket->recv($data, POSIX::BUFSIZ, 0);

		# Data was read.
		if (length($data))
		{
			$rx = 1;
			$banner .= $data;
		}
		elsif (!length($data) && $rx == 1)
		{
			# We've recieved data, at some stage and
			# now we're not getting anything.
			# So assume we're done reading everything.
			last;
		}

		# Sleep for 500ms before trying a read again.
		select undef, undef, undef, 0.50;
	}

	close $socket;

	if (defined($banner))
	{
		open BANNERS, ">>$victim.banners.txt";
		print BANNERS "Banner grabbed for port $port:\n";
		print BANNERS "-"x77 . "\n";
		print BANNERS $banner;
		print BANNERS "-"x77 . "\n\n";
		close BANNERS;
	}

	# Exit here, to kill the child process we forked.
	exit 0;
}

## Clean up after the child process is stopped or terminated.
sub reaper()
{
	wait;
}

## Sets a socket in to nonblocking operation.
sub nonblock()
{
	# Define local variables.
	my $socket = shift;

	# Get the current descriptor flags.
	my $flags = fcntl($socket, F_GETFL, 0)
		or die "Can't get flags for socket: $!\n";

	# Add the O_NONBLOCK flag to the socket.
	fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
		or die "Can't make socket nonblocking: $!\n";
}
