Difference between revisions of "Summerschool Aachen 2004/Project Day I"
(→HTTP Tunnelling) |
|||
Line 8: | Line 8: | ||
--[[Samad Nasserian]] | --[[Samad Nasserian]] | ||
+ | |||
+ | == Fingerprinting Tool in perl == | ||
+ | |||
+ | This is the current version of the fingerprinting tool, I'm working on. | ||
+ | |||
+ | <pre> | ||
+ | #!/usr/bin/perl -w | ||
+ | |||
+ | use strict; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | package Net::DNS::PacketExt; | ||
+ | |||
+ | use strict; | ||
+ | use vars qw(@ISA); | ||
+ | |||
+ | @ISA = qw(Net::DNS::Packet); | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref $proto || $proto; | ||
+ | my $data = shift; | ||
+ | |||
+ | my $self = Net::DNS::Packet->new($data); | ||
+ | $self->{"header"} = Net::DNS::HeaderExt->new($data); | ||
+ | |||
+ | bless $self, $class; | ||
+ | return $self; | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | package Net::DNS::HeaderExt; | ||
+ | |||
+ | use strict; | ||
+ | use vars qw(@ISA); | ||
+ | |||
+ | @ISA = qw(Net::DNS::Header); | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref $proto || $proto; | ||
+ | my $data = shift; | ||
+ | |||
+ | my $self = Net::DNS::Header->new($data); | ||
+ | |||
+ | my @a = unpack("n C2 n4", $$data); | ||
+ | |||
+ | $self->{"z0"} = ($a[2] >> 6) & 0x1; | ||
+ | $self->{"z1"} = ($a[2] >> 5) & 0x1; | ||
+ | $self->{"z2"} = ($a[2] >> 4) & 0x1; | ||
+ | |||
+ | bless $self, $class; | ||
+ | return $self; | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | package MapTool; | ||
+ | |||
+ | use strict; | ||
+ | use Carp; | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref $proto || $proto; | ||
+ | my $targethost = $_[0] or croak "need target host"; | ||
+ | |||
+ | my %self; | ||
+ | $self{targethost} = $targethost; | ||
+ | |||
+ | $self{smtp} = MapTool::Smtp->new(); | ||
+ | $self{ftp} = MapTool::Ftp->new(); | ||
+ | $self{dns} = MapTool::Dns->new(); | ||
+ | |||
+ | bless \%self, $class; | ||
+ | return \%self; | ||
+ | } | ||
+ | |||
+ | sub probe | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | $self->{dns}->probe($self->{targethost}); | ||
+ | } | ||
+ | |||
+ | sub results | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | $self->{dns}->results(); | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | package MapTool::Smtp; | ||
+ | |||
+ | use strict; | ||
+ | use vars qw/@ISA/; | ||
+ | use Carp; | ||
+ | use Net::DNS::Resolver; | ||
+ | |||
+ | @ISA = ("MapTool"); | ||
+ | |||
+ | my $mydomain = 'ccc.de'; | ||
+ | my $validsrc = 'cpunkt@ccc.de'; | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref($proto) || $proto; | ||
+ | |||
+ | my $fpres = "smtp_fingerprints"; | ||
+ | my $fpcmd = "smtp_tests"; | ||
+ | |||
+ | |||
+ | my $self = {}; | ||
+ | |||
+ | bless $self, $class; | ||
+ | $self->mkdb($fpcmd, $fpres); | ||
+ | |||
+ | return $self; | ||
+ | } | ||
+ | |||
+ | sub mkdb | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | $self->{commands} = []; | ||
+ | $self->{fingerprints} = {}; | ||
+ | |||
+ | open TESTS, "$_[0]" or croak "can't open $_[0]"; | ||
+ | open RESULTS, "$_[1]" or croak "can't open $_[1]"; | ||
+ | |||
+ | while (my $tline = <TESTS>) { | ||
+ | next if ($tline =~ /^#/); | ||
+ | chomp $tline; | ||
+ | |||
+ | my $invalidsrc = $self->generate_source_address(); | ||
+ | |||
+ | $tline =~ s/\$MY_DOMAIN/${mydomain}/g; | ||
+ | $tline =~ s/\$VALID_SOURCE/${validsrc}/g; | ||
+ | $tline =~ s/\$INVALID_SOURCE/${invalidsrc}/g; | ||
+ | |||
+ | my @cmd_sequence = split /->/, $tline; | ||
+ | |||
+ | push @{$self->{commands}}, \@cmd_sequence; | ||
+ | } | ||
+ | |||
+ | close TESTS; | ||
+ | |||
+ | while (my $rline = <RESULTS>) { | ||
+ | next if ($rline =~ /^#/); | ||
+ | chomp $rline; | ||
+ | |||
+ | my @res_sequence = split /:/, $rline; | ||
+ | my $software_version = shift @res_sequence; | ||
+ | |||
+ | $self->{fingerprints}->{$software_version} = \@res_sequence; | ||
+ | } | ||
+ | |||
+ | close RESULTS; | ||
+ | } | ||
+ | |||
+ | sub generate_source_address | ||
+ | { | ||
+ | my ($i, $ok, $query); | ||
+ | my ($fake_domain, $fake_user) = ('', ''); | ||
+ | my $res = Net::DNS::Resolver->new; | ||
+ | my @VALID_CHARS = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9', '_'); | ||
+ | |||
+ | while (not $ok) { | ||
+ | for $i (1 .. 25) { | ||
+ | $fake_domain .= $VALID_CHARS[int rand(@VALID_CHARS)]; | ||
+ | } | ||
+ | |||
+ | $fake_domain .= '.com'; | ||
+ | $ok = 1; | ||
+ | |||
+ | $query = $res->query($fake_domain, 'SOA'); | ||
+ | |||
+ | $ok = 1 if (not $query); | ||
+ | } | ||
+ | |||
+ | for $i (1 .. 10) { | ||
+ | $fake_user .= $VALID_CHARS[int rand(@VALID_CHARS)]; | ||
+ | } | ||
+ | |||
+ | return "$fake_user\@$fake_domain"; | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | package MapTool::Ftp; | ||
+ | |||
+ | use strict; | ||
+ | use vars qw/@ISA/; | ||
+ | use Carp; | ||
+ | |||
+ | @ISA = ("MapTool"); | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref($proto) || $proto; | ||
+ | |||
+ | my $fpres = "ftp_fingerprints"; | ||
+ | my $fpcmd = "ftp_tests"; | ||
+ | |||
+ | my $self = {}; | ||
+ | |||
+ | bless $self, $class; | ||
+ | $self->mkdb($fpcmd, $fpres); | ||
+ | |||
+ | return $self; | ||
+ | } | ||
+ | |||
+ | sub mkdb | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | $self->{commands} = []; | ||
+ | $self->{fingerprints} = {}; | ||
+ | |||
+ | open TESTS, "$_[0]" or croak "can't open $_[0]"; | ||
+ | open RESULTS, "$_[1]" or croak "can't open $_[1]"; | ||
+ | |||
+ | my $isnulled = 0; | ||
+ | |||
+ | while (my $tline = <TESTS>) { | ||
+ | next if ($tline =~ /^#/); | ||
+ | chomp $tline; | ||
+ | |||
+ | if ($tline =~ /^#if 0/) { | ||
+ | $isnulled = 1; | ||
+ | } elsif ($isnulled && $tline =~ /^#endif/) { | ||
+ | $isnulled = 0; | ||
+ | } | ||
+ | |||
+ | next if ($isnulled || $tline !~ /^\s*"([^"]+)"/); | ||
+ | |||
+ | push @{$self->{commands}}, $1; | ||
+ | } | ||
+ | |||
+ | close TESTS; | ||
+ | |||
+ | my $inblock = 0; | ||
+ | my $softwareversion; | ||
+ | |||
+ | while (my $rline = <RESULTS>) { | ||
+ | chomp $rline; | ||
+ | |||
+ | if ($inblock && $rline =~ /^\s*\}/) { | ||
+ | $inblock = 0; | ||
+ | $softwareversion = ''; | ||
+ | next; | ||
+ | } elsif ($inblock) { | ||
+ | my @checksums = split /,/, $rline; | ||
+ | push @{$self->{fingerprints}->{$softwareversion}}, \@checksums; | ||
+ | next; | ||
+ | } | ||
+ | |||
+ | if ($rline =~ /^\s*0UL,\s*"([^"]+)"/) { | ||
+ | $softwareversion = $1; | ||
+ | $inblock = 1 | ||
+ | } | ||
+ | } | ||
+ | |||
+ | close RESULTS; | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | package MapTool::Dns; | ||
+ | |||
+ | use strict; | ||
+ | use vars qw/@ISA/; | ||
+ | use Carp; | ||
+ | use LWP::UserAgent; | ||
+ | use Sys::Hostname; | ||
+ | use Socket; | ||
+ | |||
+ | @ISA = ("MapTool"); | ||
+ | |||
+ | sub new | ||
+ | { | ||
+ | my $proto = shift; | ||
+ | my $class = ref($proto) || $proto; | ||
+ | |||
+ | my $self = {}; | ||
+ | |||
+ | my $ua = LWP::UserAgent->new; | ||
+ | $ua->timeout(23); | ||
+ | |||
+ | my $djb_response = $ua->get('http://cr.yp.to/surveys/dns1.html'); | ||
+ | |||
+ | if ($djb_response->is_success) { | ||
+ | bless $self, $class; | ||
+ | $self->mkdb($djb_response->content); | ||
+ | return $self; | ||
+ | } | ||
+ | |||
+ | croak $djb_response->status_line; | ||
+ | } | ||
+ | |||
+ | sub mkdb | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | $self->{commands} = []; | ||
+ | $self->{fingerprints} = {}; | ||
+ | |||
+ | my $html_response = $_[0]; | ||
+ | my @html_lines = split /\n/, $html_response; | ||
+ | |||
+ | my $tables = 0; | ||
+ | my $table_open = 0; | ||
+ | |||
+ | $self->{commands} = [ | ||
+ | "\0\0\10\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\3", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\77", | ||
+ | "\0\0\0\0\0\2\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1\0\0\20\0\1", | ||
+ | "\0\0\40\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\50\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\0\0\0\0\0\0\0", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\6", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0", | ||
+ | "\0\0\2\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\7Authors\4BIND\0\0\20\0\3", | ||
+ | "\0\0\4\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\17\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\20\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\40\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\100\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\20\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\30\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\60\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\25erre-con-erre-cigarro\7maradns\3org\0\0\20\0\1", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\7version\6server\0\0\20\0\3", | ||
+ | "\0\0\0\0\0\1\0\0\0\0\0\0\7version\4bind\0\0\20\0\3" | ||
+ | ]; | ||
+ | |||
+ | foreach my $hline (@html_lines) { | ||
+ | if ($hline =~ /<table/) { | ||
+ | $tables++; | ||
+ | $table_open = 1; | ||
+ | next; | ||
+ | } elsif ($hline =~ /<\/table/) { | ||
+ | $table_open = 0; | ||
+ | next; | ||
+ | } | ||
+ | |||
+ | if ($tables > 3 && $tables < 10 && $table_open) { | ||
+ | if ($hline =~ /<tr><td>Software/ || $hline !~ s/<tr><td>([^<]+)<\/td>//) { | ||
+ | next; | ||
+ | } | ||
+ | |||
+ | my $softwareversion = $1; | ||
+ | my @results; | ||
+ | |||
+ | while ($hline =~ s/<td>([^<]+)<\/td>//) { | ||
+ | push @results, $1; | ||
+ | } | ||
+ | |||
+ | push @{$self->{fingerprints}->{$softwareversion}}, \@results; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | sub probe | ||
+ | { | ||
+ | my $self = shift; | ||
+ | my $targethost = $_[0]; | ||
+ | |||
+ | my $iaddr = inet_aton("0.0.0.0"); | ||
+ | my $proto = getprotobyname('udp'); | ||
+ | my $port = getservbyname('domain', 'udp'); | ||
+ | my $paddr = sockaddr_in(0, $iaddr); | ||
+ | |||
+ | socket(SOCK, PF_INET, SOCK_DGRAM, $proto) || croak "socket: $!"; | ||
+ | bind(SOCK, $paddr) || croak "bind: $!"; | ||
+ | |||
+ | $| = 1; | ||
+ | |||
+ | my $hisiaddr = inet_aton($targethost) || croak "unknown host"; | ||
+ | my $hispaddr = sockaddr_in($port, $hisiaddr); | ||
+ | |||
+ | my $count = 0; | ||
+ | |||
+ | foreach my $request (@{$self->{commands}}) { | ||
+ | $count++; | ||
+ | |||
+ | defined(send(SOCK, $request, 0, $hispaddr)) || croak "send ".$targethost.": $!"; | ||
+ | |||
+ | my $rin = ''; | ||
+ | vec($rin, fileno(SOCK), 1) = 1; | ||
+ | |||
+ | my $rout; | ||
+ | |||
+ | if (!select($rout = $rin, undef, undef, 3.0)) { | ||
+ | $count > 0 && do { $count = 0; $self->pushresult("timeout"); next; }; | ||
+ | redo; | ||
+ | } | ||
+ | |||
+ | my $response; | ||
+ | |||
+ | ($hispaddr = recv(SOCK, $response, 1000, 0)) || croak "recv: $!"; | ||
+ | |||
+ | $self->pushresult($response); | ||
+ | |||
+ | $count = 0; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | sub pushresult | ||
+ | { | ||
+ | my $self = shift; | ||
+ | my $result = $_[0]; | ||
+ | |||
+ | push @{$self->{answers}}, $result; | ||
+ | } | ||
+ | |||
+ | sub results | ||
+ | { | ||
+ | my $self = shift; | ||
+ | |||
+ | if (scalar @{$self->{answers}} != scalar @{$self->{commands}}) { | ||
+ | croak "number of answers doesn't match number of commands"; | ||
+ | } | ||
+ | |||
+ | $self->{results} = {}; | ||
+ | |||
+ | foreach my $swversion (keys %{$self->{fingerprints}}) { | ||
+ | for (my $j = 0; $j < scalar @{$self->{fingerprints}->{$swversion}}; $j++) { | ||
+ | my $matchcount; | ||
+ | |||
+ | for (my $i = 0; $i < scalar @{$self->{answers}}; $i++) { | ||
+ | $matchcount += $self->compare($self->{answers}->[$i], | ||
+ | $self->{fingerprints}->{$swversion}->[$j]->[$i]); | ||
+ | } | ||
+ | |||
+ | push @{$self->{results}->{$matchcount}}, $swversion; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | for (my $score = scalar @{$self->{answers}}; $score >= 0; $score--) { | ||
+ | next unless (defined $self->{results}->{$score}); | ||
+ | |||
+ | print "matched with score ".int ($score/(scalar @{$self->{answers}})*100).":\n"; | ||
+ | map { print "\t$_\n"; } @{$self->{results}->{$score}}; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | sub compare | ||
+ | { | ||
+ | my $self = shift; | ||
+ | my ($response, $fingerprint) = @_; | ||
+ | |||
+ | $fingerprint =~ s/\s+//g; | ||
+ | |||
+ | if ($fingerprint =~ /t/) { | ||
+ | return($response eq 'timeout'); | ||
+ | } elsif ($response eq 'timeout') { | ||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | my @alternatives = split /,/, $fingerprint; | ||
+ | my $packet = Net::DNS::PacketExt->new(\$response); | ||
+ | |||
+ | $packet || croak("DNS::Packet refuses the response..."); | ||
+ | |||
+ | my $header = $packet->header; | ||
+ | my @queries = $packet->question; | ||
+ | my @answers = $packet->answer; | ||
+ | |||
+ | my $match = 0; | ||
+ | |||
+ | foreach my $alternative (@alternatives) { | ||
+ | if ($alternative =~ s/0//) { | ||
+ | $match = ($header->rcode eq 'NOERROR'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/1//) { | ||
+ | $match = ($header->rcode eq 'FORMERR'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/2//) { | ||
+ | $match = ($header->rcode eq 'SERVFAIL'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/3//) { | ||
+ | $match = ($header->rcode eq 'NXDOMAIN'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/4//) { | ||
+ | $match = ($header->rcode eq 'NOTIMP'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/5//) { | ||
+ | $match = ($header->rcode eq 'REFUSED'); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/TC//) { | ||
+ | $match = $header->tc; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/RD//) { | ||
+ | $match = $header->rd; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/AA//) { | ||
+ | $match = $header->aa; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/Z0//) { | ||
+ | $match = $header->z0; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/Z1//) { | ||
+ | $match = $header->z1; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/Z2//) { | ||
+ | $match = $header->z2; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/q//) { | ||
+ | $match = ($#queries == -1); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/Q2//) { | ||
+ | $match = ($#queries == 1); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/X//) { | ||
+ | $match = 1; | ||
+ | next unless ($match); | ||
+ | } | ||
+ | if ($alternative =~ s/D//) { | ||
+ | $match = ($#answers > -1); | ||
+ | next unless ($match); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return $match; | ||
+ | } | ||
+ | |||
+ | 1; | ||
+ | |||
+ | # --------------------------------------------------------------------------------- | ||
+ | |||
+ | use strict; | ||
+ | |||
+ | if ($#ARGV != 0) { | ||
+ | print "usage: $0 <hostname>\n\n"; | ||
+ | exit 1; | ||
+ | } | ||
+ | |||
+ | my $targethost = $ARGV[0]; | ||
+ | |||
+ | my $map = MapTool->new($targethost); | ||
+ | |||
+ | $map->probe(); | ||
+ | $map->results(); | ||
+ | |||
+ | </pre> |
Revision as of 17:21, 30 September 2004
HTTP Tunnelling
I was planning to implement in my project first a simple program and then extend it to a more "advanced" program: Assume we have a host H1 behind a firewall F who wants to communicate with a host H2 who is outside the firewall. Let us further assume that the firewall F only lets HTTP packets through, i.e. packets with destination port 80, and assume that host H1 wants to build up an SSH connection with host H2. I want first to write a (static) C program run on a host X between host H1 and H2, such that H1 can contact build up an SSH connection with X at port 80 (meaning that the traffic will be let through by F) and X forwards the traffic to H2 at port 22. F should also forward the traffic from H2 to H1. Later I want to extend this program to a more dynamic program in a way that H1 can tell X with whom he wants to build up an SSH connection and X does this. If there is still some time left (which I hope there will be :)) I would like to extend that program to a more generic program which cannot only forward SSH traffic but also SMTP and other kinds of traffic. I know that there are probably already many tunnelling programs doing such kinds of things. However, as I don't have that much experience of network programming (except a small webserver I have once set up in Java) I think that such a project will help me to improve my skills in network programming.
Fingerprinting Tool in perl
This is the current version of the fingerprinting tool, I'm working on.
#!/usr/bin/perl -w use strict; # --------------------------------------------------------------------------------- package Net::DNS::PacketExt; use strict; use vars qw(@ISA); @ISA = qw(Net::DNS::Packet); sub new { my $proto = shift; my $class = ref $proto || $proto; my $data = shift; my $self = Net::DNS::Packet->new($data); $self->{"header"} = Net::DNS::HeaderExt->new($data); bless $self, $class; return $self; } 1; # --------------------------------------------------------------------------------- package Net::DNS::HeaderExt; use strict; use vars qw(@ISA); @ISA = qw(Net::DNS::Header); sub new { my $proto = shift; my $class = ref $proto || $proto; my $data = shift; my $self = Net::DNS::Header->new($data); my @a = unpack("n C2 n4", $$data); $self->{"z0"} = ($a[2] >> 6) & 0x1; $self->{"z1"} = ($a[2] >> 5) & 0x1; $self->{"z2"} = ($a[2] >> 4) & 0x1; bless $self, $class; return $self; } 1; # --------------------------------------------------------------------------------- package MapTool; use strict; use Carp; sub new { my $proto = shift; my $class = ref $proto || $proto; my $targethost = $_[0] or croak "need target host"; my %self; $self{targethost} = $targethost; $self{smtp} = MapTool::Smtp->new(); $self{ftp} = MapTool::Ftp->new(); $self{dns} = MapTool::Dns->new(); bless \%self, $class; return \%self; } sub probe { my $self = shift; $self->{dns}->probe($self->{targethost}); } sub results { my $self = shift; $self->{dns}->results(); } 1; # --------------------------------------------------------------------------------- package MapTool::Smtp; use strict; use vars qw/@ISA/; use Carp; use Net::DNS::Resolver; @ISA = ("MapTool"); my $mydomain = 'ccc.de'; my $validsrc = 'cpunkt@ccc.de'; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $fpres = "smtp_fingerprints"; my $fpcmd = "smtp_tests"; my $self = {}; bless $self, $class; $self->mkdb($fpcmd, $fpres); return $self; } sub mkdb { my $self = shift; $self->{commands} = []; $self->{fingerprints} = {}; open TESTS, "$_[0]" or croak "can't open $_[0]"; open RESULTS, "$_[1]" or croak "can't open $_[1]"; while (my $tline = <TESTS>) { next if ($tline =~ /^#/); chomp $tline; my $invalidsrc = $self->generate_source_address(); $tline =~ s/\$MY_DOMAIN/${mydomain}/g; $tline =~ s/\$VALID_SOURCE/${validsrc}/g; $tline =~ s/\$INVALID_SOURCE/${invalidsrc}/g; my @cmd_sequence = split /->/, $tline; push @{$self->{commands}}, \@cmd_sequence; } close TESTS; while (my $rline = <RESULTS>) { next if ($rline =~ /^#/); chomp $rline; my @res_sequence = split /:/, $rline; my $software_version = shift @res_sequence; $self->{fingerprints}->{$software_version} = \@res_sequence; } close RESULTS; } sub generate_source_address { my ($i, $ok, $query); my ($fake_domain, $fake_user) = ('', ''); my $res = Net::DNS::Resolver->new; my @VALID_CHARS = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9', '_'); while (not $ok) { for $i (1 .. 25) { $fake_domain .= $VALID_CHARS[int rand(@VALID_CHARS)]; } $fake_domain .= '.com'; $ok = 1; $query = $res->query($fake_domain, 'SOA'); $ok = 1 if (not $query); } for $i (1 .. 10) { $fake_user .= $VALID_CHARS[int rand(@VALID_CHARS)]; } return "$fake_user\@$fake_domain"; } 1; # --------------------------------------------------------------------------------- package MapTool::Ftp; use strict; use vars qw/@ISA/; use Carp; @ISA = ("MapTool"); sub new { my $proto = shift; my $class = ref($proto) || $proto; my $fpres = "ftp_fingerprints"; my $fpcmd = "ftp_tests"; my $self = {}; bless $self, $class; $self->mkdb($fpcmd, $fpres); return $self; } sub mkdb { my $self = shift; $self->{commands} = []; $self->{fingerprints} = {}; open TESTS, "$_[0]" or croak "can't open $_[0]"; open RESULTS, "$_[1]" or croak "can't open $_[1]"; my $isnulled = 0; while (my $tline = <TESTS>) { next if ($tline =~ /^#/); chomp $tline; if ($tline =~ /^#if 0/) { $isnulled = 1; } elsif ($isnulled && $tline =~ /^#endif/) { $isnulled = 0; } next if ($isnulled || $tline !~ /^\s*"([^"]+)"/); push @{$self->{commands}}, $1; } close TESTS; my $inblock = 0; my $softwareversion; while (my $rline = <RESULTS>) { chomp $rline; if ($inblock && $rline =~ /^\s*\}/) { $inblock = 0; $softwareversion = ''; next; } elsif ($inblock) { my @checksums = split /,/, $rline; push @{$self->{fingerprints}->{$softwareversion}}, \@checksums; next; } if ($rline =~ /^\s*0UL,\s*"([^"]+)"/) { $softwareversion = $1; $inblock = 1 } } close RESULTS; } 1; # --------------------------------------------------------------------------------- package MapTool::Dns; use strict; use vars qw/@ISA/; use Carp; use LWP::UserAgent; use Sys::Hostname; use Socket; @ISA = ("MapTool"); sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; my $ua = LWP::UserAgent->new; $ua->timeout(23); my $djb_response = $ua->get('http://cr.yp.to/surveys/dns1.html'); if ($djb_response->is_success) { bless $self, $class; $self->mkdb($djb_response->content); return $self; } croak $djb_response->status_line; } sub mkdb { my $self = shift; $self->{commands} = []; $self->{fingerprints} = {}; my $html_response = $_[0]; my @html_lines = split /\n/, $html_response; my $tables = 0; my $table_open = 0; $self->{commands} = [ "\0\0\10\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\3", "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\77", "\0\0\0\0\0\2\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1\0\0\20\0\1", "\0\0\40\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\50\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\0\0\0\0\0\0\0\0\0", "\0\0\0\0\0\1\0\0\0\0\0\0\6", "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0", "\0\0\2\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\0\0\1\0\0\0\0\0\0\7Authors\4BIND\0\0\20\0\3", "\0\0\4\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\17\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\20\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\40\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\100\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\20\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\30\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\60\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\0\0\1\0\0\0\0\0\0\4test\17network-surveys\2cr\2yp\2to\0\0\20\0\1", "\0\0\0\0\0\1\0\0\0\0\0\0\25erre-con-erre-cigarro\7maradns\3org\0\0\20\0\1", "\0\0\0\0\0\1\0\0\0\0\0\0\7version\6server\0\0\20\0\3", "\0\0\0\0\0\1\0\0\0\0\0\0\7version\4bind\0\0\20\0\3" ]; foreach my $hline (@html_lines) { if ($hline =~ /<table/) { $tables++; $table_open = 1; next; } elsif ($hline =~ /<\/table/) { $table_open = 0; next; } if ($tables > 3 && $tables < 10 && $table_open) { if ($hline =~ /<tr><td>Software/ || $hline !~ s/<tr><td>([^<]+)<\/td>//) { next; } my $softwareversion = $1; my @results; while ($hline =~ s/<td>([^<]+)<\/td>//) { push @results, $1; } push @{$self->{fingerprints}->{$softwareversion}}, \@results; } } } sub probe { my $self = shift; my $targethost = $_[0]; my $iaddr = inet_aton("0.0.0.0"); my $proto = getprotobyname('udp'); my $port = getservbyname('domain', 'udp'); my $paddr = sockaddr_in(0, $iaddr); socket(SOCK, PF_INET, SOCK_DGRAM, $proto) || croak "socket: $!"; bind(SOCK, $paddr) || croak "bind: $!"; $| = 1; my $hisiaddr = inet_aton($targethost) || croak "unknown host"; my $hispaddr = sockaddr_in($port, $hisiaddr); my $count = 0; foreach my $request (@{$self->{commands}}) { $count++; defined(send(SOCK, $request, 0, $hispaddr)) || croak "send ".$targethost.": $!"; my $rin = ''; vec($rin, fileno(SOCK), 1) = 1; my $rout; if (!select($rout = $rin, undef, undef, 3.0)) { $count > 0 && do { $count = 0; $self->pushresult("timeout"); next; }; redo; } my $response; ($hispaddr = recv(SOCK, $response, 1000, 0)) || croak "recv: $!"; $self->pushresult($response); $count = 0; } } sub pushresult { my $self = shift; my $result = $_[0]; push @{$self->{answers}}, $result; } sub results { my $self = shift; if (scalar @{$self->{answers}} != scalar @{$self->{commands}}) { croak "number of answers doesn't match number of commands"; } $self->{results} = {}; foreach my $swversion (keys %{$self->{fingerprints}}) { for (my $j = 0; $j < scalar @{$self->{fingerprints}->{$swversion}}; $j++) { my $matchcount; for (my $i = 0; $i < scalar @{$self->{answers}}; $i++) { $matchcount += $self->compare($self->{answers}->[$i], $self->{fingerprints}->{$swversion}->[$j]->[$i]); } push @{$self->{results}->{$matchcount}}, $swversion; } } for (my $score = scalar @{$self->{answers}}; $score >= 0; $score--) { next unless (defined $self->{results}->{$score}); print "matched with score ".int ($score/(scalar @{$self->{answers}})*100).":\n"; map { print "\t$_\n"; } @{$self->{results}->{$score}}; } } sub compare { my $self = shift; my ($response, $fingerprint) = @_; $fingerprint =~ s/\s+//g; if ($fingerprint =~ /t/) { return($response eq 'timeout'); } elsif ($response eq 'timeout') { return 0; } my @alternatives = split /,/, $fingerprint; my $packet = Net::DNS::PacketExt->new(\$response); $packet || croak("DNS::Packet refuses the response..."); my $header = $packet->header; my @queries = $packet->question; my @answers = $packet->answer; my $match = 0; foreach my $alternative (@alternatives) { if ($alternative =~ s/0//) { $match = ($header->rcode eq 'NOERROR'); next unless ($match); } if ($alternative =~ s/1//) { $match = ($header->rcode eq 'FORMERR'); next unless ($match); } if ($alternative =~ s/2//) { $match = ($header->rcode eq 'SERVFAIL'); next unless ($match); } if ($alternative =~ s/3//) { $match = ($header->rcode eq 'NXDOMAIN'); next unless ($match); } if ($alternative =~ s/4//) { $match = ($header->rcode eq 'NOTIMP'); next unless ($match); } if ($alternative =~ s/5//) { $match = ($header->rcode eq 'REFUSED'); next unless ($match); } if ($alternative =~ s/TC//) { $match = $header->tc; next unless ($match); } if ($alternative =~ s/RD//) { $match = $header->rd; next unless ($match); } if ($alternative =~ s/AA//) { $match = $header->aa; next unless ($match); } if ($alternative =~ s/Z0//) { $match = $header->z0; next unless ($match); } if ($alternative =~ s/Z1//) { $match = $header->z1; next unless ($match); } if ($alternative =~ s/Z2//) { $match = $header->z2; next unless ($match); } if ($alternative =~ s/q//) { $match = ($#queries == -1); next unless ($match); } if ($alternative =~ s/Q2//) { $match = ($#queries == 1); next unless ($match); } if ($alternative =~ s/X//) { $match = 1; next unless ($match); } if ($alternative =~ s/D//) { $match = ($#answers > -1); next unless ($match); } } return $match; } 1; # --------------------------------------------------------------------------------- use strict; if ($#ARGV != 0) { print "usage: $0 <hostname>\n\n"; exit 1; } my $targethost = $ARGV[0]; my $map = MapTool->new($targethost); $map->probe(); $map->results();