require 'singleton' module PacketFu # Utils is a collection of various and sundry network utilities that are useful for packet # manipulation. class Utils # Returns the MAC address of an IP address, or nil if it's not responsive to arp. Takes # a dotted-octect notation of the target IP address, as well as a number of parameters: # # === Parameters # :eth_saddr # Source MAC address. Defaults to "00:00:00:00:00:00". # :ip_saddr # Source IP address. Defaults to "0.0.0.0" # :flavor # The flavor of the ARP request. Defaults to :none. # :timeout # Timeout in seconds. Defaults to 3. # # === Example # PacketFu::Utils::arp("192.168.1.1") #=> "00:18:39:01:33:70" # PacketFu::Utils::arp("192.168.1.1", :timeout => 5, :flavor => :hp_deskjet) # # === Warning # # It goes without saying, spewing forged ARP packets on your network is a great way to really # irritate your co-workers. def self.arp(target_ip,args={}) arp_pkt = PacketFu::ARPPacket.new(:flavor => (args[:flavor] || :none)) arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = (args[:eth_saddr] || ($packetfu_default.config[:eth_saddr] if $packetfu_default) || "00:00:00:00:00:00" ) arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff" arp_pkt.arp_daddr_mac = "00:00:00:00:00:00" arp_pkt.arp_saddr_ip = (args[:ip_saddr] || ($packetfu_default.config[:ip_saddr] if $packetfu_default) || "0.0.0.0") arp_pkt.arp_daddr_ip = target_ip iface = (args[:iface] || ($packetfu_default.iface if $packetfu_default) || "eth0") # Stick the Capture object in its own thread. cap_thread = Thread.new do target_mac = nil cap = PacketFu::Capture.new(:iface => iface, :start => true, :filter => "arp src #{target_ip} and ether dst #{arp_pkt.eth_saddr}") arp_pkt.to_w(iface) # Shorthand for sending single packets to the default interface. timeout = 0 while target_mac.nil? && timeout <= (args[:timeout] || 3) if cap.save > 0 arp_response = PacketFu::Packet.parse(cap.array[0]) target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip = target_ip end timeout += 0.1 sleep 0.1 # Check for a response ten times per second. end target_mac end # cap_thread cap_thread.value end # Discovers the local IP and Ethernet address, which is useful for writing # packets you expect to get a response to. Note, this is a noisy # operation; a UDP packet is generated and dropped on to the default (or named) # interface, and then captured (which means you need to be root to do this). # # whoami? returns a hash of :eth_saddr, :eth_src, :ip_saddr, :ip_src, # :eth_dst, and :eth_daddr (the last two are usually suitable for a # gateway mac address). It's most useful as an argument to PacketFu::Config.new. # # === Parameters # :iface => "eth0" # An interface to listen for packets on. Note that since we rely on the OS to send the probe packet, # you will need to specify a target which will use this interface. # :target => "1.2.3.4" # A target IP address. By default, a packet will be sent to a random address in the 177/8 network. # Since this network is IANA reserved (for now), this network should be handled by your default gateway # and default interface. def self.whoami?(args={}) if args[:iface] =~ /^lo/ # Linux loopback more or less. Need a switch for windows loopback, too. dst_host = "127.0.0.1" else dst_host = (args[:target] || IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET).to_s) end dst_port = rand(0xffff-1024)+1024 msg = "PacketFu whoami? packet #{(Time.now.to_i + rand(0xffffff)+1)}" cap = PacketFu::Capture.new(:iface => (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo" ), :start => true, :filter => "udp and dst host #{dst_host} and dst port #{dst_port}") UDPSocket.open.send(msg,0,dst_host,dst_port) cap.save pkt = Packet.parse(cap.array[0]) unless cap.save.zero? timeout = 0 while timeout < 1 # Sometimes packet generation can be a little pokey. if pkt timeout = 1.1 # Cancel the timeout if pkt.payload == msg my_data = { :iface => args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo", :pcapfile => args[:pcapfile] || "/tmp/out.pcap", :eth_saddr => pkt.eth_saddr, :eth_src => pkt.eth_src.to_s, :ip_saddr => pkt.ip_saddr, :ip_src => pkt.ip_src.to_s, :eth_dst => pkt.eth_dst.to_s, :eth_daddr => pkt.eth_daddr } else raise SecurityError, "whoami() packet doesn't match sent data. Something fishy's going on." end else sleep 0.1; timeout += 0.1 cap.save pkt = Packet.parse(cap.array[0]) unless cap.save.zero? end raise SocketError, "Didn't receive the whomi() packet." if !pkt cap = nil end my_data end # This is a brute-force approach at trying to find a suitable interface with an IP address. def self.lookupdev # XXX cycle through eth0-9 and wlan0-9, and if a cap start throws a RuntimeErorr (and we're # root), it's not a good interface. Boy, really ought to fix lookupdev directly with another # method that returns an array rather than just the first candidate. end # Handles ifconfig for various (okay, one) platforms. Mac guys, fix this and submit a patch! # Will have Windows done shortly. # # Takes an argument (either string or symbol) of the interface to look up, and # returns a hash which contains at least the :iface element, and if configured, # these additional elements: # # :eth_saddr # A human readable MAC address # :eth_src # A packed MAC address # :ip_saddr # A dotted-quad string IPv4 address # :ip_src # A packed IPv4 address # :ip4_obj # An IPAddr object with bitmask # :ip6_saddr # A colon-delimited hex IPv6 address, with bitmask # :ip6_obj # An IPAddr object with bitmask # # === Example # PacketFu::Utils.ifconfig :wlan0 # Not associated yet # #=> {:eth_saddr=>"00:1d:e0:73:9d:ff", :eth_src=>"\000\035\340s\235\377", :iface=>"wlan0"} # PacketFu::Utils.ifconfig("eth0") # Takes 'eth0' as default # #=> {:eth_saddr=>"00:1c:23:35:70:3b", :eth_src=>"\000\034#5p;", :ip_saddr=>"10.10.10.9", :ip4_obj=>#, :ip_src=>"\n\n\n\t", :iface=>"eth0", :ip6_saddr=>"fe80::21c:23ff:fe35:703b/64", :ip6_obj=>#} # PacketFu::Utils.ifconfig :lo # #=> {:ip_saddr=>"127.0.0.1", :ip4_obj=>#, :ip_src=>"\177\000\000\001", :iface=>"lo", :ip6_saddr=>"::1/128", :ip6_obj=>#} def self.ifconfig(iface='eth0') ret = {} iface = iface.to_s.scan(/[0-9A-Za-z]/).join # Sanitizing input, no spaces, semicolons, etc. case RUBY_PLATFORM when /linux/i ifconfig_data = %x[ifconfig #{iface}] if ifconfig_data =~ /#{iface}/i ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/) else raise ArgumentError, "Cannot ifconfig #{iface}" end real_iface = ifconfig_data.first ret[:iface] = real_iface.split.first.downcase if real_iface =~ /[\s]HWaddr[\s]+([0-9a-fA-F:]{17})/i ret[:eth_saddr] = $1.downcase ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr]) end ifconfig_data.each do |s| case s when /inet addr:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9])(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]))?/i ret[:ip_saddr] = $1 ret[:ip_src] = [IPAddr.new($1).to_i].pack("N") ret[:ip4_obj] = IPAddr.new($1) ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3 when /inet6 addr:[\s]*([0-9a-fA-F:\x2f]+)/ ret[:ip6_saddr] = $1 ret[:ip6_obj] = IPAddr.new($1) end end end # linux ret end end end # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby