mirror of
https://github.com/nmap/nmap.git
synced 2025-12-08 05:31:31 +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)
216 lines
6.9 KiB
Lua
216 lines
6.9 KiB
Lua
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local table = require "table"
|
|
local bin = require "bin"
|
|
local bit = require "bit"
|
|
local packet = require "packet"
|
|
local ipOps = require "ipOps"
|
|
local target = require "target"
|
|
local math = require "math"
|
|
local string = require "string"
|
|
|
|
description = [[
|
|
Resolves a hostname by using the LLMNR (Link-Local Multicast Name Resolution) protocol.
|
|
|
|
The script works by sending a LLMNR Standard Query containing the hostname to
|
|
the 5355 UDP port on the 224.0.0.252 multicast address. It listens for any
|
|
LLMNR responses that are sent to the local machine with a 5355 UDP source port.
|
|
A hostname to resolve must be provided.
|
|
|
|
For more information, see:
|
|
* http://technet.microsoft.com/en-us/library/bb878128.aspx
|
|
]]
|
|
|
|
---
|
|
--@args llmnr-resolve.hostname Hostname to resolve.
|
|
--
|
|
--@args llmnr-resolve.timeout Max time to wait for a response. (default 3s)
|
|
--
|
|
--@usage
|
|
-- nmap --script llmnr-resolve --script-args 'llmnr-resolve.hostname=examplename' -e wlan0
|
|
--
|
|
--@output
|
|
-- Pre-scan script results:
|
|
-- | llmnr-query:
|
|
-- | acer-PC : 192.168.1.4
|
|
-- |_ Use the newtargets script-arg to add the results as targets
|
|
--
|
|
|
|
prerule = function()
|
|
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"}
|
|
|
|
|
|
--- Returns a raw llmnr query
|
|
-- @param hostname Hostname to query for.
|
|
-- @return query Raw llmnr query.
|
|
local llmnrQuery = function(hostname)
|
|
local query = bin.pack(">S", math.random(0,65535)) -- transaction ID
|
|
query = query .. bin.pack(">S", 0x0000) -- Flags: Standard Query
|
|
query = query .. bin.pack(">S", 0x0001) -- Questions = 1
|
|
query = query .. bin.pack(">S", 0x0000) -- Answer RRs = 0
|
|
query = query .. bin.pack(">S", 0x0000) -- Authority RRs = 0
|
|
query = query .. bin.pack(">S", 0x0000) -- Additional RRs = 0
|
|
query = query .. bin.pack(">CAC", #hostname, hostname, 0x00) -- Hostname
|
|
query = query .. bin.pack(">S", 0x0001) -- Type: Host Address
|
|
query = query .. bin.pack(">S", 0x0001) -- Class: IN
|
|
return query
|
|
end
|
|
|
|
--- Sends a llmnr query.
|
|
-- @param query Query to send.
|
|
local llmnrSend = function(query, mcast, mport)
|
|
-- Multicast IP and UDP port
|
|
local sock = nmap.new_socket()
|
|
local status, err = sock:connect(mcast, mport, "udp")
|
|
if not status then
|
|
stdnse.print_debug("%s: %s", SCRIPT_NAME, err)
|
|
return
|
|
end
|
|
sock:send(query)
|
|
sock:close()
|
|
end
|
|
|
|
-- Listens for llmnr responses
|
|
-- @interface Network interface to listen on.
|
|
-- @timeout Maximum time to listen.
|
|
-- @result table to put responses into.
|
|
local llmnrListen = function(interface, timeout, result)
|
|
local condvar = nmap.condvar(result)
|
|
local start = nmap.clock_ms()
|
|
local listener = nmap.new_socket()
|
|
local status, l3data, _
|
|
|
|
-- packets that are sent to our UDP port number 5355
|
|
local filter = 'dst host ' .. interface.address .. ' and udp src port 5355'
|
|
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
|
|
local p = packet.Packet:new(l3data, #l3data)
|
|
-- Skip IP and UDP headers
|
|
local llmnr = string.sub(l3data, p.ip_hl*4 + 8 + 1)
|
|
-- Flags
|
|
local _, trans = bin.unpack(">S", llmnr)
|
|
local _, flags = bin.unpack(">S", llmnr, 3)
|
|
-- Questions number
|
|
local _, questions = bin.unpack(">S", llmnr, 5)
|
|
|
|
-- Make verifications
|
|
-- Message == Response bit
|
|
-- and 1 Question (hostname we requested) and
|
|
if (bit.rshift(flags, 15) == 1) and questions == 0x01 then
|
|
stdnse.print_debug("%s got response from %s", SCRIPT_NAME, p.ip_src)
|
|
-- Skip header's 12 bytes
|
|
-- extract host length
|
|
local index, qlen = bin.unpack(">C", llmnr, 13)
|
|
-- Skip hostname, null byte, type field and class field
|
|
index = index + qlen + 1 + 2 + 2
|
|
|
|
-- Now, answer record
|
|
local response, alen = {}
|
|
index, alen = bin.unpack(">C", llmnr, index)
|
|
-- Extract hostname with the correct case sensivity.
|
|
index, response.hostname = bin.unpack(">A".. alen, llmnr, index)
|
|
|
|
-- skip null byte, type, class, ttl, dlen
|
|
index = index + 1 + 2 + 2 + 4 + 2
|
|
index, response.address = bin.unpack("<I", llmnr, index)
|
|
response.address = ipOps.fromdword(response.address)
|
|
table.insert(result, response)
|
|
else
|
|
stdnse.print_debug("%s skipped llmnr response.", SCRIPT_NAME)
|
|
end
|
|
end
|
|
end
|
|
condvar("signal")
|
|
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 3) * 1000
|
|
local hostname = stdnse.get_script_args(SCRIPT_NAME .. ".hostname")
|
|
local result, output = {}, {}
|
|
local mcast = "224.0.0.252"
|
|
local mport = 5355
|
|
|
|
-- Check if a valid hostname was provided
|
|
if not hostname or #hostname == 0 then
|
|
stdnse.print_debug("%s no hostname was provided.", SCRIPT_NAME)
|
|
return
|
|
end
|
|
|
|
-- Check if a valid interface was provided
|
|
local interface = nmap.get_interface()
|
|
if interface then
|
|
interface = nmap.get_interface_info(interface)
|
|
else
|
|
interface = getInterface(mcast)
|
|
end
|
|
if not interface then
|
|
return ("\n ERROR: Couldn't get interface for %s"):format(mcast)
|
|
end
|
|
|
|
-- Launch listener thread
|
|
stdnse.new_thread(llmnrListen, interface, timeout, result)
|
|
-- Craft raw query
|
|
local query = llmnrQuery(hostname)
|
|
-- Small sleep so the listener doesn't miss the response
|
|
stdnse.sleep(0.5)
|
|
-- Send query
|
|
llmnrSend(query, mcast, mport)
|
|
-- Wait for listener thread to finish
|
|
local condvar = nmap.condvar(result)
|
|
condvar("wait")
|
|
|
|
-- Check responses
|
|
if #result > 0 then
|
|
for _, response in pairs(result) do
|
|
table.insert(output, response.hostname.. " : " .. response.address)
|
|
if target.ALLOW_NEW_TARGETS then
|
|
target.add(response.address)
|
|
end
|
|
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
|