#!/usr/bin/perl # # # # Usage: # echo | nwrap -i - -p # # require 5.004; use POSIX qw(:signal_h); use Getopt::Std; use Getopt::Long; use Socket; sub exitclean { my ($msg) = @_; print "$msg\n"; exit 2; } sub debugprint { my ($msg) = @_; print STDERR "[debug] $msg\n" unless (!$OPTdebug); } sub sig_catch { my $signame = shift; print "\nRecieved a SIG$signame, exiting...\n"; exit 2; } ############################################################################### # # Function: main # Purpose: read in our host configuration file and start the scans # To-Do: Done # Status: Under Development # # Date: 04/15/99 # ############################################################################### # variables @ValidScans = ("S", "T", "N", "X", "F", "U"); $DefaultScan = "T"; $InputFile = "-"; # STDIN by default. $syncscans = 7; $ScanType = $DefaultScan; $NmapBin = "/usr/local/bin/nmap"; $SourcePort = 20; $ScanDelay = 5; # Parse our options ... &GetOptions( "debug" => \$OPTdebug, # debug flag "p=s" => \$OPTports, # ports to scan "i:s" => \$OPTinput, # input file, empty = stdin "t:i" => \$OPTthreads, # number of parallel nmap processes "s:s" => \$OPTscantype, # scan type to pass to nmap (S,N,X,U,M,F) "o:s" => \$OPToutput, # output file, defaults to ./nwrap-pid.log "rsp" => \$OPTrsp, # randomize the source port "sp:i" => \$OPTsport, # source port "delay:i" => \$OPTdelay, # minimum delay between packets to any host "nmap:s" => \$OPTnmap # location of nmap binary ); if ($OPTports eq "") { print "You must specify which ports to scan!\n"; exit(2); } if (defined($OPTsport)) { if (defined($OPTrsp)) { print STDERR "You cannot set a source port and specify random source ports.\n"; exit(2); } if ($OPTsport > 65535 || $OPTsport <= 0 ) { print STDERR "The source port must be in the range of 1 and 65535\n"; exit(2); } else { $SourcePort = $OPTsport; } } if (defined($OPTthreads)) { if ($OPTthreads > 100 || $OPTthreads <= 0) { print STDERR "You must specify a number of threads between 1 and 100.\n"; exit(2); } else { $syncscans = $OPTthreads; } } if (defined($OPTinput)) { if (! -r $OPTinput) { print STDERR "Input file is not readable.\n"; exit(2); } else { $InputFile = $OPTinput; } } if (defined($OPToutput)) { $LogFile = $OPToutput; } else { $LogFile = "nwrap-$$.log"; } if (defined($OPTscantype)) { $valid = 0; foreach $Type (@ValidScans) { if($OPTscantype eq $Type) { $valid = 1; } } if ($valid == 1) { $ScanType = $OPTscantype; } else { print STDERR "Invalid scan type '$OPTscantype', must be one of the following: @ValidScans\n"; exit(2); } } if (defined($OPTnmap)) { if ( -x $OPTnmap ) { $NmapBin = $OPTnmap; } else { print STDERR "Could not execute the nmap binary you specified '$OPTnmap'\n"; exit(2); } } if (defined($OPTdelay)) { $ScanDelay = $OPTdelay; } # Start the host/port list generation... # clear the screen... $cls = `clear`; # unbuffer STDOUT & STDERR select(STDERR); $| = 1; select(STDOUT); $| = 1; # install signal handler for each signal we want to trap @SIGNALS = ("INT", "HUP", "KILL", "TERM", "QUIT"); foreach $SIGNAL (@SIGNALS) { $SIG{$SIGNAL}=\&sig_catch; } # open our input files open (INPUT,"<".$InputFile) || exitclean("Could not open host input file: $!"); @targets = (); close(INPUT) || debugprint("close() failed on INPUT: $!"); # create a host/port list and shuffle it @targets = shuffle(\@targets); @ports = parse_ports($OPTports); @ports = shuffle(\@ports); foreach $host (@targets) { chomp($host); $host =~ s/\s+//g; if ($host ne "" && ! $host =~ m/^#/) { print "host = $host\n"; @ports = shuffle(\@ports); foreach $port (@ports) { push @output, "$host $port"; } } } @output = shuffle(\@output); debugprint("My PID = $$"); # now do something with that host/port list $counter = 0; $lastindex = $#output; $startfork = 0; $endfork = $syncscans; $forkcount = 0; $stop = 0; %hostdelays = (); %pids = (); %read_buf = (); #initialize bitmask $rin = ''; $timeout = 10; $start = time(); open (LOG, ">>" . $LogFile) || die "Could not open log file: $!"; while ($stop == 0) { debugprint("Starting spawning loop: $startfork -> $endfork"); for ($forkcount = $startfork; $forkcount <= $endfork; $forkcount++) { if ($forkcount >= ($lastindex + 1)) { $stop = 1; } else { ($nmaptarget,$nmapport) = split(/\s+/,$output[$forkcount]); $FD = "NMAP" . $forkcount; if ($OPTrsp) { srand( (time / $nmapport) * (time + $nmapport) ); ($SourcePort, $junk) = split(/\./,(rand() * 65534) + 1); } # make sure we dont send more than one packet every $ScanDelay seconds to any host. while (time() - $hostdelays{$nmaptarget} < $ScanDelay) { debugprint("Sleeping until time() - $hostdelays{$nmaptarget} < $ScanDelay (" . time() . ")\n"); sleep 1; } if ($ScanType ne "T" ) { $SPort = "-g ${SourcePort}"; } else { $SPort = ""; } die "Could not fork nmap: $!" unless defined ($pid = open($FD, "$NmapBin -s${ScanType} $SPort -m - -P0 -p $nmapport $nmaptarget|")); $hostdelays{$nmaptarget} = time(); $pids{$pid} = $FD; select($FD) || debugprint("Could not select $FD: $!"); $| = 1; # unbuffer debugprint("New FD = $FD PID = $pid"); vec($rin,fileno($FD),1) = 1; if (!$OPTdebug) { select STDOUT; print $cls; ($ctime,$junk) = split(/\./, (int(time - $start) / 60)); print "Scanning Host " . ($forkcount + 1) . " of " . ($lastindex + 1) . "\n"; print "Dst Host:\t$nmaptarget\n"; print "Dst Port:\t$nmapport\n"; print "Src Port:\t$SourcePort\n"; print "Time:\t$ctime minutes.\n"; } } } if (!$OPTdebug) { $pcount = scalar(keys(%pids)); print "\n\n$pcount Processes Launched:\nCompleted:\t"; } $startfork = $startfork + $syncscans; $endfork = $endfork + $syncscans; $forkcount = $startfork; debugprint("Completed spawning loop..."); $n = select($rout = $rin,undef,undef,$timeout); ######################################## # S while ($n != -1) { $n = select($rout = $rin,undef,undef,$timeout); @deleteQ = (); foreach $npid (keys(%pids)) { $n = select($rout = $rin,undef,undef,$timeout); # hold the pid's of the items to delete from our hash. debugprint("select = $n"); $FileNo = fileno($pids{$npid}); debugprint("FileNo: $FileNo"); debugprint("Doing if vec()"); if ($FileNo >=0 && vec($rout, $FileNo,1) == 1) { # we have new data debugprint("Trying to read data from $pids{$npid}"); $FH = $pids{$npid}; my $data = <$FH>; if (length($data) > 0) { print LOG $data; } } debugprint("Testing PID $npid for repsonse..."); if (!kill $npid => 0) { debugprint("Process $npid has exited with handle: $pids{$npid}"); close($pids{$npid}); # seems to work better if we close this... push @deleteQ, $npid; if (!$OPTdebug) { print "! "; } } } foreach $deadpid (@deleteQ) { debugprint("Removing $deadpid from pid hash"); delete $pids{$deadpid}; } } debugprint("Finished select()"); if (!$OPTdebug) { print " Done!\n"; sleep 1; } # E ######################################## } close(LOG); exit(0); # # Functions # ############################################################################### # # Function: getpppip # Purpose: crude function to get our current ppp device's ip address # To-Do: Done # Date: 04/09/99 # ############################################################################### sub getpppip { my $DATA=`ifconfig | grep P-t-P | awk \'\{ print \$2 \}\'`; my $crap; my $ip; chomp($DATA); ($crap,$ip) = split(/\:/,$DATA); return $ip; } ############################################################################### # # Function: rdecoys # Purpose: generate 6 random ip address in the same subnet as the input address # To-Do: Done # Date: 04/09/99 # ############################################################################### sub rdecoys { my ($ip) = @_; my @octets = split(/\./,$ip); my $count; my @decoys = (); my $decoy; my $output; for ($count = 0; $count < 6 ; $count++) { $decoys[$count] = int(rand()*255); } foreach $decoy (@decoys) { $output .= "$octets[0].$octets[1].$octets[2].$decoy,"; } $output .="ME"; return $output; } ############################################################################### # # Function: shuffle # Purpose: Randomize an array # To-Do: Done # Date: 04/09/99 # # Comments: This routine was pretty much ripped from 'Perl Cookbook' pg 121-122 # ############################################################################### sub shuffle { my $array = shift; my $i = scalar(@$array); my $j; foreach $item (@$array ) { --$i; $j = int rand ($i+1); next if $i == $j; @$array [$i,$j] = @$array[$j,$i]; } return @$array; } ############################################################################### # # Function: parse_ports # Purpose: Take in an nmap style port list and return an array # To-Do: Add a check to make sure all the ports added are numeric # Date: 04/09/99 # ############################################################################### sub parse_ports { my ($portstring) = @_; my $splitter = ","; my @portlist = (); my @portsplit = (); my $port; @portsplit = split($splitter,$portstring); foreach $port (@portsplit) { @range = split(/\-/,$port); if (scalar(@range) > 1) { if ($range[0] > $range[1] || $range[0] < 0 || $range[0] > 65535 || $range[1] < 0 || $range[1] > 65535) { print "Your range of $range[0] -> $range[1] is invalid!\n"; exit(1); } for ($i = $range[0]; $i < $range[1] + 1; $i++) { if ($i > 0 && $i < 65536) { push @portlist, $i; } } } else { push @portlist, $port; } } return @portlist; } # EOF