mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 13:11:28 +00:00
Added llmnr-resolve.nse script.
This commit is contained in:
211
scripts/llmnr-resolve.nse
Normal file
211
scripts/llmnr-resolve.nse
Normal file
@@ -0,0 +1,211 @@
|
||||
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"
|
||||
|
||||
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. Defaults to
|
||||
-- <code>3</code> seconds.
|
||||
--
|
||||
--@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
|
||||
--
|
||||
|
||||
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 = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 3
|
||||
local hostname = stdnse.get_script_args(SCRIPT_NAME .. ".hostname")
|
||||
timeout = timeout * 1000
|
||||
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
|
||||
if target.ALLOW_NEW_TARGETS then target.add(response.address) end
|
||||
table.insert(output, response.hostname.. " : " .. response.address)
|
||||
end
|
||||
if not target.ALLOW_NEW_TARGETS then
|
||||
table.insert(result,"Use the newtargets script-arg to add the results as targets")
|
||||
end
|
||||
return stdnse.format_output(true, output)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user