#!/usr/bin/perl
###############

##[ Header
#         Name:  nmbping.pl
#      Purpose:  Quick scanner for locating Samba servers
#       Author:  H D Moore <hdmoore@digitaldefense.net>
#    Copyright:  Copyright (C) 2003 Digital Defense Inc.
# Distribution:  This code may be freely redistributed.
# Release Date:  April 7, 2003
#     Revision:  1.0
#     Download:  http://www.digitaldefense.net/labs/securitytools.html
##

# use strict doesn't like the bare socket handle :(

use Socket;
use POSIX;
use Fcntl;

select(STDERR); $|++;
select(STDOUT); $|++;
local *S;

my $net = shift() || Usage();
my $dport = 137;

my %responses = ();    
my @targets = ();
my $netblock = new smbNetmask($net);

for my $ip ($netblock->enumerate())
{
    push @targets, $ip;
}

shuffle(\@targets);

socket(S, PF_INET, SOCK_DGRAM, getprotobyname('udp'));
nonblock(S);

srand(time() + $$);

my $query = 
"\x00\x00\x00\x01\x00\x00\x00\x00".
"\x00\x00\x20\x43\x4B\x41\x41\x41".
"\x41\x41\x41\x41\x41\x41\x41\x41".
"\x41\x41\x41\x41\x41\x41\x41\x41".
"\x41\x41\x41\x41\x41\x41\x41\x41".
"\x41\x41\x41\x00\x00\x21\x00\x01";

# the parent does the actual scan
# the child recv's and decodes

my $par = $$;
my $pid = fork();
my $sweep_cnt = 3;

