summaryrefslogtreecommitdiffstats
path: root/scripts/mrinfo.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/mrinfo.nse')
-rw-r--r--scripts/mrinfo.nse289
1 files changed, 289 insertions, 0 deletions
diff --git a/scripts/mrinfo.nse b/scripts/mrinfo.nse
new file mode 100644
index 0000000..fb7284f
--- /dev/null
+++ b/scripts/mrinfo.nse
@@ -0,0 +1,289 @@
+local nmap = require "nmap"
+local packet = require "packet"
+local ipOps = require "ipOps"
+local stdnse = require "stdnse"
+local string = require "string"
+local target = require "target"
+local table = require "table"
+
+
+description = [[
+Queries targets for multicast routing information.
+
+This works by sending a DVMRP Ask Neighbors 2 request to the target and
+listening for DVMRP Neighbors 2 responses that are sent back and which contain
+local addresses and the multicast neighbors on each interface of the target. If
+no specific target is specified, the request will be sent to the 224.0.0.1 All
+Hosts multicast address.
+
+This script is similar somehow to the mrinfo utility included with Windows and
+Cisco IOS.
+]]
+
+---
+-- @args mrinfo.target Host to which the request is sent. If not set, the
+-- request will be sent to <code>224.0.0.1</code>.
+--
+-- @args mrinfo.timeout Time to wait for responses.
+-- Defaults to <code>5s</code>.
+--
+--@usage
+-- nmap --script mrinfo
+-- nmap --script mrinfo -e eth1
+-- nmap --script mrinfo --script-args 'mrinfo.target=172.16.0.4'
+--
+--@output
+-- Pre-scan script results:
+-- | mrinfo:
+-- | Source: 224.0.0.1
+-- | Version 12.4
+-- | Local address: 172.16.0.2
+-- | Neighbor: 172.16.0.4
+-- | Neighbor: 172.16.0.3
+-- | Local address: 172.17.0.1
+-- | Neighbor: 172.17.0.2
+-- | Local address: 172.18.0.1
+-- | Neighbor: 172.18.0.2
+-- | Source: 224.0.0.1
+-- | Version 12.4
+-- | Local address: 172.16.0.4
+-- | Neighbor: 172.16.0.3
+-- | Neighbor: 172.16.0.2
+-- | Local address: 172.17.0.2
+-- | Neighbor: 172.17.0.1
+-- | Source: 224.0.0.1
+-- | Version 12.4
+-- | Local address: 172.16.0.3
+-- | Neighbor: 172.16.0.4
+-- | Neighbor: 172.16.0.2
+-- | Local address: 172.18.0.2
+-- | Neighbor: 172.18.0.1
+-- |_ Use the newtargets script-arg to add the responses as targets
+--
+
+
+author = "Hani Benhabiles"
+
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+
+categories = {"discovery", "safe", "broadcast"}
+
+
+prerule = function()
+ if nmap.address_family() ~= 'inet' then
+ stdnse.verbose1("is IPv4 only.")
+ return false
+ end
+ if not nmap.is_privileged() then
+ stdnse.verbose1("not running for lack of privileges.")
+ return false
+ end
+ return true
+end
+
+-- Parses a DVMRP Ask Neighbor 2 raw data and returns
+-- a structured response.
+-- @param data raw data.
+local mrinfoParse = function(data)
+ local index, address, neighbor
+ local response = {}
+
+ -- first byte should be IGMP type == 0x13 (DVMRP)
+ if data:byte(1) ~= 0x13 then return end
+
+ -- DVMRP Code
+ response.code,
+ -- Checksum
+ response.checksum,
+ -- Capabilities (Skip one reserved byte)
+ response.capabilities,
+ -- Major and minor version
+ response.minver,
+ response.majver, index = string.unpack(">B I2 x B B B", data, 2)
+ response.addresses = {}
+ -- Iterate over target local addresses (interfaces)
+ while index < #data do
+ if data:byte(index) == 0x00 then break end
+ address = {}
+ -- Local address
+ address.ip,
+ -- Link metric
+ address.metric,
+ -- Threshold
+ address.threshold,
+ -- Flags
+ address.flags,
+ -- Number of neighbors
+ address.ncount, index = string.unpack(">c4BBBB", data, index)
+ address.ip = ipOps.str_to_ip(address.ip)
+
+ address.neighbors = {}
+ -- Iterate over neighbors
+ for i = 1, address.ncount do
+ neighbor, index = string.unpack(">c4", data, index)
+ table.insert(address.neighbors, ipOps.str_to_ip(neighbor))
+ end
+ table.insert(response.addresses, address)
+ end
+ return response
+end
+
+-- Listens for DVMRP Ask Neighbors 2 responses
+--@param interface Network interface to listen on.
+--@param timeout Time to listen for a response.
+--@param responses table to insert responses into.
+local mrinfoListen = function(interface, timeout, responses)
+ local condvar = nmap.condvar(responses)
+ local start = nmap.clock_ms()
+ local listener = nmap.new_socket()
+ local p, mrinfo_raw, status, l3data, response, _
+
+ -- IGMP packets that are sent to our host
+ local filter = 'ip proto 2 and dst host ' .. interface.address
+ listener:set_timeout(100)
+ listener:pcap_open(interface.device, 1024, true, filter)
+
+ while (nmap.clock_ms() - start) < timeout do
+ status, _, _, l3data = listener:pcap_receive()
+ if status then
+ p = packet.Packet:new(l3data, #l3data)
+ mrinfo_raw = string.sub(l3data, p.ip_hl*4 + 1)
+ if p then
+ -- Check that IGMP Type == DVMRP (0x13) and DVMRP code == Neighbor 2 (0x06)
+ if mrinfo_raw:byte(1) == 0x13 and mrinfo_raw:byte(2) == 0x06 then
+ response = mrinfoParse(mrinfo_raw)
+ if response then
+ response.srcip = p.ip_src
+ table.insert(responses, response)
+ end
+ end
+ end
+ end
+ end
+ condvar("signal")
+end
+
+-- Function that generates a raw DVMRP Ask Neighbors 2 request.
+local mrinfoRaw = function()
+ local mrinfo_raw = string.pack(">BB I2 I2 BB",
+ 0x13, -- Type: DVMRP
+ 0x05, -- Code: Ask Neighbor v2
+ 0x0000, -- Checksum: Calculated later
+ 0x000a, -- Reserved
+ -- Version == Cisco IOS 12.4
+ 0x04, -- Minor version: 4
+ 0x0c) -- Major version: 12
+
+ -- Calculate checksum
+ mrinfo_raw = mrinfo_raw:sub(1,2) .. string.pack(">I2", packet.in_cksum(mrinfo_raw)) .. mrinfo_raw:sub(5)
+
+ return mrinfo_raw
+end
+
+-- Function that sends a DVMRP query.
+--@param interface Network interface to use.
+--@param dstip Destination IP to send to.
+local mrinfoQuery = function(interface, dstip)
+ local mrinfo_packet, sock, eth_hdr
+ local srcip = interface.address
+
+ local mrinfo_raw = mrinfoRaw()
+ local ip_raw = stdnse.fromhex( "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw
+ mrinfo_packet = packet.Packet:new(ip_raw, ip_raw:len())
+ mrinfo_packet:ip_set_bin_src(ipOps.ip_to_str(srcip))
+ mrinfo_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip))
+ mrinfo_packet:ip_set_len(ip_raw:len())
+ if dstip == "224.0.0.1" then
+ -- Doesn't affect results, but we should respect RFC 3171 :)
+ mrinfo_packet:ip_set_ttl(1)
+ end
+ mrinfo_packet:ip_count_checksum()
+
+ sock = nmap.new_dnet()
+ if dstip == "224.0.0.1" then
+ sock:ethernet_open(interface.device)
+ -- Ethernet IPv4 multicast, our ethernet address and packet type IP
+ eth_hdr = "\x01\x00\x5e\x00\x00\x01" .. interface.mac .. "\x08\x00"
+ sock:ethernet_send(eth_hdr .. mrinfo_packet.buf)
+ sock:ethernet_close()
+ else
+ sock:ip_open()
+ sock:ip_send(mrinfo_packet.buf, dstip)
+ sock:ip_close()
+ end
+end
+
+-- Returns the network interface used to send packets to a target host.
+--@param target host to which the interface is used.
+--@return interface Network interface used for target host.
+local getInterface = function(target)
+ -- First, create dummy UDP connection to get interface
+ local sock = nmap.new_socket()
+ local status, err = sock:connect(target, "12345", "udp")
+ if not status then
+ stdnse.verbose1("%s", err)
+ return
+ end
+ local status, address, _, _, _ = sock:get_info()
+ if not status then
+ stdnse.verbose1("%s", err)
+ return
+ end
+ for _, interface in pairs(nmap.list_interfaces()) do
+ if interface.address == address then
+ return interface
+ end
+ end
+end
+
+action = function()
+ local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
+ timeout = (timeout or 5) * 1000
+ local target = stdnse.get_script_args(SCRIPT_NAME .. ".target") or "224.0.0.1"
+ local responses = {}
+ local interface, result
+
+ interface = nmap.get_interface()
+ if interface then
+ interface = nmap.get_interface_info(interface)
+ else
+ interface = getInterface(target)
+ end
+ if not interface then
+ return stdnse.format_output(false, ("Couldn't get interface for %s"):format(target))
+ end
+
+ stdnse.debug1("will send to %s via %s interface.", target, interface.shortname)
+
+ -- Thread that listens for responses
+ stdnse.new_thread(mrinfoListen, interface, timeout, responses)
+
+ -- Send request after small wait to let Listener start
+ stdnse.sleep(0.1)
+ mrinfoQuery(interface, target)
+ local condvar = nmap.condvar(responses)
+ condvar("wait")
+
+ if #responses > 0 then
+ local output, ifoutput = {}
+ for _, response in pairs(responses) do
+ result = {}
+ result.name = "Source: " .. response.srcip
+ table.insert(result, ("Version %s.%s"):format(response.majver, response.minver))
+ for _, address in pairs(response.addresses) do
+ ifoutput = {}
+ ifoutput.name = "Local address: " .. address.ip
+ for _, neighbor in pairs(address.neighbors) do
+ if target.ALLOW_NEW_TARGETS then target.add(neighbor) end
+ table.insert(ifoutput, "Neighbor: " .. neighbor)
+ end
+ table.insert(result, ifoutput)
+ end
+ table.insert(output, result)
+ end
+ if not target.ALLOW_NEW_TARGETS then
+ table.insert(output,"Use the newtargets script-arg to add the results as targets")
+ end
+ return stdnse.format_output(true, output)
+ end
+end