lib/packetgen/utils.rb in packetgen-3.2.0 vs lib/packetgen/utils.rb in packetgen-3.2.1

- old
+ new

@@ -14,14 +14,31 @@ # This module is not enabled by default. You need to: # require 'packetgen/utils' # @author Sylvain Daubert # @since 2.1.3 module Utils + # @private + ARP_FILTER = 'arp src %<ipaddr>s and ether dst %<hwaddr>s' + # @private + MITM_FILTER = '((ip src %<target1>s and not ip dst %<local_ip>s) or' \ + ' (ip src %<target2>s and not ip dst %<local_ip>s) or' \ + ' (ip dst %<target1>s and not ip src %<local_ip>s) or' \ + ' (ip dst %<target2>s and not ip src %<local_ip>s))' \ + ' and ether dst %<local_mac>s' + # Get local ARP cache # @return [Hash] key: IP address, value: array containing MAC address and # interface name def self.arp_cache + return self.cache_from_arp_command if File.exist?('/usr/sbin/arp') + return self.cache_from_ip_command if File.exist?('/usr/bin/ip') + + {} + end + + # @private + def self.cache_from_arp_command raw_cache = `/usr/sbin/arp -an` cache = {} raw_cache.split("\n").each do |line| match = line.match(/\((\d+\.\d+\.\d+\.\d+)\) at (([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})(?: \[ether\])? on (\w+)/) @@ -29,20 +46,35 @@ end cache end + # @private + def self.cache_from_ip_command + raw_cache = `ip neigh` + + cache = {} + raw_cache.split("\n").each do |line| + match = line.match(/^(\d+\.\d+\.\d+\.\d+) dev (\w+) lladdr (([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2})/) + cache[match[1]] = [match[3], match[2]] if match + end + + cache + end + + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize + # Get MAC address from an IP address, or nil if this IP address is unknown # on local network. # @param [String] ipaddr dotted-octet IP address # @param [Hash] options # @option options [String] :iface interface name. Default to # {PacketGen.default_iface} # @option options [Boolean] :no_cache if +true+, do not query local ARP # cache and always send an ARP request on wire. Default to +false+ # @option options [Integer] :timeout timeout in seconds before stopping - # request. Default to 2. + # request. Default to 1. # @return [String,nil] # @raise [RuntimeError] user don't have permission to capture packets on network device. def self.arp(ipaddr, options={}) unless options[:no_cache] local_cache = self.arp_cache @@ -55,23 +87,24 @@ arp_pkt = Packet.gen('Eth', dst: 'ff:ff:ff:ff:ff:ff', src: my_hwaddr) .add('ARP', sha: Config.instance.hwaddr(iface), spa: Config.instance.ipaddr(iface), tpa: ipaddr) - capture = Capture.new(iface: iface, timeout: timeout, max: 1, - filter: "arp src #{ipaddr} and ether dst #{my_hwaddr}") + capture = Capture.new(iface: iface, timeout: timeout, max: 1, filter: ARP_FILTER % { ipaddr: ipaddr, hwaddr: my_hwaddr }) cap_thread = Thread.new { capture.start } + sleep 0.1 arp_pkt.to_w(iface) cap_thread.join return if capture.packets.empty? capture.packets.each do |pkt| break pkt.arp.sha.to_s if pkt.arp.spa.to_s == ipaddr end end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize # Do ARP spoofing on given IP address. Call to this method blocks. # @note This method is provided for test purpose. # For more control, see {ARPSpoofer} class. # @param [String] target_ip target IP address @@ -123,39 +156,40 @@ # @since 2.2.0 # @raise [RuntimeError] user don't have permission to capture packets on network device. def self.mitm(target1, target2, options={}) options = { iface: PacketGen.default_iface }.merge(options) - mac1 = arp(target1) - mac2 = arp(target2) - spoofer = Utils::ARPSpoofer.new(options) spoofer.add target1, target2, options spoofer.add target2, target1, options - my_mac = Config.instance.hwaddr(options[:iface]) - my_ip = Config.instance.ipaddr(options[:iface]) + cfg = Config.instance + my_mac = cfg.hwaddr(options[:iface]) capture = Capture.new(iface: options[:iface], - filter: "((ip src #{target1} and not ip dst #{my_ip}) or" \ - " (ip src #{target2} and not ip dst #{my_ip}) or" \ - " (ip dst #{target1} and not ip src #{my_ip}) or" \ - " (ip dst #{target2} and not ip src #{my_ip}))" \ - " and ether dst #{my_mac}") + filter: MITM_FILTER % { target1: target1, target2: target2, local_ip: cfg.ipaddr(options[:iface]), local_mac: my_mac }) spoofer.start_all + mitm_core(capture, target1, target2, my_mac, &proc) + spoofer.stop_all + end + + # @private + def self.mitm_core(capture, target1, target2, my_mac) + mac1 = arp(target1) + mac2 = arp(target2) + capture.start do |pkt| modified_pkt = yield pkt iph = modified_pkt.ip l2 = modified_pkt.is?('Dot11') ? modified_pkt.dot11 : modified_pkt.eth - if (iph.src == target1) || (iph.dst == target2) - l2.dst = mac2 - elsif (iph.src == target2) || (iph.dst == target1) - l2.dst = mac1 - else - next - end - modified_pkt.to_w(options[:iface]) + l2.src = my_mac + l2.dst = if (iph.src == target1) || (iph.dst == target2) + mac2 + else # (iph.src == target2) || (iph.dst == target1) + mac1 + end + modified_pkt.to_w(capture.iface) end end end end