210 lines
6.3 KiB
Lua
210 lines
6.3 KiB
Lua
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
|