id = "NBSTAT" description = "Sends a NetBIOS query to target host to try to determine \ the NetBIOS name and MAC address." author = "Brandon Enright " license = "See nmaps COPYING for licence" -- This script was created by reverse-engineering the packets -- sent by NBTSCAN and hacking with the Wireshark NetBIOS -- protocol dissector. I do not believe this constitutes -- a derivative work in the GPL sense of the phrase. categories = {"discovery", "safe"} -- I have excluded the port function param because it doesn't make much sense -- for a hostrule. It works without warning. The NSE documentation is -- not explicit enough in this regard. hostrule = function(host) -- The following is an attempt to only run this script against hosts -- that will probably respond to a UDP 137 probe. One might argue -- that sending a single UDP packet and waiting for a response is no -- big deal and that it should be done for every host. In that case -- simply change this rule to always return true. local port_t135 = nmap.get_port_state(host, {number=135, protocol="tcp"}) local port_t139 = nmap.get_port_state(host, {number=139, protocol="tcp"}) local port_t445 = nmap.get_port_state(host, {number=445, protocol="tcp"}) local port_u137 = nmap.get_port_state(host, {number=137, protocol="udp"}) if ( (port_t135 ~= nil and port_t135.state == "open") or (port_t139 ~= nil and port_t139.state == "open") or (port_t445 ~= nil and port_t445.state == "open") or (port_u137 ~= nil and (port_u137.state == "open" or port_u137.state == "open|filtered"))) then return true else return false end end -- Again, I have excluded the port param. Is this okay on a hostrule? action = function(host) local socket = nmap.new_socket() socket:set_timeout(5000) local result local status = true status, result = socket:connect(host.ip, 137, "udp") if (not status) then -- Can a UDP connect ever fail? return end -- This is the UDP NetBIOS request packet. I didn't feel like -- actually generating a new one each time so this has been shamelessly -- copied from a packet dump of nbtscan. -- See http://www.unixwiz.net/tools/nbtscan.html for code. -- The magic number in this code is \000\097. status, result = socket:send( "\000\097\000\016\000\001\000\000" .. "\000\000\000\000\032\067\075\065" .. "\065\065\065\065\065\065\065\065" .. "\065\065\065\065\065\065\065\065" .. "\065\065\065\065\065\065\065\065" .. "\065\065\065\065\065\000\000\033" .. "\000\001") if (not status) then -- Can the first UDP send ever fail? return end -- this receive_bytes will consume all the input available -- with a minimum of 1 byte. status, result = socket:receive_bytes(1); -- We don't need this socket anymore socket:close() if (not status) then return end if (result == "TIMEOUT") then return end -- We got data back from 137, make sure we know it is open nmap.set_port_state(host, {number=137, protocol="udp"}, "open") -- Magic numbers: -- Offset to number of names returned: 57 -- Useful name length: 14 -- Length of each name + name type: 18 -- Length of MAC address: 6 if (string.len(result) < 57) then return end -- Make sure the response at least looks like a NBTSTAT response -- The first 2 bytes are the magic number sent originally, The second -- 2 bytes should be 0x84 0x00 (errorless name query response) if (string.sub(result, 1, 4) ~= "\000\097\132\000" ) then return end local namenum = string.byte(result, 57) if (string.len(result) < 58 + namenum * 18 + 6) then return end -- Names come back trailing-space-padded so strip that off.. local namefield = string.sub (result, 58, 58 + 14) local name local padindex = string.find(namefield, " ") if (padindex ~= nil and padindex > 1) then name = string.sub(namefield, 1, padindex - 1) else name = namefield end -- SAMBA likes to say its MAC is all 0s. That could be detected... -- If people say printing a MAC of 0000.0000.000 is more wrong -- than not returning a MAC at all then fix it here. local macfield = string.sub (result, 58 + namenum * 18, 58 + namenum * 18 + 6) local mac = string.format ("%02X:%02X:%02X:%02X:%02X:%02X", string.byte(macfield, 1), string.byte(macfield, 2), string.byte(macfield, 3), string.byte(macfield, 4), string.byte(macfield, 5), string.byte(macfield, 6)) return "NetBIOS name: " .. name .. ", NetBIOS MAC: " .. mac end