if(!$pid)
{
    # udp is unreliable, try 3 times to make sure
    for (1 .. $sweep_cnt)
    {
        my $sweep = $_;
        my $cnt = 1;
        
        foreach my $ip (@targets)
        {
            my $xid;           
            my $dip = inet_aton($ip);
            my $paddr = sockaddr_in($dport,$dip);

            $xid = chr(int(rand() * 255)) . chr(int(rand() * 255));
            send(S, $xid . $query, 0, $paddr);
            usleep(0.001);

            print STDERR (" " x 50) . "\r[*] Sweep: $sweep/$sweep_cnt\tHost: $cnt/" . scalar(@targets) . "\r";

            $cnt++;
        }
    }
    kill("USR2", $par);
    exit(0);
    
    
} else {

    $SIG{"USR2"} = \&ShowResults;
    
    while(1)
    {
        my ($data, $paddr);
        
        if(($paddr = recv(S, $data, POSIX::BUFSIZ, 0)))
        {
            my ($port, $addr) = sockaddr_in($paddr);
            my $host = inet_ntoa($addr);
        
            if($data && ! exists($responses{$host}))
            {
                if (length($data) > 56)
                {
                    # decode nmb protocol              
                    my @resp = split(//, $data);

                    my $name_count = ord($resp[56]);
                    my $mac_start = 57 + ($name_count * 18);
                    my $mac_addr;
                    
                    for(0 .. 5)
                    { $mac_addr .= sprintf("%.2x", ord($resp[$mac_start + $_])); }
                    
                    if ($mac_addr eq "000000000000")
                    {
                        $responses{$host} = "Samba";
                    } else {
                        $responses{$host} = "Windows";
                    }
                }
  
            }
        }
    }
}   

close(S);

sub Usage {
    my ($targets) = @_;
    
    print STDERR "\n";
    print STDERR " nmbping.pl - Quick and Dirty Samba Scanner\n";
    print STDERR "============================================\n";
    print STDERR "    Usage: $0 <network address/cidr>\n";
    print STDERR "  Example: $0 192.168.10.0/24\n\n";

    exit(1);
}

sub ShowResults {
    print STDERR (" " x 60) . "\r\n";
    print STDERR "===========================\n=      Scan Results       =\n===========================\n";
    foreach my $host (keys(%responses))
    {
        print STDERR "  ";
        print STDOUT $host . " " . $responses{$host} . "\n";
    }
    print STDERR "\n";
    
    exit(0);
}

sub nonblock {
        my $socket = shift;
        my $flags;

        $flags=fcntl($socket,F_GETFL,0)
                || die "Can't get flags for socket: $!\n";
        fcntl($socket,F_SETFL,$flags|O_NONBLOCK)
                || die "Can't make socket nonblocking: $!\n";
}

sub shuffle {
    my $array = shift;
    my $i = scalar(@$array);
    my $j;
    foreach my $item (@$array )
    {
        --$i;
        $j = int rand ($i+1);
        next if $i == $j;
        @$array [$i,$j] = @$array[$j,$i];
    }
    return @$array;
}

sub usleep {
    my ($nap) = @_;
    select(undef, undef, undef, $nap);
}

# ripped from Net::Netmask
package smbNetmask;

use vars qw($VERSION);
$VERSION = 1.9;

my $remembered = {};
my %quadmask2bits;
my %imask2bits;
my %size2bits;

use vars qw($error $debug);
$debug = 1;

use strict;
use Carp;

sub new
{
        my ($package, $net, $mask) = @_;

        $mask = '' unless defined $mask;

        my $base;
        my $bits;
        my $ibase;
        undef $error;

        if ($net =~ m,^(\d+\.\d+\.\d+\.\d+)/(\d+)$,) {
                ($base, $bits) = ($1, $2);
        } elsif ($net =~ m,^(\d+\.\d+\.\d+\.\d+):(\d+\.\d+\.\d+\.\d+)$,) {
                $base = $1;
                my $quadmask = $2;
                if (exists $quadmask2bits{$quadmask}) {
                        $bits = $quadmask2bits{$quadmask};
                } else {
                        $error = "illegal netmask: $quadmask";
                }
        } elsif (($net =~ m,^\d+\.\d+\.\d+\.\d+$,)
                && ($mask =~ m,\d+\.\d+\.\d+\.\d+$,))
        {
                $base = $net;
                if (exists $quadmask2bits{$mask}) {
                        $bits = $quadmask2bits{$mask};
                } else {
                        $error = "illegal netmask: $mask";
                }
        } elsif (($net =~ m,^\d+\.\d+\.\d+\.\d+$,) &&
                ($mask =~ m,0x[a-z0-9]+,i))
        {
                $base = $net;
                my $imask = hex($mask);
                if (exists $imask2bits{$imask}) {
                        $bits = $imask2bits{$imask};
                } else {
                        $error = "illegal netmask: $mask ($imask)";
                }
        } elsif ($net =~ /^\d+\.\d+\.\d+\.\d+$/ && ! $mask) {
                ($base, $bits) = ($net, 32);
        } elsif ($net =~ /^\d+\.\d+\.\d+$/ && ! $mask) {
                ($base, $bits) = ("$net.0", 24);
        } elsif ($net =~ /^\d+\.\d+$/ && ! $mask) {
                ($base, $bits) = ("$net.0.0", 16);
        } elsif ($net =~ /^\d+$/ && ! $mask) {
                ($base, $bits) = ("$net.0.0.0", 8);
        } elsif ($net =~ m,^(\d+\.\d+\.\d+)/(\d+)$,) {
                ($base, $bits) = ("$1.0", $2);
        } elsif ($net =~ m,^(\d+\.\d+)/(\d+)$,) {
                ($base, $bits) = ("$1.0.0", $2);
        } elsif ($net eq 'default') {
                ($base, $bits) = ("0.0.0.0", 0);
        } elsif ($net =~ m,^(\d+\.\d+\.\d+\.\d+)\s*-\s*(\d+\.\d+\.\d+\.\d+)$,) {
                # whois format
                $ibase = quad2int($1);
                my $end = quad2int($2);
                $error = "illegal dotted quad: $net"
                        unless defined($ibase) && defined($end);
                my $diff = ($end || 0) - ($ibase || 0) + 1;
                $bits = $size2bits{$diff};
                $error = "could not find exact fit for $net"
                        if ! defined($bits) && ! defined($error);
        } else {
                $error = "could not parse $net $mask";
        }

        carp $error if $error && $debug;

        $ibase = quad2int($base || 0) unless $ibase;
        $error = "could not parse $net $mask"
                unless defined($ibase) || defined($error);
        $ibase &= imask($bits)
                if defined $ibase && defined $bits;

        return bless {
                'IBASE' => $ibase,
                'BITS' => $bits,
                ( $error ? ( 'ERROR' => $error ) : () ),
        };
}

sub errstr { return $error; }
sub debug  { my $this = shift; return (@_ ? $debug = shift : $debug) }

sub base { my ($this) = @_; return int2quad($this->{'IBASE'}); }
sub bits { my ($this) = @_; return $this->{'BITS'}; }
sub size { my ($this) = @_; return 2**(32- $this->{'BITS'}); }
sub next { my ($this) = @_; int2quad($this->{'IBASE'} + $this->size()); }

sub imask
{
        return (2**32 -(2** (32- $_[0])));
}

sub enumerate
{
        my ($this, $bitstep) = @_;
        $bitstep = 32 unless $bitstep;
        my $size = $this->size();
        my $increment = 2**(32-$bitstep);
        my @ary;
        my $ibase = $this->{'IBASE'};
        for (my $i = 0; $i < $size; $i += $increment) {
                push(@ary, int2quad($ibase+$i));
        }
        return @ary;
}

sub quad2int
{
        my @bytes = split(/\./,$_[0]);

        return undef unless @bytes == 4 && ! grep {!(/\d+$/ && $_<256)} @bytes;

        return unpack("N",pack("C4",@bytes));
}

sub int2quad
{
        return join('.',unpack('C4', pack("N", $_[0])));
}

 
BEGIN {
        for (my $i = 0; $i <= 32; $i++) {
                $imask2bits{imask($i)} = $i;
                $quadmask2bits{int2quad(imask($i))} = $i;
                $size2bits{ 2**(32-$i) } = $i;
        }
}
1;
