Combine and Extract Ports from Multiple NMAP Scans

A copy of this script is available here: http://wirefall.com/nmap_combined_ports.txt

#!/usr/bin/perl




####################################################################################################################
 # nmap_combined_ports
 # Union all ports of specified state(s) and output along with list of hosts containing one or more of these ports.
 #
 # Version: 0.3.2
 #
 # Author: Dustin K. Dykes
 # Last Revision: 20130814
 #
 # Tested on: Nmap 6.25 xml files
 #
 # Tested with: Perl v5.14.2 (ActiveState) on Windows 7 and Perl v5.10.1 on Ubuntu 10.04.3 LTS (BT5r3)
 #
 # Usage: perl nmap_combined_ports.pl ({ -f single Nmap xml file } || { -d directory containing Nmap xml files }) \
 #           { -o output text file } { -p port status (a || c || cf (fc) || f || o || oc (co) || of (fo)) } \
 #           { -t type (all, tcp, udp) } { -c configuration } { -h help } { -v verbose } { -x overwrite }
 #
 #####################################################################################################################




use Getopt::Std;
 use Nmap::Parser;
 #use strict;




getopts ('d:f:o:p:t:chvx');




my $after_tcp;
 my $after_udp;
 my $before_tcp;
 my $before_udp;
 my $configuration = "n";
 my $dir = "./";
 my $dir_file;
 my $file;
 my $host;
 my $ip_sorted;
 my $np;
 my $num_hosts = 0;
 my $num_tcp = 0;
 my $num_udp = 0;
 my $port_status = "a";
 my $port_open = "n";
 my $port_closed = "n";
 my $port_filtered = "n";
 my $ref;
 my $tcp_port;
 my $udp_port;
 my $tcp_status = "y";
 my $udp_status = "y";
 my $out;
 my $overwrite = "n";
 my $verbose = "n";
 my $version = "0.3.2";




my %seen;




my @files;
 my @hosts;
 my @ip;
 my @splitdata;
 my @sorted_ips;
 my @sorted_tcp;
 my @sorted_udp;
 my @tcp;
 my @udp;
 my @uniq_ips;
 my @uniq_tcp;
 my @uniq_udp;
 my @xml_file;
 my @xml_files;




my $help_msg = '
 NAME
  nmap_combined_ports.pl will parse Nmap xml file(s) and output
  the union of all ports of specified state(s) for combined hosts.

SYNOPSIS
  perl nmap_combined_ports.pl
  [-f Nmap xml file] [-d directory] [-o output text file]
  [-p port status (a/c/cf(fc)/f/o/oc(co)/of(fo))] [-t type (all/tcp/udp)]
  [-c configuration] [-h help] [-v verbose] [-x overwrite]




DESCRIPTION
  This script takes an Nmap xml file or a directory of Nmap xml files
  and outputs a combined and ordered list of discovered ports and
  combined hosts.

 The script requires the following modules:

  Nmap::Parser
   Getopt::Std

 The options are as follows:

 -c Prints configuration information to output.




 -d The target directory where the Nmap xml files are located.
   This option will search the target directory files that end with
   XML or xml extentions. Note if the directory path has a
   "SPACE" then use double quotes.




 -f Specify a single file for parsing. Note if the path or filename has
   a "SPACE" then use double quotes.

 -h Prints this message and exits.




 -o The output file name. If not provided, then the results are
   printed to the screen.




 -p Defines the status of ports returned: (a)ll, (o)pen, (c)losed,
   (f)iltered, (oc || co) open or closed, (of || fo) open or filtered,
   or (cf || fc) closed or filtered.
   If not provided, then the default setting is all.




 -t Defines the type of ports returned: (a)ll, (t)cp, or (u)dp. If not
   provided, then the default setting is all.

 -v Outputs status messages, including xml parsing errors.

 -x Overwrite output file instead of appending to it.

