mirror of
https://github.com/nmap/nmap.git
synced 2025-12-08 05:31:31 +00:00
Added broadcast-igmp-discovery script.
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added broadcast-igmp-discovery script which discovers and outputs
|
||||||
|
interesting information from targets that have multicast groups memberships.
|
||||||
|
[Hani Benhabiles]
|
||||||
|
|
||||||
o Scripts can now return a structured name-value table so that results
|
o Scripts can now return a structured name-value table so that results
|
||||||
are queryable from XML output. Scripts can return a string as before,
|
are queryable from XML output. Scripts can return a string as before,
|
||||||
or a table, or a table and a string. In this last case, the table will
|
or a table, or a table and a string. In this last case, the table will
|
||||||
@@ -18,7 +22,7 @@ o [NPING] Nping now prints out an error and exists when the user tries to use
|
|||||||
o [NSE] Added smb-print-text script which prints specified text using SMB
|
o [NSE] Added smb-print-text script which prints specified text using SMB
|
||||||
shared printer. [Aleksandar Nikolic]
|
shared printer. [Aleksandar Nikolic]
|
||||||
|
|
||||||
o [NSE] Added mrinfo script whiches queries a target router for multicast
|
o [NSE] Added mrinfo script which queries a target router for multicast
|
||||||
information. [Hani Benhabiles]
|
information. [Hani Benhabiles]
|
||||||
|
|
||||||
o [NSE] Added ssl-date script which gets server's time from SSL ServerHello
|
o [NSE] Added ssl-date script which gets server's time from SSL ServerHello
|
||||||
|
|||||||
360
scripts/broadcast-igmp-discovery.nse
Normal file
360
scripts/broadcast-igmp-discovery.nse
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
local nmap = require "nmap"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local table = require "table"
|
||||||
|
local bin = require "bin"
|
||||||
|
local packet = require "packet"
|
||||||
|
local ipOps = require "ipOps"
|
||||||
|
local target = require "target"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Discovers targets that have IGMP Multicast memberships and grabs interesting information.
|
||||||
|
|
||||||
|
The scripts works by sending IGMP Membership Query message to the 224.0.0.1 All
|
||||||
|
Hosts multicast address and listening for IGMP Membership Report messages. The
|
||||||
|
script then extracts all the interesting information from the report messages
|
||||||
|
such as the version, group, mode, source addresses (depending on the version).
|
||||||
|
|
||||||
|
The script defaults to sending an IGMPv2 Query but this could be changed to
|
||||||
|
another version (version 1 or 3) or to sending queries of all three version. If
|
||||||
|
no interface was specified as a script argument or with the -e option, the
|
||||||
|
script will proceed to sending queries through all the valid ethernet
|
||||||
|
interfaces.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @args broadcast-igmp-discovery.timeout Time to wait for reports in seconds.
|
||||||
|
-- Defaults to <code>5</code> seconds.
|
||||||
|
--
|
||||||
|
-- @args broadcast-igmp-discovery.version IGMP version to use. Could be
|
||||||
|
-- <code>1</code>, <code>2</code>, <code>3</code> or <code>all</code>. Defaults to <code>2</code>
|
||||||
|
--
|
||||||
|
-- @args broadcast-igmp-discovery.interface Network interface to use.
|
||||||
|
--
|
||||||
|
--@usage
|
||||||
|
-- nmap --script broadcast-igmp-discovery
|
||||||
|
-- nmap --script broadcast-igmp-discovery -e wlan0
|
||||||
|
-- nmap --script broadcast-igmp-discovery
|
||||||
|
-- --script-args 'broadcast-igmp-discovery.version=all, broadcast-igmp-discovery.timeout=3'
|
||||||
|
--
|
||||||
|
--@output
|
||||||
|
--Pre-scan script results:
|
||||||
|
-- | broadcast-igmp-discovery:
|
||||||
|
-- | 192.168.2.2
|
||||||
|
-- | Interface: tap0
|
||||||
|
-- | Version: 3
|
||||||
|
-- | Group: 239.1.1.1
|
||||||
|
-- | Mode: EXCLUDE
|
||||||
|
-- | Group: 239.1.1.2
|
||||||
|
-- | Mode: EXCLUDE
|
||||||
|
-- | Group: 239.1.1.44
|
||||||
|
-- | Mode: INCLUDE
|
||||||
|
-- | Sources:
|
||||||
|
-- | 192.168.31.1
|
||||||
|
-- | 192.168.1.3
|
||||||
|
-- | Interface: wlan0
|
||||||
|
-- | Version: 2
|
||||||
|
-- | Group: 239.255.255.250
|
||||||
|
-- | 192.168.1.3
|
||||||
|
-- | Interface: wlan0
|
||||||
|
-- | Version: 2
|
||||||
|
-- | Group: 239.255.255.253
|
||||||
|
-- |_ Use the newtargets script-arg to add the results as targets
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
prerule = function()
|
||||||
|
if nmap.address_family() ~= 'inet' then
|
||||||
|
stdnse.print_verbose("%s is IPv4 only.", SCRIPT_NAME)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if ( not(nmap.is_privileged()) ) then
|
||||||
|
stdnse.print_verbose("%s not running due to lack of privileges.", SCRIPT_NAME)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
author = "Hani Benhabiles"
|
||||||
|
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
|
categories = {"discovery", "safe", "broadcast"}
|
||||||
|
|
||||||
|
--- Parses a raw igmp packet and return a structred packet.
|
||||||
|
-- @param data string IGMP Raw packet.
|
||||||
|
-- @return response table Structured igmp packet.
|
||||||
|
local igmpParse = function(data)
|
||||||
|
local index
|
||||||
|
local response = {}
|
||||||
|
local group, source
|
||||||
|
-- Report type (0x12 == v1, 0x16 == v2, 0x22 == v3)
|
||||||
|
index, response.type = bin.unpack(">C", data, index)
|
||||||
|
if response.type == 0x12 or response.type == 0x16 then
|
||||||
|
-- Max response time
|
||||||
|
index, response.maxrt = bin.unpack(">C", data, index)
|
||||||
|
-- Checksum
|
||||||
|
index, response.checksum = bin.unpack(">S", data, index)
|
||||||
|
-- Multicast group
|
||||||
|
index, response.group = bin.unpack("<I", data, index)
|
||||||
|
response.group = ipOps.fromdword(response.group)
|
||||||
|
return response
|
||||||
|
elseif response.type == 0x22 and #data >= 12 then
|
||||||
|
-- Skip reserved byte
|
||||||
|
index = index + 1
|
||||||
|
-- Checksum
|
||||||
|
index, response.checksum = bin.unpack(">S", data, index)
|
||||||
|
-- Skip reserved byte
|
||||||
|
index = index + 2
|
||||||
|
-- Number of groups
|
||||||
|
index, response.ngroups = bin.unpack(">S", data, index)
|
||||||
|
response.groups = {}
|
||||||
|
for i=1,response.ngroups do
|
||||||
|
group = {}
|
||||||
|
-- Mode is either INCLUDE or EXCLUDE
|
||||||
|
index, group.mode = bin.unpack(">C", data, index)
|
||||||
|
-- Auxiliary data length in the group record (in 32bits units)
|
||||||
|
index, group.auxdlen = bin.unpack(">C", data, index)
|
||||||
|
-- Number of source addresses
|
||||||
|
index, group.nsrc = bin.unpack(">S", data, index)
|
||||||
|
index, group.address = bin.unpack("<I", data, index)
|
||||||
|
group.address = ipOps.fromdword(group.address)
|
||||||
|
group.src = {}
|
||||||
|
if group.nsrc > 0 then
|
||||||
|
for i=1,group.nsrc do
|
||||||
|
index, source = bin.unpack("<I", data, index)
|
||||||
|
table.insert(group.src, ipOps.fromdword(source))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Skip auxiliary data
|
||||||
|
index = index + group.auxdlen
|
||||||
|
-- Insert group
|
||||||
|
table.insert(response.groups, group)
|
||||||
|
end
|
||||||
|
return response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Listens for IGMP Membership reports packets.
|
||||||
|
-- @param interface Interface to listen on.
|
||||||
|
-- @param timeout Amount of time to listen for.
|
||||||
|
-- @param responses table to put valid responses into.
|
||||||
|
local igmpListener = function(interface, timeout, responses)
|
||||||
|
local condvar = nmap.condvar(responses)
|
||||||
|
local start = nmap.clock_ms()
|
||||||
|
local listener = nmap.new_socket()
|
||||||
|
local p, igmp_raw, status, l3data, response
|
||||||
|
local devices = {}
|
||||||
|
listener:set_timeout(100)
|
||||||
|
listener:pcap_open(interface.device, 1024, true, 'ip proto 2')
|
||||||
|
|
||||||
|
while (nmap.clock_ms() - start) < timeout do
|
||||||
|
status, _, _, l3data = listener:pcap_receive()
|
||||||
|
if status then
|
||||||
|
p = packet.Packet:new(l3data, #l3data)
|
||||||
|
igmp_raw = string.sub(l3data, p.ip_hl*4 + 1)
|
||||||
|
if p then
|
||||||
|
-- check the first byte before sending to the parser
|
||||||
|
-- response 0x12 == Membership Response version 1
|
||||||
|
-- response 0x16 == Membership Response version 2
|
||||||
|
-- response 0x22 == Membership Response version 3
|
||||||
|
local igmptype = igmp_raw:byte(1)
|
||||||
|
if igmptype == 0x12 or igmptype == 0x16 or igmptype == 0x22 then
|
||||||
|
response = igmpParse(igmp_raw)
|
||||||
|
if response then
|
||||||
|
response.src = p.ip_src
|
||||||
|
response.interface = interface.shortname
|
||||||
|
-- Many hosts return more than one same response message
|
||||||
|
-- this is to not output duplicates
|
||||||
|
if not devices[response.src..response.type..(response.group or response.ngroups)] then
|
||||||
|
devices[response.src..response.type..(response.group or response.ngroups)] = true
|
||||||
|
table.insert(responses, response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
condvar("signal")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Crafts a raw IGMP packet.
|
||||||
|
-- @param interface Source interface of the packet.
|
||||||
|
-- @param vesion IGMP version. Could be 1, 2 or 3.
|
||||||
|
-- @return string Raw IGMP packet.
|
||||||
|
local igmpRaw = function(interface, version)
|
||||||
|
-- Only 1, 2 and 3 are valid IGMP versions
|
||||||
|
if version ~= 1 and version ~= 2 and version ~= 3 then
|
||||||
|
stdnse.print_debug("IGMP version %s doesn't exist.", version)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Let's craft an IGMP Membership Query
|
||||||
|
local igmp_raw = bin.pack(">C", 0x11) -- Membership Query, same for all versions
|
||||||
|
if version == 1 then
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">C", 0x00) -- Unused, 0x00 for version 1 only
|
||||||
|
else
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">C", 0x16) -- Max response time: 10 Seconds, for version 2 and 3
|
||||||
|
end
|
||||||
|
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">S", 0x00) -- Checksum, calculated later
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">I", 0x00) -- Multicast Address: 0.0.0.0
|
||||||
|
|
||||||
|
if version == 3 then
|
||||||
|
-- Reserved = 4 bits (Should be zeroed)
|
||||||
|
-- Supress Flag = 1 bit
|
||||||
|
-- QRV (Querier's Robustness Variable) = 3 bits
|
||||||
|
-- all are set to 0
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">C", 0x00)
|
||||||
|
-- QQIC (Querier's Query Interval Code) in seconds = Set to 0 to get insta replies.
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">C", 0x10)
|
||||||
|
-- Number of sources (in the next arrays) = 1 ( Our IP only)
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">S", 0x01)
|
||||||
|
-- Source = Our IP address
|
||||||
|
igmp_raw = igmp_raw .. bin.pack(">I", ipOps.todword(interface.address))
|
||||||
|
end
|
||||||
|
|
||||||
|
igmp_raw = igmp_raw:sub(1,2) .. bin.pack(">S", packet.in_cksum(igmp_raw)) .. igmp_raw:sub(5)
|
||||||
|
|
||||||
|
return igmp_raw
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local igmpQuery;
|
||||||
|
--- Sends an IGMP Membership query.
|
||||||
|
-- @param interface Network interface to send on.
|
||||||
|
-- @param vesion IGMP version. Could be 1, 2, 3 or all.
|
||||||
|
igmpQuery = function(interface, version)
|
||||||
|
local srcip = interface.address
|
||||||
|
local dstip = "224.0.0.1"
|
||||||
|
|
||||||
|
if version == 'all' then
|
||||||
|
-- Small pause to let listener begin and not miss reports.
|
||||||
|
stdnse.sleep(0.5)
|
||||||
|
igmpQuery(interface, 3)
|
||||||
|
igmpQuery(interface, 2)
|
||||||
|
igmpQuery(interface, 1)
|
||||||
|
else
|
||||||
|
local igmp_raw = igmpRaw(interface, version)
|
||||||
|
|
||||||
|
local ip_raw = bin.pack("H", "45c00040ed780000010218bc0a00c8750a00c86b") .. igmp_raw
|
||||||
|
local igmp_packet = packet.Packet:new(ip_raw, ip_raw:len())
|
||||||
|
igmp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip))
|
||||||
|
igmp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip))
|
||||||
|
igmp_packet:ip_set_len(#igmp_packet.buf)
|
||||||
|
igmp_packet:ip_count_checksum()
|
||||||
|
|
||||||
|
local sock = nmap.new_dnet()
|
||||||
|
sock:ethernet_open(interface.device)
|
||||||
|
|
||||||
|
-- Ethernet IPv4 multicast, our ethernet address and type IP
|
||||||
|
local eth_hdr = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00")
|
||||||
|
sock:ethernet_send(eth_hdr .. igmp_packet.buf)
|
||||||
|
sock:ethernet_close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Function to compare wieght of an IGMP response message.
|
||||||
|
-- Used to sort elements in responses table.
|
||||||
|
local respCompare = function(a,b)
|
||||||
|
return ipOps.todword(a.src) + a.type + (a.ngroups or ipOps.todword(a.group))
|
||||||
|
< ipOps.todword(b.src) + b.type + (b.ngroups or ipOps.todword(b.group))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 7
|
||||||
|
local version = stdnse.get_script_args(SCRIPT_NAME .. ".version") or 2
|
||||||
|
local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
|
||||||
|
timeout = timeout * 1000
|
||||||
|
if version ~= 'all' then
|
||||||
|
version = tonumber(version)
|
||||||
|
end
|
||||||
|
|
||||||
|
local responses, results, interfaces, lthreads = {}, {}, {}, {}
|
||||||
|
local result, grouptable, sourcetable
|
||||||
|
|
||||||
|
-- Check the interface
|
||||||
|
interface = interface or nmap.get_interface()
|
||||||
|
if interface then
|
||||||
|
-- Get the interface information
|
||||||
|
interface = nmap.get_interface_info(interface)
|
||||||
|
if not interface then
|
||||||
|
return ("ERROR: Failed to retreive %s interface information."):format(interface)
|
||||||
|
end
|
||||||
|
interfaces = {interface}
|
||||||
|
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, interface.shortname)
|
||||||
|
else
|
||||||
|
local ifacelist = nmap.list_interfaces()
|
||||||
|
for _, iface in ipairs(ifacelist) do
|
||||||
|
-- Match all ethernet interfaces
|
||||||
|
if iface.address and iface.link=="ethernet" and
|
||||||
|
iface.address:match("%d+%.%d+%.%d+%.%d+") then
|
||||||
|
|
||||||
|
stdnse.print_debug("%s: Will use %s interface.", SCRIPT_NAME, iface.shortname)
|
||||||
|
table.insert(interfaces, iface)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- We should iterate over interfaces
|
||||||
|
for _, interface in pairs(interfaces) do
|
||||||
|
local co = stdnse.new_thread(igmpListener, interface, timeout, responses)
|
||||||
|
igmpQuery(interface, version)
|
||||||
|
lthreads[co] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local condvar = nmap.condvar(responses)
|
||||||
|
-- Wait for the listening threads to finish
|
||||||
|
repeat
|
||||||
|
condvar("wait")
|
||||||
|
for thread in pairs(lthreads) do
|
||||||
|
if coroutine.status(thread) == "dead" then lthreads[thread] = nil end
|
||||||
|
end
|
||||||
|
until next(lthreads) == nil;
|
||||||
|
|
||||||
|
-- Output useful info from the responses
|
||||||
|
if #responses > 0 then
|
||||||
|
-- We should sort our list here.
|
||||||
|
-- This is useful to have consistent results for tools such as Ndiff.
|
||||||
|
table.sort(responses, respCompare)
|
||||||
|
|
||||||
|
for _, response in pairs(responses) do
|
||||||
|
result = {}
|
||||||
|
result.name = response.src
|
||||||
|
table.insert(result, "Interface: " .. response.interface)
|
||||||
|
-- Add to new targets if newtargets script arg provided
|
||||||
|
if target.ALLOW_NEW_TARGETS then target.add(response.src) end
|
||||||
|
if response.type == 0x12 then
|
||||||
|
table.insert(result, "Version: 1")
|
||||||
|
table.insert(result, "Multicast group: ".. response.group)
|
||||||
|
elseif response.type == 0x16 then
|
||||||
|
table.insert(result, "Version: 2")
|
||||||
|
table.insert(result, "Group: ".. response.group)
|
||||||
|
elseif response.type == 0x22 then
|
||||||
|
table.insert(result, "Version: 3")
|
||||||
|
for _, group in pairs(response.groups) do
|
||||||
|
grouptable = {}
|
||||||
|
grouptable.name = "Group: " .. group.address
|
||||||
|
if group.mode == 0x01 then
|
||||||
|
table.insert(grouptable, "Mode: INCLUDE")
|
||||||
|
elseif group.mode == 0x02 then
|
||||||
|
table.insert(grouptable, "Mode: EXCLUDE")
|
||||||
|
end
|
||||||
|
if group.nsrc > 0 then
|
||||||
|
sourcetable = {}
|
||||||
|
sourcetable.name = "Sources:"
|
||||||
|
table.insert(sourcetable, group.src)
|
||||||
|
table.insert(grouptable, sourcetable)
|
||||||
|
end
|
||||||
|
table.insert(result, grouptable)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(results, result)
|
||||||
|
end
|
||||||
|
if #results>0 and not target.ALLOW_NEW_TARGETS then
|
||||||
|
table.insert(results,"Use the newtargets script-arg to add the results as targets")
|
||||||
|
end
|
||||||
|
return stdnse.format_output(true, results)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -30,6 +30,7 @@ Entry { filename = "broadcast-dhcp-discover.nse", categories = { "broadcast", "s
|
|||||||
Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast", "safe", } }
|
||||||
|
Entry { filename = "broadcast-igmp-discovery.nse", categories = { "discovery", "safe", "broadcast", } }
|
||||||
Entry { filename = "broadcast-listener.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-listener.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-ms-sql-discover.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-ms-sql-discover.nse", categories = { "broadcast", "safe", } }
|
||||||
Entry { filename = "broadcast-netbios-master-browser.nse", categories = { "broadcast", "safe", } }
|
Entry { filename = "broadcast-netbios-master-browser.nse", categories = { "broadcast", "safe", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user