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

# arpguard.pl - H D Moore <hdm[at]metasploit.com>


use Net::Pcap;
use Getopt::Std;
use Sys::Syslog;
use Socket;

use strict;

use constant SOCK_PACKET => 10;

my %args = ();
getopts("i:f:t", \%args);

my %erate = ();
my %hosts = ();
my $tmode = 0;
my $agmac = "\xDE\xFA\xCE\xDF\xEE\xDD";
my $intf = "eth0";
my $sock;

openlog("arpguard", "cons,pid,ndelay", "user");

if ($args{i}) {
    $intf = $args{i};
}

if ($args{t}) {
    $tmode++;
}

if (! $args{f} && ! -r $args{f}) {
    print "[-] Could not open hosts file\n";
    exit(0);
}

# load the hosts file
open (my $inp, "<".$args{f});
while (<$inp>) {
    chomp;
    my ($ip, $type, $mac) = split(/\s+/, $_);
    $ip  = gethostbyname($ip);
    $mac = ether_aton($mac); 
    $hosts{$ip} = $mac;
}
close ($inp);

# verify that our interface is live
system("/sbin/ifconfig $intf up");


# linux-specific SOCK_PACKET for sending link-layer datagrams
if (! $tmode) {
    $sock = sock_packet();
    if (! $sock) {
        print "[-] Could not create raw socket: $!\n";
        exit(0);
    }
}

my ($err, $filtert);
my $pcap_t = Net::Pcap::open_live($intf, 60, 0, 10, \$err);

if (! defined($pcap_t))  {
    print "[-] Pcap error on device $args{i}: $err\n";
    exit(0);
}

if (Net::Pcap::datalink($pcap_t) != 1) {
    print "Pcap error: only ethernet is currently supported\n";
    exit(0);
}

my $filter = "arp";
if (Net::Pcap::compile($pcap_t, \$filtert, $filter, 1, 0) == -1) {
    print "[-] Pcap error: filter failed to compile\n";
    exit(0);
}

Net::Pcap::setfilter($pcap_t, $filtert);

while (1) {
    my (%hdr, $pkt);
    if ($pkt = Net::Pcap::next($pcap_t, \%hdr)) {
        my @bytes = unpack('C*', $pkt);

        # arp who-has
        if ($bytes[21] == 1) {
            my $sadd = pack('C*', @bytes[28..31]);
            my $dadd = pack('C*', @bytes[38..41]);
            if (! exists($hosts{$dadd})) {
                SendARP($sock, $agmac, pack('C*', @bytes[22..27]), $sadd, $dadd);
            }
        }

        # arp reply
        if ($bytes[21] == 2) {
            # ignore our own replies
            next if pack('C*', @bytes[6..12]) eq $agmac;
            my $sadd = pack('C*', @bytes[28..31]);
            my $dadd = pack('C*', @bytes[38..41]);
            my $seth = pack('C*', @bytes[22..27]);
            if (exists($hosts{$sadd}) && $hosts{$sadd} ne $seth) {
                if (time > ($erate{$sadd} + 300)) {
                    syslog('warning', 
                           "ethernet address mismatch for ".inet_ntoa($sadd).
                           " real=".ether_ntoa($hosts{$sadd})." fake=".ether_ntoa($seth)
                    );
                    
                    $erate{$sadd} = time();
                }
            }
        }       
    }
}

sub SendARP {
    my $sock = shift;
    my $dmac = shift;
    my $smac = shift;
    my $sadd = shift;
    my $dadd = shift;
    my $bmac = ("\xff" x 6);
    
    if ($tmode) {
        print "[*] Would have sent arp response for ".inet_ntoa($dadd)."\n";
        return;
    }

    my $resp = $bmac . $dmac . 
              "\x08\x06".   # ARP ethernet type
              "\x00\x01".   # Ethernet hardware type
              "\x08\x00".   # IP protocol type
              "\x06\x04".   # Hardware/Protocol address size
              "\x00\x02".   # ARP reply
               $dmac . $dadd . $bmac . $sadd;
               
    my $dest = ("\x00" x 2).$intf.("\x00" x (14 - length($intf)));
    send($sock, $resp, 0, $dest);
}


sub sock_packet {
    my $ret = socket(my $soc, PF_INET, SOCK_PACKET, 0x300);
    select($soc); $|++;
    select(STDOUT);
    return $soc;
}

sub ether_aton {
    my $ether = shift;
    $ether =~ s/://g;
    $ether =~ s/([a-f0-9][a-f0-9])/chr(hex($1))/egi;
    return $ether;
}

sub ether_ntoa {
    my $ether = shift;
    return uc(sprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", unpack('C*', $ether)));
}
