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