#!/usr/bin/perl # # $Id: sinfp3.pl 2214 2012-12-02 14:38:04Z gomor $ # use strict; use warnings; use Getopt::Long; use Pod::Usage; my %lopts = (); GetOptions( "version" => \$lopts{version}, "help" => \$lopts{help}, # Global options "target=s" => \$lopts{target}, "port=s" => \$lopts{port}, "port-src=i" => \$lopts{port_src}, "6" => \$lopts{6}, "jobs=i" => \$lopts{jobs}, "dns-reverse" => \$lopts{dns_reverse}, "device=s" => \$lopts{device}, "thread" => \$lopts{thread}, "retry=i" => \$lopts{retry}, "timeout=i" => \$lopts{timeout}, "pps=i" => \$lopts{pps}, "ip-src=s" => \$lopts{ip_src}, "ip6-src=s" => \$lopts{ip6_src}, "mac-src=s" => \$lopts{mac_src}, "subnet-src=s" => \$lopts{subnet_src}, "subnet6-src=s" => \$lopts{subnet6_src}, "ip-gateway=s" => \$lopts{ip_gateway}, "ip6-gateway=s" => \$lopts{ip6_gateway}, "mac-gateway=s" => \$lopts{mac_gateway}, "verbose=i" => \$lopts{verbose}, "quiet" => \$lopts{quiet}, "threshold=i" => \$lopts{threshold}, "best-score" => \$lopts{best_score}, "passive" => \$lopts{passive}, # Generic options "input=s" => \$lopts{input}, "input-arg=s%" => \$lopts{input_arg}, "db=s" => \$lopts{db}, "db-arg=s%" => \$lopts{db_arg}, "mode=s" => \$lopts{mode}, "mode-arg=s%" => \$lopts{mode_arg}, "search=s" => \$lopts{search}, "search-arg=s%" => \$lopts{search_arg}, "output=s" => \$lopts{output}, "output-arg=s%" => \$lopts{output_arg}, # Plugin-loading options "input-null" => \$lopts{input_null}, "input-arpdiscover" => \$lopts{input_arpdiscover}, "input-pcap" => \$lopts{input_pcap}, "input-synscan" => \$lopts{input_synscan}, "input-ipport" => \$lopts{input_ipport}, "input-sniff" => \$lopts{input_sniff}, "input-signature" => \$lopts{input_signature}, "input-signaturep" => \$lopts{input_signaturep}, "input-connect" => \$lopts{input_connect}, "input-server" => \$lopts{input_server}, "mode-null" => \$lopts{mode_null}, "mode-active" => \$lopts{mode_active}, "mode-passive" => \$lopts{mode_passive}, "db-null" => \$lopts{db_null}, "db-sinfp3" => \$lopts{db_sinfp3}, "search-null" => \$lopts{search_null}, "search-active" => \$lopts{search_active}, "search-passive" => \$lopts{search_passive}, "log-null" => \$lopts{log_null}, "log-console" => \$lopts{log_console}, "output-null" => \$lopts{output_null}, "output-console" => \$lopts{output_console}, "output-csv" => \$lopts{output_csv}, "output-dumper" => \$lopts{output_dumper}, "output-osonly" => \$lopts{output_osonly}, "output-osversionfamily" => \$lopts{output_osversionfamily}, "output-pcap" => \$lopts{output_pcap}, "output-ubigraph" => \$lopts{output_ubigraph}, "output-simple" => \$lopts{output_simple}, "output-client" => \$lopts{output_client}, # Plugin-specific options "db-update" => \$lopts{db_update}, "db-file=s" => \$lopts{db_file}, "sniff-promiscuous" => \$lopts{sniff_promiscuous}, "pcap-anonymize" => \$lopts{pcap_anonymize}, "pcap-append" => \$lopts{pcap_append}, "pcap-filter=s" => \$lopts{pcap_filter}, "pcap-file=s" => \$lopts{pcap_file}, "active-1" => \$lopts{active_1}, "active-2" => \$lopts{active_2}, "active-3" => \$lopts{active_3}, "synscan-fingerprint" => \$lopts{synscan_fingerprint}, "csv-file=s" => \$lopts{csv_file}, ) or pod2usage(2); if ($lopts{version}) { use Class::Gomor; use DBD::SQLite; use Net::Libdnet; use Net::Frame; use Net::Frame::Device; use Net::Frame::Dump; use Net::Frame::Layer::ICMPv6; use Net::Frame::Layer::IPv6; use Net::Frame::Layer::SinFP3; use Net::Frame::Simple; use Net::Write; print "\n -- SinFP3 - $Net::SinFP3::VERSION --\n". "\n". ' $Id: sinfp3.pl 2214 2012-12-02 14:38:04Z gomor $'."\n". "\n -- Perl modules --\n\n". " o Class::Gomor - $Class::Gomor::VERSION\n". " o DBD::SQLite - $DBD::SQLite::VERSION\n". " o Net::Libdnet - $Net::Libdnet::VERSION\n". " o Net::Frame - $Net::Frame::VERSION\n". " o Net::Frame::Device - $Net::Frame::Device::VERSION\n". " o Net::Frame::Dump - $Net::Frame::Dump::VERSION\n". " o Net::Frame::Layer::ICMPv6 - $Net::Frame::Layer::ICMPv6::VERSION\n". " o Net::Frame::Layer::IPv6 - $Net::Frame::Layer::IPv6::VERSION\n". " o Net::Frame::Layer::SinFP3 - $Net::Frame::Layer::SinFP3::VERSION\n". " o Net::Frame::Simple - $Net::Frame::Simple::VERSION\n". " o Net::Write - $Net::Write::VERSION\n". "\n"; exit(0); } elsif ($lopts{help}) { pod2usage(1); } # Load required modules use Net::SinFP3; use Net::SinFP3::Global; use Net::SinFP3::Log::Null; use Net::SinFP3::Log::Console; use Net::SinFP3::Input::Null; use Net::SinFP3::Input::IpPort; use Net::SinFP3::Input::Pcap; use Net::SinFP3::Input::ArpDiscover; use Net::SinFP3::Input::Server; use Net::SinFP3::Input::SynScan; use Net::SinFP3::Input::Sniff; use Net::SinFP3::Input::Signature; use Net::SinFP3::Input::SignatureP; use Net::SinFP3::Input::Connect; use Net::SinFP3::DB::Null; use Net::SinFP3::DB::SinFP3; use Net::SinFP3::Mode::Null; use Net::SinFP3::Mode::Active; use Net::SinFP3::Mode::Passive; use Net::SinFP3::Search::Null; use Net::SinFP3::Search::Active; use Net::SinFP3::Search::Passive; use Net::SinFP3::Output::Null; use Net::SinFP3::Output::Client; use Net::SinFP3::Output::Console; use Net::SinFP3::Output::Simple; use Net::SinFP3::Output::CSV; use Net::SinFP3::Output::Dumper; use Net::SinFP3::Output::OsOnly; use Net::SinFP3::Output::OsVersionFamily; use Net::SinFP3::Output::Pcap; use Net::SinFP3::Output::Ubigraph; # Set global default values $lopts{6} ||= 0; $lopts{jobs} ||= 10; $lopts{dns_reverse} ||= 0; $lopts{thread} ||= 0; $lopts{retry} ||= 3; $lopts{timeout} ||= 3; $lopts{pps} ||= 300; $lopts{port} ||= 'top10'; $lopts{port_src} ||= 53; $lopts{threshold} ||= 70; $lopts{best_score} ||= 0; $lopts{verbose} ||= 1; $lopts{passive} ||= 0; if ($lopts{quiet}) { $lopts{verbose} = 0; } # Set default plugin options $lopts{input_synscan} ||= 1; $lopts{mode_active} ||= 1; $lopts{db_sinfp3} ||= 1; $lopts{search_active} ||= 1; $lopts{log_console} ||= 1; $lopts{output_simple} ||= 1; # Set default plugin-specific values $lopts{db_update} ||= 0; $lopts{sniff_promiscuous} ||= 1; $lopts{pcap_anonymize} ||= 0; $lopts{pcap_append} ||= 0; $lopts{synscan_fingerprint} ||= 0; $lopts{csv_file} ||= 'sinfp3-output.csv'; # # Prepare log module # my $log; if ($lopts{log}) { $log = loadModule('Log', 'log', 'log_arg', \%lopts); } elsif ($lopts{log_null}) { $log = Net::SinFP3::Log::Null->new or exit(1); } # Default elsif ($lopts{log_console}) { $log = Net::SinFP3::Log::Console->new( level => $lopts{verbose}, ) or exit(1); } $log->init; # Prepare global configuration my $worker = 'Fork'; if ($lopts{thread}) { $worker = 'Thread'; } my %args = ( log => $log, worker => $worker, ipv6 => $lopts{6}, dnsReverse => $lopts{dns_reverse}, jobs => $lopts{jobs}, retry => $lopts{retry}, timeout => $lopts{timeout}, pps => $lopts{pps}, threshold => $lopts{threshold}, bestScore => $lopts{best_score}, ); if ($lopts{device}) { $args{device} = $lopts{device}; } if ($lopts{target}) { $args{target} = $lopts{target}; } if ($lopts{port}) { $args{port} = $lopts{port}; } if ($lopts{ip_src}) { $args{ip} = $lopts{ip_src}; } if ($lopts{ip6_src}) { $args{ip6} = $lopts{ip6_src}; } if ($lopts{mac_src}) { $args{mac} = $lopts{mac_src}; } if ($lopts{ip_gateway}) { $args{gatewayIp} = $lopts{ip_gateway}; } if ($lopts{ip6_gateway}) { $args{gatewayIp6} = $lopts{ip6_gateway}; } if ($lopts{subnet}) { $args{subnet} = $lopts{subnet}; } if ($lopts{subnet6}) { $args{subnet6} = $lopts{subnet6}; } if ($lopts{mac_gateway}) { $args{gatewayMac} = $lopts{mac_gateway}; } my $global = Net::SinFP3::Global->new(%args) or exit(1); # Prepare plugins my @input = (); my @db = (); my @mode = (); my @search = (); my @output = (); # # Prepare DB module # if ($lopts{db_null}) { push @db, Net::SinFP3::DB::Null->new( global => $global, ) or exit(1); } elsif ($lopts{db_update}) { # If user wants to update its database my %args = ( global => $global, ); if ($lopts{db_file}) { $args{file} = $lopts{db_file}; } my $db = Net::SinFP3::DB::SinFP3->new(%args) or exit(1); $db->update; $log->post; exit(0); } elsif ($lopts{db}) { push @db, loadModule('DB', 'db', 'db_arg', \%lopts, $global); } # Default elsif ($lopts{db_sinfp3}) { my %args = ( global => $global, ); if ($lopts{db_file}) { $args{file} = $lopts{db_file}; } push @db, Net::SinFP3::DB::SinFP3->new(%args) or exit(1); } # # Prepare Input module # if ($lopts{input}) { push @input, loadModule('Input', 'input', 'input_arg', \%lopts, $global); } elsif ($lopts{input_null}) { push @input, Net::SinFP3::Input::Null->new( global => $global, ) or exit(1); } elsif ($lopts{input_ipport}) { if (! $lopts{target}) { $log->fatal("You must provide -target argument"); } push @input, Net::SinFP3::Input::IpPort->new( global => $global, ) or exit(1); } elsif ($lopts{input_arpdiscover}) { push @input, Net::SinFP3::Input::ArpDiscover->new( global => $global, ) or exit(1); } elsif ($lopts{input_sniff}) { my $input = Net::SinFP3::Input::Sniff->new( global => $global, promisc => $lopts{sniff_promiscuous}, ) or exit(1); $input->filter($lopts{pcap_filter}) if $lopts{pcap_filter}; push @input, $input; } elsif ($lopts{input_signature}) { push @input, Net::SinFP3::Input::Signature->new( global => $global, ) or exit(1); } elsif ($lopts{input_signaturep}) { push @input, Net::SinFP3::Input::SignatureP->new( global => $global, ) or exit(1); } elsif ($lopts{input_connect}) { if (! $lopts{target}) { $log->fatal("You must provide -target argument"); } push @input, Net::SinFP3::Input::Connect->new( global => $global, ) or exit(1); } elsif ($lopts{input_server}) { push @input, Net::SinFP3::Input::Server->new( global => $global, ) or exit(1); } elsif ($lopts{input_pcap}) { if (! $lopts{pcap_file}) { $log->fatal("You must provide -pcap-file argument"); } if ($lopts{mode_passive}) { my $input = Net::SinFP3::Input::Pcap->new( global => $global, file => $lopts{pcap_file}, ) or exit(1); $input->filter($lopts{pcap_filter}) if $lopts{pcap_filter}; push @input, $input; } elsif ($lopts{mode_active}) { push @input, Net::SinFP3::Input::Pcap->new( global => $global, file => $lopts{pcap_file}, count => 10, ) or exit(1); } } # Default elsif ($lopts{input_synscan}) { if (! $lopts{target}) { $log->fatal("You must provide -target or -input-sniff argument, ". "or try -help for usage"); } push @input, Net::SinFP3::Input::SynScan->new( global => $global, fingerprint => $lopts{synscan_fingerprint}, ) or exit(1); } # # Prepare output module # if ($lopts{output}) { push @output, loadModule('Output', 'output', 'output_arg', \%lopts, $global); } elsif ($lopts{output_null}) { push @output, Net::SinFP3::Output::Null->new( global => $global, ) or exit(1); } elsif ($lopts{output_dumper}) { push @output, Net::SinFP3::Output::Dumper->new( global => $global, ) or exit(1); } elsif ($lopts{output_osonly}) { push @output, Net::SinFP3::Output::OsOnly->new( global => $global, ) or exit(1); } elsif ($lopts{output_osversionfamily}) { push @output, Net::SinFP3::Output::OsVersionFamily->new( global => $global, ) or exit(1); } elsif ($lopts{output_csv}) { push @output, Net::SinFP3::Output::CSV->new( global => $global, file => $lopts{csv_file}, ) or exit(1); } elsif ($lopts{output_ubigraph}) { push @output, Net::SinFP3::Output::Ubigraph->new( global => $global, file => $lopts{csv_file}, ) or exit(1); } elsif ($lopts{output_console}) { push @output, Net::SinFP3::Output::Console->new( global => $global, ) or exit(1); } elsif ($lopts{output_client}) { push @output, Net::SinFP3::Output::Client->new( global => $global, ) or exit(1); } # Default elsif ($lopts{output_simple}) { push @output, Net::SinFP3::Output::Simple->new( global => $global, ) or exit(1); } # We use multiple output module for this one if (!$lopts{output_null} && $lopts{output_pcap}) { push @output, Net::SinFP3::Output::Pcap->new( global => $global, anonymize => $lopts{pcap_anonymize}, append => $lopts{pcap_append}, ) or exit(1); } # # Prepare mode # if ($lopts{mode}) { push @mode, loadModule('Mode', 'mode', 'mode_arg', \%lopts, $global); } elsif ($lopts{mode_null}) { push @mode, Net::SinFP3::Mode::Null->new( global => $global, ) or exit(1); } elsif ($lopts{mode_passive} || $lopts{passive}) { push @mode, Net::SinFP3::Mode::Passive->new( global => $global, ) or exit(1); } # Default elsif ($lopts{mode_active}) { my %args = ( global => $global, ); if ($lopts{active_3}) { $args{doP1} = 1; $args{doP2} = 1; $args{doP3} = 1; } elsif ($lopts{active_2}) { $args{doP1} = 1; $args{doP2} = 1; $args{doP3} = 0; } elsif ($lopts{active_1}) { $args{doP1} = 0; $args{doP2} = 1; $args{doP3} = 0; } else { # Default for -active-2 $args{doP1} = 1; $args{doP2} = 1; $args{doP3} = 0; } push @mode, Net::SinFP3::Mode::Active->new(%args) or exit(1); } # # Prepare search engine # if ($lopts{search}) { push @search, loadModule('Search', 'search', 'search_arg', \%lopts, $global); } elsif ($lopts{search_null}) { push @search, Net::SinFP3::Search::Null->new( global => $global, ) or exit(1); } elsif ($lopts{search_passive} || $lopts{passive}) { push @search, Net::SinFP3::Search::Passive->new( global => $global, ) or exit(1); } # Default elsif ($lopts{search_active}) { push @search, Net::SinFP3::Search::Active->new( global => $global, ) or exit(1); } # Ready to run, my lord my $sinfp = Net::SinFP3->new( global => $global, input => \@input, db => \@db, mode => \@mode, search => \@search, output => \@output, ) or exit(1); $sinfp->run; $log->post; exit(0); sub loadModule { my ($kind, $module, $values, $lopts, $global) = @_; my $mod = 'Net::SinFP3::'.$kind.'::'.$lopts->{$module}; $log->debug("Loading module [$mod]"); eval "use $mod"; if ($@) { chomp($@); $log->fatal("$kind module [$mod] loading failed with error [$@]"); } my $new = $mod->new( global => $global, %{$lopts->{$values}}, ) or exit(1); if ($new->can('ipv6')) { $new->ipv6($lopts->{6}); } return $new; } __END__ =head1 NAME sinfp3.pl - more than a passive and active OS fingerprinting tool =head1 SYNOPSIS o Information about signature database updates and more: o https://www.secure-side.com/lists/mailman/listinfo/sinfp sinfp3.pl [options] -target ip|ip6|hostname -port port|portList Examples: # Single port active fingerprinting sinfp3.pl -target example.com -port 80 -input-ipport # Single port IPv6 active fingerprinting sinfp3.pl -target example.com -port 80 -input-ipport -6 # SynScan active fingerprinting of a single target sinfp3.pl -target example.com -port top100 # SynScan IPv6 active fingerprinting of a single target sinfp3.pl -target example.com -port top100 -6 # SynScan active fingerprinting of a target subnet sinfp3.pl -target 192.0.43.0/24 -port top100 # Passive fingerprinting sinfp3.pl -mode-passive -search-active -input-sniff # Passive IPv6 fingerprinting sinfp3.pl -mode-passive -search-active -input-sniff -6 # Active fingerprinting of LAN sinfp3.pl -input-arpdiscovery # Active fingerprinting of IPv6 LAN sinfp3.pl -input-arpdiscovery -6 # Simply SynScan the target sinfp3.pl -target example.com -port full -mode-null -search-null -db-null =head1 OPTIONS =over 4 =item B =over 8 =item B<-version> Print B version. =item B<-help> This help message. =item B<-target> ip|ip6|hostname Target. This is used to auto-detect some global parameters like B or B. =item B<-port> port|portList|top10|top100|top1000|all Target port. Default for top10 ports for plugins able to handle multiple ports. This format is documented in `perldoc Net::SinFP3::Global' B method. =item B<-port-src> port Source port to use. Not supported by all plugins. =item B<-passive> Use passive fingerprinting. Default to use active one. =item B<-6> Use IPv6 fingerprinting where available. Default to off. =item B<-jobs> number Maximum number of jobs in parallel. Default: 10. =item B<-dns-reverse> Do a reverse DNS lookup for targets. Default to no. =item B<-device> name Network device to use. Default to auto-detect. =item B<-thread> Use threaded worker model (discouraged). Fork is used by default (and in Perl, it is better than ithreads). =item B<-retry> times Re-launch probes specified number of time. Default: 3. =item B<-timeout> seconds Time in seconds before timing out. Default: 3. =item B<-pps> number Number of packet per seconds. Default: 200. =item B<-ip-src> ip The source IPv4 address to use. Default to auto-detect. =item B<-ip6-src> ip6 The source IPv6 address to use. Default to auto-detect. =item B<-mac-src> mac The source MAC address to use. Default to auto-detect. =item B<-subnet-src> subnet The source IPv4 subnet address to use. Default to auto-detect. =item B<-subnet6-src> subnet The source IPv6 subnet address to use. Default to auto-detect. =item B<-ip-gateway> ip The gateway IPv4 address to use. Default to auto-detect. =item B<-ip6-gateway> ip6 The gateway IPv6 address to use. Default to auto-detect. =item B<-mac-gateway> mac The gateway MAC address to use. Default to auto-detect. =item B<-verbose> level Use the following verbose level number. Between 0 and 3, from the less verbose to the most verbose. Default to 1. =item B<-quiet> Set verbose level to 0. Default to not. =item B<-threshold> score Use the specified threshold for plugins supporting it. Default to no threshold (0). =item B<-best-score> Only gather results for the best matches. Default to not. =back =back =over 4 =item B =over 8 =item B<-input> plugin Use specified plugin for input. Default input plugin is L. =item B<-input-arg> plugin-arg Parameter to the specified input plugin. Must use multiple times to give multiple parameters. =item B<-db> plugin Use specified plugin for db. Default DB plugin is L. Example: L. =item B<-db-arg> plugin-arg Parameter to the specified db plugin. Must use multiple times to give multiple parameters. =item B<-mode> plugin Use specified plugin for mode. Default mode plugin is L. =item B<-mode-arg> plugin-arg Parameter to the specified mode plugin. Must use multiple times to give multiple parameters. =item B<-search> plugin Use specified plugin for search. Default search plugin is L. =item B<-search-arg> plugin-arg Parameter to the specified search plugin. Must use multiple times to give multiple parameters. =item B<-output> plugin Use specified plugin for output. Default output plugin is L. =item B<-output-arg> plugin-arg Parameter to the specified output plugin. Must use multiple times to give multiple parameters. =back =back =over 4 =item B =over 8 =item B<-input-null> Turn off input plugin. =item B<-input-arpdiscover> Use ARP scanning on the local subnet to discover targets. Works also with B<-6> argument. =item B<-input-pcap> Take a pcap file (or files) as input. =item B<-input-synscan> Perform a TCP SYN scan to find open ports. Default plugin. =item B<-input-ipport> Use only target IP or hostname and one port. =item B<-input-sniff> Listen on the network to capture frames. =item B<-input-signature> Will ask the end-user to past an active signature as a string. =item B<-input-signaturep> Will ask the end-user to past a passive signature as a string. =item B<-input-connect> Performs a standard TCP connect() and sends a "GET /HTTP/1.0". Then, it analyzes the SYN|ACK response to perform active fingerprinting. =item B<-input-server> Starts a SinFP3 server on localhost:32000, so clients speaking the SinFP3 API will be able to access the fingerprinrint engine. =item B<-mode-null> Turn off mode plugin. =item B<-mode-active> Run using active plugin. This does active OS fingerprinting via SinFP3 engine. =item B<-mode-passive> Run using passive plugin. This does passive OS fingerprinting via SinFP3 engine. =item B<-db-null> Turn off DB plugin. =item B<-db-sinfp3> Use B database plugin. Default plugin. =item B<-search-null> Turn off search plugin. =item B<-search-active> Perform a search through a database in active mode. Default plugin. =item B<-search-passive> Perform a search through a database in passive mode. =item B<-log-null> Turn off log plugin. =item B<-log-console> Log messages to the console. Default plugin. =item B<-output-null> Turn off output plugin. =item B<-output-console> Render output to the console with many details. =item B<-output-client> Render output to the connected client using SinFP3 communication protocol. =item B<-output-simple> Render output to the console, in a simple way. Default plugin. =item B<-output-dumper> Prints a dump to the console. =item B<-output-osonly> Only outputs operating system, and not full details of the fingerprint. =item B<-output-osversionfamily> Only outputs operating system and its version family, and not full details of the fingerprint. =item B<-output-pcap> Saves a trace to a pcap file. You can reply it afterwards using B. =item B<-output-csv> Saves fingerprinting results a csv file. You can use L<-csv-file> to choose the output file. =item B<-output-ubigraph> Takes a CSV file and display results using Ubigraph. You must use a CSV file as generated by B. You can use L<-csv-file> to choose the input file. =back =back =over 4 =item B =over 8 =item B<-db-update> Will update the database for the selected B plugin. =item B<-db-file> file Database file to use. Default is plugin dependant. =item B<-sniff-promiscuous> Use promiscuous mode while sniffing. Default to true. =item B<-pcap-anonymize> Replaces IP source and destination addresses (and update IP/TCP checksums) to anonymize a pcap output. Default to not. =item B<-pcap-append> Append to an already existing pcap file. Default to not. =item B<-pcap-filter> pcap Use specified pcap filter. Use it where available. =item B<-csv-file> file Use input taken from specified CSV file. =item B<-pcap-file> file|fileList Use input taken from specified pcap file or fileList. FileList uses Perl B function. =item B<-active-3> Run all probes in active mode (default). =item B<-active-2> Run only probes P1 and P2 in active mode (stealthier). =item B<-active-1> Run only probe P2 in active mode (even stealthier). =item B<-synscan-fingerprint> Do not perform classic 3 packets fingerprinting, just use the SYN|ACK reply from the SYN request for fingerprinting. =back =back =cut