EXAMPLES
  The command:
  perl /path/to/script/nmap_combined_ports.pl -d /foo/bar -o out.txt

  This command will search the direcoty specified by the "-d" option
   for xml files and parse the files found. The output containing all
   hosts with open, closed, or filtered ports will be directed to the file
   specified by the "-o" option.

 The command:
  perl /path/to/script/nmap_combined_ports.pl -f /foo/bar/nmap_results.xml

  This command will parse the file specified by the "-f" option. The
   output containing all hosts with open, closed, or filtered ports will
   be directed to the screen.

 The command:
  perl /path/to/script/nmap_combined_ports.pl -f nmap_results.xml -p c

  This command will parse the file specified by the "-f" option. The
   output containing hosts with closed ports will be directed to
   the screen.
 ';




if (defined $opt_h){
  print $help_msg;exit;
  }

if (defined $opt_v){
  $verbose = "y";
  }

if (defined $opt_c){
  $configuration = "y";
  }




if (defined $opt_x){
  $overwrite = "y";
  }

if (defined $opt_o){
  if ($overwrite eq "y"){
   open($out, ">", $opt_o) || die "\nCould not open file \"$opt_o\" for overwriting\n";
   }
  else{
   open($out, ">>", $opt_o) || die "\nCould not open file \"$opt_o\" for appending\n";
   }
  }
 else{
  $out = \*STDOUT;
  }




if (defined $opt_p){
  $port_status = $opt_p;
  }




if (defined $opt_t){
  if ($opt_t eq "t"){
   $tcp_status = "y";
   $udp_status = "n";
   }
  elsif ($opt_t eq "u"){
   $tcp_status = "n";
   $udp_status = "y";
   }
  }




if($opt_d && $opt_f){
     print "Cannot assign both a file and directory option.\n\n";
     print $help_msg;exit;
  }
 elsif((!defined $opt_d) && (!defined $opt_f)){
     if ($verbose eq "y"){print "No file or directory provided. Using current directory...\n\n";}
  &enumerateFiles();
  }
 elsif(defined $opt_d){
  $dir = $opt_d;
  &enumerateFiles();
  }
 elsif(defined $opt_f){
  push @xml_files,$opt_f;
  }
 else{
     print $help_msg;exit;
  }




if ($port_status eq "c"){
  if ($verbose eq "y"){print "\nSearching for closed ports only...\n\n";}
  $port_closed = "y";
  }
 elsif ($port_status eq "f"){
  if ($verbose eq "y"){print "\nSearching for filtered ports only...\n\n";}
  $port_filtered = "y";
  }
 elsif ($port_status eq "o"){
  if ($verbose eq "y"){print "\nSearching for open ports only...\n\n";}
  $port_open = "y";
  }
 elsif (($port_status eq "oc") || ($port_status eq "co")){
  if ($verbose eq "y"){print "\nSearching for open or closed ports only...\n\n";}
  $port_open = "y";
  $port_closed = "y";
  }
 elsif (($port_status eq "of") || ($port_status eq "fo")){
  if ($verbose eq "y"){print "\nSearching for open or filtered ports only...\n\n";}
  $port_open = "y";
  $port_filtered = "y";
  }
 elsif (($port_status eq "cf") || ($port_status eq "fc")){
  if ($verbose eq "y"){print "\nSearching for closed or filtered ports only...\n\n";}
  $port_closed = "y";
  $port_filtered = "y";
  }
 else{
  if ($verbose eq "y"){print "\nSearching for all port states (open, closed, and filtered)...\n\n";}
  $port_closed = "y";
  $port_filtered = "y";
  $port_open = "y";
  }




foreach $file (@xml_files){
  $np = new Nmap::Parser;
  $np->callback( \&enumeratePorts );
  $ref = eval {$np->parsefile($file);};
   if ($verbose eq "y"){
    if($@) {print "Parse error in $file!\n\n";}
    else {print "$file parsed correctly.\n\n";}
    }
  }




