#!/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";
}