Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Suggestions & Improvements on a portscanner using IO::Socket

by czarfred (Beadle)
on May 07, 2002 at 16:07 UTC ( [id://164692]=perlquestion: print w/replies, xml ) Need Help??

czarfred has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks, I just recently started trying to learn IO::Socket and networking in Perl stuff. I did this simple port scanner (for security purposes only), I would would be most grateful if you could give me suggestions on parts of the code that need improving. Thanks!

Code below...
#!/usr/bin/perl -w # 5/6/02 # David Aslanian # nightelf@cableone.net use Getopt::Long; use IO::Socket::INET; use strict; ## options config my($port,$host,$proto,$help) = ('', '', 'tcp',''); ## proto defaults +to tcp GetOptions('ports|port=s' => \$port, 'host=s' => \$host, 'proto=s' => \$proto, 'help' => \$help) or die $!; die usage() if($help); ## validate and expand the port listing(s) in the --port option my @ports = expand_ports($port); ## validate options validate_opts($host, $proto); ######################################### ## main ######################################### my $sock; my @openports = (); my $i = -1; print "Scanning $host for open ports.....\n\n"; foreach my $cport (@ports) { $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $cport, Proto => $proto); if($sock) { $i++; $openports[$i] = $cport; } } print "Open ports on $host (using $proto):\n\t", join("\n\t", @openpor +ts), "\n"; ######################################### ## subs ######################################### sub validate_opts { my($host, $proto) = (shift, shift); ## validate $host ## no ports, proto spec, or file path in the hostname allowed ## below: this is not right #if(($host =~ /^(\w+\.)?\w+\.\w+$/i) || ($host =~ /^(\d{1,3}\.){3}(\d +{1,3})$/) || ($host =~ /^[a-z-]+$/) { # ## we're fine #} else { # die usage("Bad host specification."); #} ## validate protocol die usage("Bad protocol specification.") unless($proto =~ /^tcp$| +^udp$/); } sub expand_ports { ## input: string containing port information, c +ommas and ranges (1-10) can be used. ## output: a expanded, unique list of sorted ports ready t +o be used for scanning. my $port = shift; my @ports = (); my($from, $to); ## used for ranges (1-10) if($port =~ /^(\d+)$/) { ## just a plain port number alone $ports[0] = $1; return(@ports); } if($port =~ /^(\d+)-(\d+)$/) { ## a range of ports alone (not in +a comma-seperated list) return(@ports = ($1 .. $2)); } if($port =~ /^([\d-]+,)+([\d-]+),?$/) { ## a list possibly contai +ning ranges of ports my @splitted = split(',', $port); foreach my $splitted (@splitted) { if($splitted =~ /^(\d+)-(\d+)$/) { ## if it is a range of + ports push(@ports, ($1 .. $2)); next; } if($splitted =~ /^(\d+)$/) { push(@ports, $1); next; } ## if we get to this point in the iteration something went + wrong... die "bad port spec.\n"; } ### take out duplicate values from @ports my %seen = (); my @uniq = (); foreach my $item (@ports) { unless($seen{$item}) { ##if we get here we haven't seen it before $seen{$item} = 1; push(@uniq, $item); } } @ports = @uniq; ### sort the ports @ports = sort { $a <=> $b } @ports; ### .. and finally return the expanded, unique, sorted ports t +o the caller return @ports; } die usage("Bad or no port specification.\n"); } sub usage { my $message = shift; print "Fatal error: $message\n\n" if($message); ## only print an +error if there was one print <<ENDUSAGE; Usage: perlscan --host=<host> --ports=<ports> [--proto=tcp|udp] Options: --host, -h (ie --host=localhost) The hostname or ip adress to be scanned. --port, --ports, -po (ie --ports=1,2,3-30) The ports to be scanned. A single port can be specified (ie --port=1), or a single +range of ports can be specified (ie --ports=1-1024), or a list o +f ports and/or ranges can be specified (ie --ports=1,2,3-30). --proto, -pr (ie --proto=tcp) The protocol to use for the scan. This can be either tcp o +r udp, this option is optionable and can be omitted, in which cas +e the default protocol will be used (tcp). --help, -h (ie --help) Display this usage information. ENDUSAGE }

Edit kudra, 2002-05-07 Changed title

Replies are listed 'Best First'.
Re: Suggestions & Improvements
by rbc (Curate) on May 07, 2002 at 18:11 UTC
    I think your script is great, so forgive me if this seems
    a bit nit-picky :)

    I think you could improve your script like so ...
    change
    my $sock; my @openports = (); my $i = -1; print "Scanning $host for open ports.....\n\n"; foreach my $cport (@ports) { $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $cport, Proto => $proto); if($sock) { $i++; $openports[$i] = $cport; } }
    ... to ...
    my $sock; my @openports = (); print "Scanning $host for open ports.....\n\n"; foreach my $cport (@ports) { $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $cport, Proto => $proto); if($sock) { push ( @openports, $cport); } }
    ... that $i = -1 thing bugs me :/
    But good example of a port scanner!