if ($configuration eq "y"){
  print $out "CONFIGURATION\n\n
  SCRIPT: $0, version = $version\n
  DIRECTORY/FILE: $opt_f $dir\n
  PORT TYPE: tcp = $tcp_status, udp = $udp_status\n
  PORT STATUS: port closed = $port_closed, port filtered = $port_filtered, port open = $port_open\n\n";
  }

$num_tcp = @tcp;
 $num_udp = @udp;




if (($num_tcp > 0) || ($num_udp > 0)){
  print $out "PORTS\n\n";
  }

if ($tcp_status eq "y"){
  %seen = ();




 if ($num_tcp > 0){
   foreach $tcp_port (@tcp){
    push(@uniq_tcp, $tcp_port) unless $seen{$tcp_port}++;
    }
   @sorted_tcp = sort {$a <=> $b} @uniq_tcp;
   print $out "TCP:";
   print $out join( ',', @sorted_tcp);
   print $out "\n\n";
   }
  }

if ($udp_status eq "y"){
  %seen = ();




 if ($num_udp > 0){
   foreach $udp_port (@udp){
    push(@uniq_udp, $udp_port) unless $seen{$udp_port}++;
    }
   @sorted_udp = sort {$a <=> $b} @uniq_udp;
   print $out "UDP:";
   print $out join( ',', @sorted_udp);
   print $out "\n\n";
   }
  }




if (@hosts){
  foreach (@hosts){
   @splitdata = split(/\|/, $_);
   push(@ip,$splitdata[0]);
   }




 @sorted_ips =
   map substr($_, 4),
   sort
   map pack('C4a*', split(/\./), $_),
   @ip;




 %seen = ();
  foreach $ip_sorted (@sorted_ips){
   push(@uniq_ips, $ip_sorted) unless $seen{$ip_sorted}++;
   }




 $num_hosts = @uniq_ips;




 if ($num_hosts > 0){
   print $out "HOSTS\n\n";
   print $out join( "\n", @uniq_ips);
   print $out "\n\n";
   }
 }

sub enumerateFiles{
  opendir DIR, $dir;
     @files = readdir(DIR);
     closedir DIR;
     @xml_file = grep {$_ =~ /((xml)|(XML))$/} @files;
     foreach (@xml_file){
         $dir_file = "$dir/$_";
   push @xml_files,$dir_file;
   }
  }




sub enumeratePorts{
  $host = shift;
  if (($port_closed eq "y") && ($tcp_status eq "y")){
   $before_tcp = @tcp;
   push(@tcp,$host->tcp_closed_ports());
   $after_tcp = @tcp;
   if ($after_tcp > $before_tcp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  if (($port_closed eq "y") && ($udp_status eq "y")){
   $before_udp = @udp;
   push(@udp,$host->udp_closed_ports());
   $after_udp = @udp;
   if ($after_udp > $before_udp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  if (($port_filtered eq "y") && ($tcp_status eq "y")){
   $before_tcp = @tcp;
   push(@tcp,$host->tcp_filtered_ports());
   $after_tcp = @tcp;
   if ($after_tcp > $before_tcp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  if (($port_filtered eq "y") && ($udp_status eq "y")){
   $before_udp = @udp;
   push(@udp,$host->udp_filtered_ports());
   $after_udp = @udp;
   if ($after_udp > $before_udp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  if (($port_open eq "y") && ($tcp_status eq "y")){
   $before_tcp = @tcp;
   push(@tcp,$host->tcp_open_ports());
   $after_tcp = @tcp;
   if ($after_tcp > $before_tcp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  if (($port_open eq "y") && ($udp_status eq "y")){
   $before_udp = @udp;
   push(@udp,$host->udp_open_ports());
   $after_udp = @udp;
   if ($after_udp > $before_udp){
    push(@hosts,$host->ipv4_addr());
    }
   }
  }

 

Leave a Reply

Your email address will not be published. Required fields are marked *