#!/usr/bin/perl
use Net::RawIP;
use Net::Pcap;
use NetPacket::IP;
use NetPacket::Ethernet;
use Mail::Sendmail;

my $dev     = 'eth0';	              # Set Ethernet Device
my $snaplen = '500';	              # Maximum Amt of packet to capture
my $promisc = '1';	              # Set promiscous mode on
my $to_ms   = '60';	     	      # Set timeout to 60 ms
my $fil     = 'dst port 68';          # Capture Filter
my $cap     = '40';	     	      # Number of packets to capture
my $server  = '10.1.2.6';	      # Legit DHCP server
my $email   = 'sheller@marlboro.edu'; # Where to send notification Email
my $mserv   = 'mail.marlboro.edu';    # mailserver to use for notification
my $local   = '0A010209';	      # IP address of host machine in Hex notation
my $discovr = '350101'; 	      # DHCP message type DISCOVER
my $request = '350103';		      # DHCP message type REQUEST 
my $badadd  = '192.168.5.22';	      # Invalid IP addresses for this network 
my @Data;

print "Lookup\n";###################################################3

# Lookup ip address and netmask for $dev
my ($address, $netmask, $err);
Net::Pcap::lookupnet($dev, \$address, \$netmask, \$err);
# Get MAC address and stick it in $smac
$smac = (split(/\s+/,qx!/sbin/ifconfig eth0!))[4];

print "Start Build\n";######################################################

# First we want to build each type of request packet

# INIT-REBOOT Requesting both valid and invalid IP addresses 
&PacketBuild( $smac, $address, '1', $request, '0', $address, '\$INIT_VALID') or die "$! \n";
&PacketBuild( $smac, $address, '1', $request, '0', $badadd, 'INIT_INVALID');
print "INIT-REBOOT\n";################################################################### 
# INIT-DISCOVER
&PacketBuild( $smac, $address, '1', $discovr, '0', '0', 'Init_Discover');
print "INIT-DISCOVER\n";####################################################################
# SELECTING Requesting both valid and invalid addresses from valid and invalid servers
&PacketBuild( $smac, $address, '1', $request, $server, $address, 'SELECT_VALID_VALID' );
&PacketBuild( $smac, $address, '1', $request, $server, $badadd, 'SELECT_VALID_INVALID' );
&PacketBuild( $smac, $address, '1', $request, $badadd, $address, 'SELECT_INVALID_VALID' );
&PacketBuild( $smac, $address, '1', $request, $badadd, $badadd, 'SELECT_INVALID_INVALID' );
print "SELECTING\n";#################################################################################
# REBIND Requesting both valid and invalid IP addresses
&PacketBuild( $smac, $address, '1', $request, '0', '0', 'REBIND_VALID' );
&PacketBuild( $smac, $badadd, '1', $request, '0', '0', 'REBIND_INVALID' );
print "REBIND\n";######################################################################################
# BootP Request
&PacketBuild ($smac, $address, '0', '0', '0', '0', 'BOOTP_REQUEST' );
print "BOOTP\n";####################################################

print "Finish Build \n";##################################################################

FORK:   {
	if ($pid = fork)
		{
		for (0 .. $cap/10)	
			{ 
			sleep(5);
			$INIT_VALID->ethsend;
			$INIT_INVALID->ethsend;
			$INIT_DISCOVER->ethsend;
			$SELECT_VALID_VALID->ethsend;
			$SELECT_VALID_INVALID->ethsend;
			$SELECT_INVALID_VALID->ethsend;
			$SELECT_INVALID_INVALID->ethsend;
			$REBIND_VALID->ethsend;
			$REBIND_INVALID->ethsend;	
			$BOOTP_REQUEST->ethsend;
			}
		}

	elsif (defined $pid)
		{
			&SnoopIt;	
		}
	}


#####################################
# Subroutines Here
#####################################
sub SnoopIt
	{

	# Build the Capture Object, create a filter for port 
	# 68 and apply it to the capture object.
	$capture = Net::Pcap::open_live($dev, $snaplen, $promisc, $to_ms, \$err); 
		   Net::Pcap::compile($capture, \$filter, $fil, '0', $netmask); 
		   Net::Pcap::setfilter($capture, $filter);
	
	# Start listening, stop after capturing num of packets defined in $cap  
	Net::Pcap::loop($capture, $cap, \&DecodePacket, $user_data);
	
	# We're done, close the Capture Device
	Net::Pcap::close($capture); 
	}

sub DecodePacket
	{

	my ($user_data, $hdr, $packet) = @_;
	my $ether_data = NetPacket::Ethernet::strip($packet);
	my $packet_data = NetPacket::IP->decode($ether_data);
	$SourceIp = $packet_data->{'src_ip'};
	if ($SourceIp != $server)
		{
		%mail = ( To      => $email,
			  From    => 'dhcp_probe@marlboro.edu',
			  Subject => 'Rouge DHCP Server',
			  Message => "Possible Rouge DHCP Server detected at $SourceIp",
			  smtp 	  => $mserv
			);
		sendmail(%mail) or die $Mail::Sendmail::error;

		}
 	} 

