diff options
Diffstat (limited to 'scripts/lltd-discovery.nse')
-rw-r--r-- | scripts/lltd-discovery.nse | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/scripts/lltd-discovery.nse b/scripts/lltd-discovery.nse new file mode 100644 index 0000000..9a40ddf --- /dev/null +++ b/scripts/lltd-discovery.nse @@ -0,0 +1,329 @@ +local datafiles = require "datafiles" +local coroutine = require "coroutine" +local nmap = require "nmap" +local os = require "os" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" +local target = require "target" +local unicode = require "unicode" +local ipOps = require "ipOps" +local rand = require "rand" + +description = [[ +Uses the Microsoft LLTD protocol to discover hosts on a local network. + +For more information on the LLTD protocol please refer to +http://www.microsoft.com/whdc/connect/Rally/LLTD-spec.mspx +]] + +--- +-- @usage +-- nmap -e <interface> --script lltd-discovery +-- +-- @args lltd-discovery.interface string specifying which interface to do lltd discovery on. If not specified, all ethernet interfaces are tried. +-- @args lltd-discovery.timeout timespec specifying how long to listen for replies (default 30s) +-- +-- @output +-- | lltd-discovery: +-- | 192.168.1.64 +-- | Hostname: acer-PC +-- | Mac: 18:f4:6a:4f:de:a2 (Hon Hai Precision Ind. Co.) +-- | IPv6: fe80:0000:0000:0000:0000:0000:c0a8:0134 +-- | 192.168.1.33 +-- | Hostname: winxp-2b2955502 +-- | Mac: 08:00:27:79:fd:d2 (Cadmus Computer Systems) +-- | 192.168.1.22 +-- | Hostname: core +-- | Mac: 08:00:27:57:30:7f (Cadmus Computer Systems) +-- |_ Use the newtargets script-arg to add the results as targets +-- + +author = {"Gorjan Petrovski", "Hani Benhabiles"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"broadcast","discovery","safe"} + + +prerule = function() + if not nmap.is_privileged() then + nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {} + if not nmap.registry[SCRIPT_NAME].rootfail then + stdnse.verbose1("not running for lack of privileges.") + end + nmap.registry[SCRIPT_NAME].rootfail = true + return nil + end + + if nmap.address_family() ~= 'inet' then + stdnse.debug1("is IPv4 compatible only.") + return false + end + + return true +end + +--- Converts a 6 byte string into the familiar MAC address formatting +-- @param mac string containing the MAC address +-- @return formatted string suitable for printing +local function get_mac_addr( mac ) + local catch = function() return end + local try = nmap.new_try(catch) + local mac_prefixes = try(datafiles.parse_mac_prefixes()) + + if mac:len() ~= 6 then + return "Unknown" + else + local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3))) + local manuf = mac_prefixes[prefix] or "Unknown" + return string.format("%s (%s)", stdnse.format_mac(mac:sub(1,6)), manuf ) + end +end + +--- Gets a raw ethernet buffer with LLTD information and returns the responding host's IP and MAC +local parseHello = function(data) + -- HelloMsg = [ + -- ethernet_hdr = [mac_dst(6), mac_src(6), protocol(2)], + -- lltd_demultiplex_hdr = [version(1), type_of_service(1), reserved(1), function(1)], + -- base_hdr = [mac_dst(6), mac_src(6), seq_no(2)], + -- up_hello_hdr = [ generation_number(2), current_mapper_address(6), apparent_mapper_address(6), tlv_list(var) ] + --] + + --HelloStruct = { + -- mac_src, + -- sequence_number, + -- generation_number, + -- tlv_list(dict) + --} + local types = {"Host ID", "Characteristics", "Physical Medium", "Wireless Mode", "802.11 BSSID", + "802.11 SSID", "IPv4 Address", "IPv6 Address", "802.11 Max Operational Rate", + "Performance Counter Frequency", nil, "Link Speed", "802.11 RSSI", "Icon Image", "Machine Name", + "Support Information", "Friendly Name", "Device UUID", "Hardware ID", "QoS Characteristics", + "802.11 Physical Medium", "AP Association Table", "Detailed Icon Image", "Sees-List Working Set", + "Component Table", "Repeater AP Lineage", "Repeater AP Table"} + local mac = nil + local ipv4 = nil + local ipv6 = nil + local hostname = nil + + local pos = 1 + pos = pos + 6 + local mac_src = data:sub(pos,pos+5) + + pos = pos + 24 + local seq_no = data:sub(pos,pos+1) + + pos = pos + 2 + local generation_no = data:sub(pos,pos+1) + + pos = pos + 14 + local tlv = data:sub(pos) + + local tlv_list = {} + local p = 1 + while p < #tlv do + local t = tlv:byte(p) + if t == 0x00 then + break + else + p = p + 1 + local l = tlv:byte(p) + + p = p + 1 + local v = tlv:sub(p,p+l-1) + + if t == 0x01 then + -- Host ID (MAC Address) + mac = get_mac_addr(v:sub(1,6)) + elseif t == 0x08 then + ipv6 = ipOps.str_to_ip(v:sub(1,16)) + elseif t == 0x07 then + -- IPv4 address + ipv4 = ipOps.str_to_ip(v:sub(1,4)) + + -- Machine Name (Hostname) + elseif t == 0x0f then + hostname = unicode.utf16to8(v) + end + + p = p + l + + if ipv4 and ipv6 and mac and hostname then + break + end + end + end + + return ipv4, mac, ipv6, hostname +end + +--- Creates an LLTD Quick Discovery packet with the source MAC address +-- @param mac_src - six byte long binary string +local QuickDiscoveryPacket = function(mac_src) + local ethernet_hdr, demultiplex_hdr, base_hdr, discover_up_lev_hdr + + -- set up ethernet header = [ mac_dst, mac_src, protocol ] + local mac_dst = "\xFF\xFF\xFF\xFF\xFF\xFF" -- broadcast + local protocol = "\x88\xd9" -- LLTD ethertype + + ethernet_hdr = mac_dst .. mac_src .. protocol + + -- set up LLTD demultiplex header = [ version, type_of_service, reserved, function ] + local lltd_version = 1 -- Fixed Value + local lltd_type_of_service = 1 -- Type Of Service = Quick Discovery(0x01) + local lltd_reserved = 0 -- Fixed value + local lltd_function = 0 -- Function = QuickDiscovery->Discover (0x00) + + demultiplex_hdr = string.pack("BBBB", lltd_version, lltd_type_of_service, lltd_reserved, lltd_function ) + + -- set up LLTD base header = [ mac_dst, mac_src, seq_num(xid) ] + local lltd_seq_num = rand.random_string(2) + + base_hdr = mac_dst .. mac_src .. lltd_seq_num + + -- set up LLTD Upper Level Header = [ generation_number, number_of_stations, station_list ] + local generation_number = rand.random_string(2) + local number_of_stations = 0 + local station_list = string.rep("\0", 6*4) + + discover_up_lev_hdr = generation_number .. string.pack(">I2", number_of_stations) .. station_list + + -- put them all together and return + return ethernet_hdr .. demultiplex_hdr .. base_hdr .. discover_up_lev_hdr +end + +--- Runs a thread which discovers LLTD Responders on a certain interface +local LLTDDiscover = function(if_table, lltd_responders, timeout) + local timeout_s = 3 + local condvar = nmap.condvar(lltd_responders) + local pcap = nmap.new_socket() + pcap:set_timeout(5000) + + local dnet = nmap.new_dnet() + local try = nmap.new_try(function() dnet:ethernet_close() pcap:close() end) + + pcap:pcap_open(if_table.device, 256, false, "") + try(dnet:ethernet_open(if_table.device)) + + local packet = QuickDiscoveryPacket(if_table.mac) + try( dnet:ethernet_send(packet) ) + stdnse.sleep(0.5) + try( dnet:ethernet_send(packet) ) + + local start = os.time() + local start_s = os.time() + while true do + local status, plen, l2, l3, _ = pcap:pcap_receive() + if status then + local packet = l2..l3 + if stdnse.tohex(packet:sub(13,14)) == "88d9" then + start_s = os.time() + + local ipv4, mac, ipv6, hostname = parseHello(packet) + + if ipv4 then + if not lltd_responders[ipv4] then + lltd_responders[ipv4] = {} + lltd_responders[ipv4].hostname = hostname + lltd_responders[ipv4].mac = mac + lltd_responders[ipv4].ipv6 = ipv6 + end + end + else + if os.time() - start_s > timeout_s then + break + end + end + else + break + end + + if os.time() - start > timeout then + break + end + end + dnet:ethernet_close() + pcap:close() + condvar("signal") +end + + +action = function() + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) + timeout = timeout or 30 + + --get interface script-args, if any + local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface") + local interface_opt = nmap.get_interface() + + -- interfaces list (decide which interfaces to broadcast on) + local interfaces ={} + if interface_opt or interface_arg then + -- single interface defined + local interface = interface_opt or interface_arg + local if_table = nmap.get_interface_info(interface) + if not (if_table and if_table.address and if_table.link=="ethernet") then + stdnse.debug1("Interface not supported or not properly configured.") + return false + end + table.insert(interfaces, if_table) + else + local tmp_ifaces = nmap.list_interfaces() + for _, if_table in ipairs(tmp_ifaces) do + if if_table.address and + if_table.link=="ethernet" and + if_table.address:match("%d+%.%d+%.%d+%.%d+") then + + table.insert(interfaces, if_table) + end + end + end + + if #interfaces == 0 then + stdnse.debug1("No interfaces found.") + return + end + + local lltd_responders={} + local threads ={} + local condvar = nmap.condvar(lltd_responders) + + -- party time + for _, if_table in ipairs(interfaces) do + -- create a thread for each interface + local co = stdnse.new_thread(LLTDDiscover, if_table, lltd_responders, timeout) + threads[co]=true + end + + repeat + for thread in pairs(threads) do + if coroutine.status(thread) == "dead" then threads[thread] = nil end + end + if ( next(threads) ) then + condvar "wait" + end + until next(threads) == nil + + -- generate output + local output = {} + for ip_addr, info in pairs(lltd_responders) do + if target.ALLOW_NEW_TARGETS then target.add(ip_addr) end + + local s = {} + s.name = ip_addr + if info.hostname then + table.insert(s, "Hostname: " .. info.hostname) + end + if info.mac then + table.insert(s, "Mac: " .. info.mac) + end + if info.ipv6 then + table.insert(s, "IPv6: " .. info.ipv6) + end + table.insert(output,s) + end + + if #output>0 and 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( (#output>0), output ) +end |