diff --git a/CHANGELOG b/CHANGELOG
index 2510ec05e..993a95bf9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added mrinfo script whiches queries a target router for multicast
+ information. [Hani Benhabiles]
+
o [NSE] Added ssl-date script which gets server's time from SSL ServerHello
reply server random part. [Aleksandar Nikolic]
diff --git a/scripts/mrinfo.nse b/scripts/mrinfo.nse
new file mode 100644
index 000000000..2d58371f1
--- /dev/null
+++ b/scripts/mrinfo.nse
@@ -0,0 +1,217 @@
+local nmap = require "nmap"
+local packet = require "packet"
+local ipOps = require "ipOps"
+local bin = require "bin"
+local stdnse = require "stdnse"
+local target = require "target"
+local table = require "table"
+
+
+description = [[
+Queries a target router for multicast information.
+
+This works by sending a DVMRP Ask Neighbors 2 request to the target and
+listening for the DVMRP Neighbors 2 response that contains local addresses and
+the multicast neighbors on each one.
+
+]]
+
+
+---
+-- @args mrinfo.timeout Time to wait for a response in seconds.
+-- Defaults to 5 seconds.
+--
+--@usage
+-- nmap --script mrinfo
+--
+--@output
+-- Host script results:
+-- | mrinfo:
+-- | Version 12.4
+-- | Local address: 192.168.2.2
+-- | Neighbor: 192.168.2.4
+-- | Neighbor: 192.168.2.3
+-- | Local address: 192.168.13.1
+-- | Neighbor: 192.168.13.3
+-- |_ Use the newtargets script-arg to add the results as targets
+
+
+author = "Hani Benhabiles"
+
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+
+categories = {"discovery", "safe"}
+
+
+hostrule = function(host)
+ if nmap.address_family() ~= 'inet' then
+ stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
+ 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, interface, neighbor
+ local response = {}
+
+ -- first byte should be IGMP type == 0x013 (DVMRP)
+ if data:byte(1) ~= 0x013 then return end
+
+ -- DVMRP Code
+ index, response.code = bin.unpack(">C", data, 2)
+ -- Checksum
+ index, response.checksum = bin.unpack(">S", data, index)
+ -- Capabilities (Skip one reserved byte)
+ index, response.capabilities = bin.unpack(">C", data, index + 1)
+ -- Major and minor version
+ index, response.minver = bin.unpack(">C", data, index)
+ index, response.majver = bin.unpack(">C", data, index)
+ response.interfaces = {}
+ -- Iterate over target local addresses (interfaces)
+ while index < #data do
+ if data:byte(index) == 0x00 then break end
+ interface = {}
+ -- Local address
+ index, interface.address = bin.unpack("C", data, index)
+ -- Treshold
+ index, interface.treshold= bin.unpack(">C", data, index)
+ -- Flags
+ index, interface.flags = bin.unpack(">C", data, index)
+ -- Number of neighbors
+ index, interface.ncount = bin.unpack(">C", data, index)
+
+ interface.neighbors = {}
+ -- Iterate over neighbors
+ for i = 1, interface.ncount do
+ index, neighbor = bin.unpack("C", 0x13)
+ -- Code: Ask Neighbor v2
+ mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x05)
+ -- Checksum: Calculated later
+ mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x0000)
+ -- Reserved
+ mrinfo_raw = mrinfo_raw.. bin.pack(">S", 0x000a)
+ -- Version == Cisco IOS 12.4
+ -- Minor version: 4
+ mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x04)
+ -- Major version: 12
+ mrinfo_raw = mrinfo_raw.. bin.pack(">C", 0x0c)
+ -- Calculate checksum
+ mrinfo_raw = mrinfo_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(mrinfo_raw)) .. mrinfo_raw:sub(5)
+
+ return mrinfo_raw
+end
+
+-- Function that sends a DVMRP query.
+--@param mrinfo_raw Raw DVMRP packet.
+--@param scrip Source IP of the packet.
+--@param dstip Destination IP to send to.
+local mrinfoQuery = function(mrinfo_raw, srcip, dstip)
+
+ local ip_raw = bin.pack("H", "45c00040ed780000400218bc0a00c8750a00c86b") .. mrinfo_raw -- Less ugly way to do it ?
+ local 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())
+
+ local sock = nmap.new_dnet()
+ sock:ip_open()
+ sock:ip_send(mrinfo_packet.buf)
+ sock:ip_close()
+end
+
+
+action = function(host)
+ local timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 5
+ local mrinfo_raw, dstip, srcip, interface
+ local results = {}
+
+ timeout = timeout * 1000
+
+ dstip = host.ip
+ interface = nmap.get_interface_info(host.interface)
+ srcip = interface.address
+
+ -- Thread that listens for responses
+ stdnse.new_thread(mrinfoListen, interface, host, timeout, results)
+
+ -- Send request
+ stdnse.sleep(0.5)
+ mrinfo_raw = mrinfoRaw()
+ mrinfoQuery(mrinfo_raw, srcip, dstip)
+ local condvar = nmap.condvar(results)
+ condvar("wait")
+
+ if #results > 0 then
+ local output, ifoutput = {}
+ local response = results[1]
+ table.insert(output, ("Version %s.%s"):format(response.majver, response.minver))
+ for _, interface in pairs(response.interfaces) do
+ ifoutput = {}
+ ifoutput.name = "Local address: " .. interface.address
+ for _, neighbor in pairs(interface.neighbors) do
+ if target.ALLOW_NEW_TARGETS then target.add(neighbor) end
+ table.insert(ifoutput, "Neighbor: " .. neighbor)
+ end
+ table.insert(output, ifoutput)
+ 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
diff --git a/scripts/script.db b/scripts/script.db
index 1ea61812c..409df9b5f 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -242,6 +242,7 @@ Entry { filename = "modbus-discover.nse", categories = { "discovery", "intrusive
Entry { filename = "mongodb-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "mongodb-databases.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "mongodb-info.nse", categories = { "default", "discovery", "safe", } }
+Entry { filename = "mrinfo.nse", categories = { "discovery", "safe", } }
Entry { filename = "ms-sql-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "ms-sql-config.nse", categories = { "discovery", "safe", } }
Entry { filename = "ms-sql-dac.nse", categories = { "discovery", "safe", } }