sub PacketBuild 
	{
	#
	# The options on the packet are :
	#        
	# Ethernet Header : 
	#----------------------
	# MAC address 
	#	1 = MAC of box running script
	#	2 = MAC that doesn't exist on the network
	#	( in order to use option 2 we need to respond 
	#	  to any ARP requests we get for that MAC      ) 
	#
	# BootP/DHCP Payload
	#------------------------
	# ciaddr (SELECTING and INIT/REBOOT)
	#	1 = Valid and unused IP address for the network
	#	2 = Invalid IP address for the network
	# 	Everything else sets it as 0.0.0.0	
	#
	# chaddr (MAC address)
	#	Same as the MAC in the ethernet header
	#	
	# Magic Cookie (DHCP Packets) 
	#	1 = 68325363
	#	Don't set this for BootP Requests	
	#
	# DHCP Message Type	
	#	1 = Discover (350101)
	#	2 = Request (350103)
	#
	# Client ID
	#	same as MAC in the ethernet header
	#
	# Server Identifier (SELECTING)
	#	1 = IP address of legit DHCP server
	#	2 = IP address of non-existent DHCP server
	#
	# Requested IP address (SELECTING and INIT/REBOOT)
	#	1 = Valid an unused IP address for the network
	#	2 = Invalid IP address for the network
	#
	# Name we'll use later to refer to this type of packet

 
	($MacVal, $CiVal, $Cookie, $Type, $ServID, $ReqIP, $Name) = @_;
	$MacAdd = $MacVal;
	$MacAdd =~ s/://g;
	print "$Name \n";
	# First we'll build the DHCP or BootP Payload
	# 01 - msg type bootrequest
	# 01 - hardware type ethernet
	# 06 - hardware address length 6
	# 00 - hops 0
	undef @Data;
	my @Data;
	push (@Data, '01010600');

	# Create a random Transaction ID
	for (0..3) { push (@Data, sprintf("%x", int(rand(239)) + 16)); }

	# 0000 - Zero Seconds Elapsed
	push (@Data, '0000');

	# 0000 - all Bootp flags 0
	push (@Data, '0000');

	# Set the Client IP address to $CiVal  
	push (@Data, $CiVal); 

	# Set yiaddr to 0.0.0.0 
	push (@Data, '00000000');

	# Set siaddr to 0.0.0.0
	push (@Data, '00000000');
	
	# Set giaddr to 0.0.0.0
	push (@Data, '00000000');

	# sname and file are both null, so generate 202 00     
	# octets and stick them into @Data.
	push (@Data, sprintf("%0404d"));

	# Set the 'magic cookie' if we're making a DHCP packet 
	if ($Cookie eq '1') 
		{ 
		push (@Data, '63825363'); 
		
		# Now put the selected message type onto @Data
		push (@Data, $Type);

		# Client Identification
		# Option 61 / Option Size 7 bytes / Hardware type 01 (ethernet) 
		push ( @Data, '3D0701' ); 
        	# Set desired MAC address
		push (@Data, $MacAdd);

		# Check if we want to set the Server ID option
		if ($ServID ne '0')
			{
			# Option 54            
			push ( @Data, '3D' );
			# Set Server ID
			push ( @Data, $ServID );
			}

		# Check if we're using the Requested IP option   
		if ($ReqIP ne '0')
			{
			# Option 50
			push ( @Data, '32' );
			# Set Requested IP
			push ( @Data, $ReqIP );
			}
	
		# End Option
		push (@Data, 'FF');
	}
	# Need padding?
	push (@Data, '0' x(600-length(join '',@Data)));
	print "Payload Done\n";#####################################################
	# Now we've got the payload all together, first lets 
	# put it into a string for Net::RawIP              
	foreach (@Data) { $tmp = join '', $tmp, $_; }
	$data = pack("H*", $tmp);
	
	# Build and Send the packet 
	$dmac = "FF:FF:FF:FF:FF:FF";
	# Now we start Building the Packet
	# UDP packet / Source Port 68 / Destination Port 67 / Payload $data
	$Name = new Net::RawIP({
				  udp => { 
					 source => '68', 
					 dest   => '67', 
					 data   => $data
					 }
				  }); 
	print "$$Name : $Name : Packet Build Stage 1\n";############################################
	# By default using interface eth0 (this should probably be a configurable option)
	$$Name->ethnew("eth0") or die "$! \n" ; 
	
	# First build the ethernet Headers
	# Source Mac Address from $MacVal / Broadcast Packet
	$$Name ->ethset(
			source => $MacVal, 
			dest => 'FF:FF:FF:FF:FF:FF:' 
			);
	print "Packet Build Stage 2\n";########################################################
	# Now we build the IP Headers
	$$Name ->set({ ip => {	frag_off => '0x0000',
				id       => int(rand(65535)),
				tos      => '0',
				saddr 	 => '0.0.0.0',
				daddr    => '255.255.255.255'}}) ; 

	print "Packet Build Stage 3\n";#########################################################
	# Everything should be set, lets send the packet 


	# Now clear everything out for the next runthrough

	undef $tmp;	 

	}

