diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/llmnr-resolve.nse | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/scripts/llmnr-resolve.nse b/scripts/llmnr-resolve.nse new file mode 100644 index 0000000..25d2acd --- /dev/null +++ b/scripts/llmnr-resolve.nse @@ -0,0 +1,209 @@ +local nmap = require "nmap" +local stdnse = require "stdnse" +local table = require "table" +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-resolve: +-- | 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.verbose1("not running due to lack of privileges.") + return false + end + return true +end + +author = "Hani Benhabiles" + +license = "Same as Nmap--See https://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) + return string.pack(">I2I2I2I2I2I2 s1x I2I2", + math.random(0,65535), -- transaction ID + 0x0000, -- Flags: Standard Query + 0x0001, -- Questions = 1 + 0x0000, -- Answer RRs = 0 + 0x0000, -- Authority RRs = 0 + 0x0000, -- Additional RRs = 0 + hostname, -- Hostname + 0x0001, -- Type: Host Address + 0x0001) -- Class: IN +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.debug1("%s", err) + return + end + sock:send(query) + sock:close() +end + +-- Listens for llmnr responses +-- @param interface Network interface to listen on. +-- @param timeout Maximum time to listen. +-- @param 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, flags, questions = string.unpack(">I2 I2 I2", llmnr) + + -- Make verifications + -- Message == Response bit + -- and 1 Question (hostname we requested) and + if ((flags >> 15) == 1) and questions == 0x01 then + stdnse.debug1("got response from %s", p.ip_src) + -- Skip header's 12 bytes + -- extract host length + local qlen, index = string.unpack(">B", llmnr, 13) + -- Skip hostname, null byte, type field and class field + index = index + qlen + 1 + 2 + 2 + + -- Now, answer record + local response, alen = {} + -- Extract hostname with the correct case sensitivity. + response.hostname, index = string.unpack(">s1x", llmnr, index) + + -- skip type, class, ttl, dlen + index = index + 2 + 2 + 4 + 2 + response.address, index = string.unpack(">c4", llmnr, index) + response.address = ipOps.str_to_ip(response.address) + table.insert(result, response) + else + stdnse.debug1("skipped llmnr response.") + 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.verbose1("%s", err) + return + end + local status, address, _, _, _ = sock:get_info() + if not status then + stdnse.verbose1("%s", 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.debug1("no hostname was provided.") + 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 stdnse.format_output(false, ("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 |