Re: Suggestions & Improvements
by ferrency (Deacon) on May 07, 2002 at 19:34 UTC
    I think you can greatly simplify expand_ports.
    # Warning: untested... sub expand_ports { my $port = shift; my %ports; foreach my $range (split /\s*,\s*/, $port) { if ($range =~ /^\d+$/) { $ports{$range}++; } elsif ($range =~ /^\s*(\d+)\s*-\s*(\d+)\s*$/) { @ports{$1..$2} = ($1..$2); } else { die "Bad port spec"; } } return sort keys %ports; }

    split() doesn't care very much if there aren't any commas, so you don't need to treat the "no commas" cases as special cases. Storing the found ports as hash keys implicitly removes duplicates. In my example I don't care about the values at all, so I just use the most convenient method of setting the hash keys to any old value in each case.

    I'm sure someone will find some typos or minor syntax errors there, I just wanted to get the general idea across :) I hope this helps.

    Alan

Re: Suggestions & Improvements
by Joost (Canon) on May 07, 2002 at 16:27 UTC
    Not being a real Socket Expert (r), I can only suggest using Pod::Usage for the usage part.

    See

    perldoc perlpod perldoc Pod::Usage perldoc Getopt::Long
    -- Joost downtime n. The period during which a system is error-free and immune from user input.
Re: Suggestions & Improvements
by Freddo (Acolyte) on May 08, 2002 at 09:04 UTC
    Hello everybody,

    Could someone suggest a way to make the same script but using threads (or scanning several ports at the 'same' time)?

    I have no idea on how it should work, I looked a little at IO::Select for now... I dont need a script, i'd really like to come up with one by myself, but a few hints would be greatly appreciated.

    Thanks
    Freddo

      freddo, Use fork to handle the packet send/recv routines. elias
        As suggested, fork will do the trick. The thing you need to be careful of is not swarming the machine with forks. Up to a point, it will get the job done faster, but isn't very friendly to the other programs.

        Also, at some point you probably get diminishing returns until you have so many forks that none of them get done in a reasonable amount of time.

        So...keep a counter of how many forks you have launched and don't start new ones until some have been reaped. (see waitpid...). On my own scanner and on various stress testing clients, I have provided the number of forks to be defined at command line (or defaulted to 5 at a time). See nmap.

        IO:Select is not what you want here. Not only is it not threading or forking, but is actually used to multiplex reading and writing to/from handles that have already been established. Once you get the scanner done, write a multiplexing server for some real fun and a good introduction to network server coding. :-)

        Have a blast!

      You could take a look at Parallel-ForkManager, it does just what you want. You could easily adapt the ping example to opening multiple ports...
Re: Suggestions & Improvements on a portscanner using IO::Socket
by ChOas (Curate) on May 08, 2002 at 12:40 UTC
    Hi!

    This is OLD code, but it should do the trick aswell:
    #!/usr/local/bin/perl -w use strict; use IO::Socket; my $Usage="$0 -start [Start port] -end [End port] -address [IP address +]"; die "Usage: $Usage \n" if ((@ARGV==0)||((scalar @ARGV)%2!=0)); my %CommandLine=@ARGV; my $Start=$CommandLine{-start} || 1; my $End=$CommandLine{-end} || 1024; my $Address=$CommandLine{-address} || "127.0.0.1"; print "Scanning $Address: ports $Start\-$End\n"; local $^W=0; for (my $Port=$Start;$Port<=$End;$Port++) { print "Port $Port (",getservbyport($1,"tcp")||"Unknown",") is open\n" + if IO::Socket::INET->new("$Address:$Port"); }; $^W++;


    GreetZ!,
      ChOas

    print "profeth still\n" if /bird|devil/;
Re: Suggestions & Improvements
by czarfred (Beadle) on May 07, 2002 at 20:10 UTC
    Thanks everybody for your improvements. This site is great ... :D
      Looks good, but you can't scan UDP like that. Since UDP is stateless, opening a UDP connection just sets up the local machine to send the packets out to the udp port when you write to the socket. Run it in udp mode for any port and it will tell you the port is open. I am not aware of any methods to test udp, short of actually testing udp servers that actually respond and trying to guess what service they are and how to talk to them. If anyone has ideas here or where to look, would love to hear them myself.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://164692]
Approved by DaWolf
Front-paged by DaWolf
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (3)
As of 2024-04-25 23:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found