diff --git a/CHANGELOG b/CHANGELOG
index 224462efb..173f0a4b0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added llmnr-resolve script which resolves a hostname by using the LLMNR
+ (Link-Local Multicast Name Resolution) protocol. [Hani Benhabiles]
+
o [NSE] Added broadcast-igmp-discovery script which discovers and outputs
interesting information from targets that have multicast groups memberships.
[Hani Benhabiles]
diff --git a/scripts/llmnr-resolve.nse b/scripts/llmnr-resolve.nse
new file mode 100644
index 000000000..c3cda342e
--- /dev/null
+++ b/scripts/llmnr-resolve.nse
@@ -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
+-- 3 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(" 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
diff --git a/scripts/script.db b/scripts/script.db
index 9c5d34f31..dc11179f6 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -233,6 +233,7 @@ Entry { filename = "ldap-novell-getpass.nse", categories = { "discovery", "safe"
Entry { filename = "ldap-rootdse.nse", categories = { "discovery", "safe", } }
Entry { filename = "ldap-search.nse", categories = { "discovery", "safe", } }
Entry { filename = "lexmark-config.nse", categories = { "discovery", "safe", } }
+Entry { filename = "llmnr-resolve.nse", categories = { "broadcast", "discovery", "safe", } }
Entry { filename = "lltd-discovery.nse", categories = { "broadcast", "discovery", "safe", } }
Entry { filename = "maxdb-info.nse", categories = { "default", "version", } }
Entry { filename = "mcafee-epo-agent.nse", categories = { "safe", "version", } }