2 # ---------------------------------------------------------------------
3 # prads-asset-report.pl
5 # Edward Fjellskaal <edwardfjellskaal@gmail.com>
6 # Kacper Wysocki <comotion@krutt.org>
8 # This script will generate a formatted report based on the data
9 # produced by Passive Real-time Asset Detection System (PRADS).
11 # Copyright (C) 2004 Matt Shelton <matt@mattshelton.com>
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 my $date = '2012-08-30';
30 # ---------------------------------------------------------------------
34 eval("use Getopt::Long"); die "[!] ERROR: Getopt::Long module must be installed!\n" if $@
;
35 eval("use Socket"); die "[!] ERROR: Socket module must be insalled!\n" if $@
;
36 use vars qw
($opt_h $opt_r $opt_w $opt_n $opt_p);
38 # Variable Declarations
39 my $report_file = "/var/log/prads-asset.log";
44 # ARP => [ $mac, $discovered, $vendor ],
46 # TCP => [ $port, $service, $app, $discovered ]
50 my %asset_storage = ();
63 $report_file = $opt_r if ($opt_r);
65 # --------------------------------------------
67 # --------------------------------------------
70 open (REPORT
, "<$report_file") or die "[!] ERROR: Unable to open $report_file - $!\n";
75 next if (/^asset,vlan,port,proto/);
78 # asset,vlan,port,proto,service,[service-info],distance,discovered
79 /^([\w\.:]+),([\d]{1,4}),([\d]{1,5}),([\d]{1,3}),(\S+?),\[(.*)\],([\d]{1,3}),(\d{10})/ && do {
80 ($sip, $vlan, $sport, $proto, $service, $s_info, $distance, $discovered) = ($1, $2, $3, $4, $5, $6, $7, $8);
83 next if not $opt_i eq $sip;
88 if ( $service =~ /SYN/ ) {
89 # 65535:128:1:48:M1460,N,N,S:.:Windows:2000 SP4, XP SP1+
90 #if ($s_info =~ /.*:.*:.*:.*:.*:.*:(.*):(.*):.*:.*:.*:.*hrs/) {
91 if ($s_info =~ /:[\d]{2,4}:\d:.*:.*:.*:(\w+):(.*):link/) {
94 #print "$os - $details\n";
96 } elsif ( $service =~ /SERVER/ || $service =~ /CLIENT/ ) {
97 $s_info =~ s/^(\w+):(.*)$/$2/;
100 # Assign this line to the asset data structure.
101 if ($service =~ /ARP/) {
103 #if ($service =~ /ARP/) {
108 push ( @
{ $asset_storage{$sip}->{"ARP"} }, [ $s_info, $discovered, $vendor ]);
110 } elsif ($proto == 1) {
112 $asset_storage{$sip}->{"ICMP"} = "ICMP";
114 } elsif ($proto == 6) {
116 if ( $service =~ /SERVER/ || $service =~ /CLIENT/ ) {
117 push (@
{$asset_storage{$sip}{"TCP"}}, [ $sport, $service, $s_info, $discovered ]);
118 } elsif ( $service =~ /SYN/ ) {
119 push (@
{$asset_storage{$sip}{"OS"}}, [ $service, $os, $details, $discovered ]);
121 } elsif ($proto == 17) {
123 if ( $service =~ /SERVER/ || $service =~ /CLIENT/ ) {
124 push (@
{$asset_storage{$sip}{"UDP"}}, [ $sport, $service, $s_info, $discovered ]);
134 # Open output file if specified on the command line.
136 open (STDOUT
, ">$opt_w") or die "[!] ERROR: $!\n";
139 # Print out this record.
142 foreach $asset (sort (keys (%asset_storage))) {
148 # Output Asset Header
149 print "$id ------------------------------------------------------\n";
150 print "IP: $asset\n";
154 if (not $opt_p or check_rfc1918
($asset)) {
155 my $ass_ip = inet_aton
($asset);
156 my ($peer_host) = gethostbyaddr($ass_ip, AF_INET
());
158 print "DNS: $peer_host";
159 my $fwd_ip = gethostbyname($peer_host.'.');
161 print ' (dnswall: no such domain)'
162 }elsif($fwd_ip ne $ass_ip){
163 print ' ('.inet_ntoa
($fwd_ip).') ';
164 my ($rev_host) = gethostbyaddr($fwd_ip, AF_INET
());
165 if($rev_host ne $peer_host){
174 my ($os,$desc,$confidence,$timestamp,$flux) = guess_asset_os
($asset);
175 print "OS: $os $desc ($confidence%) $flux\n";
176 # Output OS and details
178 #foreach $_ ( @ { $asset_storage{$asset}->{"OS"}}) {
179 # my ($date) = from_unixtime($_->[3]);
180 # printf("OS %-1s: %-1s ", $_->[0], $_->[1]);
181 # if ($_->[2] ne "unknown") {
182 # printf("- %-18s", $_->[2]);
186 # printf(" (%-19s)\n", $date);
190 # Output MAC Addresses
192 foreach $_ ( @
{ $asset_storage{$asset}->{"ARP"}}) {
194 my ($date) = from_unixtime
($_->[1]);
195 printf("MAC(s): %-18s (%-19s)\n", $_->[0], $date);
196 printf("VENDOR: %-18s\n", $_->[2]) if ($_->[2] ne "unknown");
199 my ($date) = from_unixtime
($_->[1]);
200 printf("%-09s %-18s (%-19s)\n", "", $_->[0], $date);
201 printf("%-09s %-18s\n", $_->[2]) if ($_->[2] ne "unknown");
206 if ($asset_storage{$asset}->{"ICMP"}) {
207 print "ICMP: Enabled\n";
212 if ($asset_storage{$asset}->{"TCP"}) {
213 printf("%-5s %-10s %-30s\n", "Port", "Service", "TCP-Application");
214 @sorted = sort {$$a[0] <=> $$b[0]} @
{$asset_storage{$asset}->{"TCP"}};
216 foreach $_ (@sorted) {
217 next if ($_->[3] < $timestamp);
218 printf("%-5d %-10s %-30s\n", $_->[0], $_->[1], $_->[2])
220 #if ($asset_storage{$asset}->{"TCP"}) {
225 if ($asset_storage{$asset}->{"UDP"}) {
226 printf("%-5s %-10s %-30s\n", "Port", "Service", "UDP-Application");
227 @sorted = sort {$$a[0] <=> $$b[0]} @
{$asset_storage{$asset}->{"UDP"}};
229 foreach $_ (@sorted) {
230 next if ($_->[3] < $timestamp);
231 printf("%-5d %-10s %-30s\n", $_->[0], $_->[1], $_->[2])
233 #if ($asset_storage{$asset}->{"UDP"}) {
241 # Close output file if specified on the command line.
246 sub check_last_os_switch
{
251 #foreach $OS (@ {$asset_storage{$asset}->{"OS"}}) {
252 foreach $OS (sort { $a <=> $b } (@
{$asset_storage{$asset}->{"OS"}})) {
253 if ($OS->[0] =~ /^SYN$/ ) {
255 #print "S : $OS->[0] $OS->[1] $OS->[2] $OS->[3] $syn\n";
256 $ctimestamp = $OS->[3] if ($OS->[3] > $ctimestamp);
261 foreach $OS (sort { $a <=> $b } (@
{$asset_storage{$asset}->{"OS"}})) {
262 if ($OS->[0] =~ /^SYNACK$/ ) {
264 #print "SA: $OS->[0] $OS->[1] $OS->[2] $OS->[3]\n";
265 $ctimestamp = $OS->[3] if ($OS->[3] > $ctimestamp);
269 #print "R : $ctimestamp\n";
270 push my @return, ($ctimestamp, $syn);
276 my ($OS, $DETAILS, $CONFIDENCE, $TS, $FLUX) = ("unknown", "unknown", 0, 0, 0);
277 push my @prefiltered, ($OS, $DETAILS, $CONFIDENCE, $TS, $FLUX);
281 # look for latest os switch...
282 ($TS,$FLUX) = check_last_os_switch
($asset);
284 # Lets look back the last 12 hours...
285 # The OS might have sent a synack long before a syn
286 # if thats what made the timestamp. And we also might
287 # have missed some servies :)
290 foreach $OS (@
{$asset_storage{$asset}->{"OS"}}) {
291 next if ($OS->[3] < $TS);
292 #print "OS: $OS->[0]\n";
293 if ($OS->[0] =~ /^SYNACK$/ ) {
294 $countos{ $OS->[1] }{"count"} += 4;
295 } elsif ($OS->[0] =~ /^SYN$/ ) {
296 $countos{$OS->[1]}{"count"} += 6;
297 } elsif ($OS->[0] =~ /^ACK$/ || $OS->[0] =~ /^FIN$/ || $OS->[0] =~ /^RST$/ ) {
298 $countos{$OS->[1]}{"count"} += 1;
302 my ($os, $os1, $os2);
304 for my $os (sort { $countos{$a} <=> $countos{$b} } keys %countos) {
305 next if ($os =~ /unknown/ );
313 #print "$countos{$os}{count}\t$os\n";
315 if (not defined $os1) {
316 if (not defined $os2) {
325 push my @midfiltered, ("unknown", "unknown", 0, $TS, $FLUX);
326 return @midfiltered unless $OS;
327 return @midfiltered if ($OS =~ /unknown/);
329 #if ( $countos{$os1}{count} > $countos{$os2}{count} ) {
331 #} elsif ( $countos{$os1}{count} == $countos{$os2}{count} ) {
332 # # sort on last timestamp or something...
337 foreach my $DESC (@
{$asset_storage{$asset}->{"OS"}}) {
338 next if ($DESC->[3] < $TS);
339 next if not $DESC->[1] =~ /$OS/;
340 if ($DESC->[0] =~ /^SYN$/) {
341 $DETAILS = $DESC->[2];
343 } elsif ($DESC->[0] =~ /^SYNACK$/) {
344 $DETAILS = $DESC->[2];
346 $DETAILS = $DESC->[2];
350 if (not defined $DETAILS) {
351 foreach my $DESC (@
{$asset_storage{$asset}->{"OS"}}) {
352 next if ($DESC->[3] < $TS);
353 next if not $DESC->[1] =~ /$OS/;
354 if ($DESC->[0] =~ /^RST$/) {
355 $DETAILS = $DESC->[2];
357 } elsif ($DESC->[0] =~ /^ACK$/) {
358 $DETAILS = $DESC->[2];
360 $DETAILS = $DESC->[2];
365 if ( not defined $OS ) {
366 $DETAILS = "unknown";
370 $CONFIDENCE = 20 + (10 * $countos{$OS}{count
});
371 $CONFIDENCE = 100 if $CONFIDENCE > 100;
373 push my @postfiltered, ($OS, $DETAILS, $CONFIDENCE, $TS, $FLUX);
374 return @postfiltered;
378 # --------------------------------------------
379 # FUNCTION : from_unixtime
380 # DESCRIPTION : This function will convert
381 # : a unix timestamp into a
383 # INPUT : 0 - UNIX timestamp
384 # RETURN : 0 - Formatted Time
385 # --------------------------------------------
387 my ($unixtime) = $_[0];
390 my ($sec, $min, $hour, $dmon, $mon, $year,
391 $wday, $yday, $isdst) = localtime($unixtime);
392 $time = sprintf("%04d/%02d/%02d %02d:%02d:%02d",
393 $year + 1900, $mon + 1, $dmon, $hour, $min, $sec);
398 # --------------------------------------------
399 # FUNCTION : check_rfc1918
400 # DESCRIPTION : This function will check to
401 # : see if a address is a RFC
403 # INPUT : 0 - IP Address
405 # --------------------------------------------
409 return 1 if (check_ip
($ip, "10.0.0.0/8"));
410 return 1 if (check_ip
($ip, "172.16.0.0/12"));
411 return 1 if (check_ip
($ip, "192.168.0.0/16"));
415 # --------------------------------------------
416 # FUNCTION : check_ip
417 # DESCRIPTION : This function will check to
418 # : see if a address falls
419 # : within a network CIDR block.
420 # INPUT : 0 - IP Address
423 # --------------------------------------------
428 return ($i eq $n) unless $n =~ /^(.*)\/(.*)$/;
429 return (((unpack('N',pack('C4',split(/\./,$i))) ^ unpack('N',pack('C4'
430 ,split(/\./,$1)))) & (0xFFFFFFFF << (32 - $2))) == 0);
432 # --------------------------------------------
434 # --------------------------------------------
438 -r
<file
> : PRADS Raw Report File
439 -w
<file
> : Output file
440 -i
<IP
> : Just get info
for this IP
441 -n
: Do
not convert IP addresses to names
.
442 -p
: Do
not convert RFC
1918 IP addresses to names
.
448 # --------------------------------------------
449 # FUNCTION : print_header
450 # --------------------------------------------
453 \n prads
-asset
-report
- PRADS Text Reporting Module
455 Edward Fjellskaal
<edwardfjellskaal\
@gmail.com
>
456 Kacper Wysocki
<comotion\
@krutt.org
>
458 http
://github
.com
/gamemlinux
/prads
/