mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 06:01:28 +00:00
Many scripts were documented as using timespecs (10s, 5000ms, etc) for timeout script-args, but one 1 or 2 actually did. Now all timeout script-args will accept timespecs, except those which took a number of milliseconds, which remain unchanged. Also fixed some documentation issues (missing script name in arg description, missing nsedoc for args, etc)
295 lines
9.3 KiB
Lua
295 lines
9.3 KiB
Lua
local nmap = require "nmap"
|
|
local packet = require "packet"
|
|
local ipOps = require "ipOps"
|
|
local bin = require "bin"
|
|
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 http://nmap.org/book/man-legal.html"
|
|
|
|
categories = {"discovery", "safe", "broadcast"}
|
|
|
|
|
|
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 for lack of privileges.", 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, address, neighbor
|
|
local response = {}
|
|
|
|
-- first byte should be IGMP type == 0x13 (DVMRP)
|
|
if data:byte(1) ~= 0x13 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.addresses = {}
|
|
-- Iterate over target local addresses (interfaces)
|
|
while index < #data do
|
|
if data:byte(index) == 0x00 then break end
|
|
address = {}
|
|
-- Local address
|
|
index, address.ip = bin.unpack("<I", data, index)
|
|
address.ip = ipOps.fromdword(address.ip)
|
|
-- Link metric
|
|
index, address.metric = bin.unpack(">C", data, index)
|
|
-- Treshold
|
|
index, address.treshold= bin.unpack(">C", data, index)
|
|
-- Flags
|
|
index, address.flags = bin.unpack(">C", data, index)
|
|
-- Number of neighbors
|
|
index, address.ncount = bin.unpack(">C", data, index)
|
|
|
|
address.neighbors = {}
|
|
-- Iterate over neighbors
|
|
for i = 1, address.ncount do
|
|
index, neighbor = bin.unpack("<I", data, index)
|
|
table.insert(address.neighbors, ipOps.fromdword(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()
|
|
-- Type: DVMRP
|
|
local mrinfo_raw = bin.pack(">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 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 = bin.pack("H", "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 = bin.pack("HAH", "01 00 5e 00 00 01", interface.mac, "08 00")
|
|
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.print_verbose("%s: %s", SCRIPT_NAME, err)
|
|
return
|
|
end
|
|
local status, address, _, _, _ = sock:get_info()
|
|
if not status then
|
|
stdnse.print_verbose("%s: %s", SCRIPT_NAME, 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 ("\n ERROR: Couldn't get interface for %s"):format(target)
|
|
end
|
|
|
|
stdnse.print_debug("%s: will send to %s via %s interface.", SCRIPT_NAME, 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
|