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