description=[[ Attempts to discover a hosts services using the DNS Service Discovery protocol. The script first sends a query for _services._dns-sd._udp.local to get a list of services. It then sends a followup query for each one to try to get more information. ]] --- -- @usage -- nmap --script=dns-service-discovery -p 5353 -- -- @output -- PORT STATE SERVICE REASON -- 5353/udp open zeroconf udp-response -- | dns-service-discovery: -- | 548/tcp afpovertcp -- | model=MacBook5,1 -- | Address=192.168.0.2 fe80:0:0:0:223:6cff:1234:5678 -- | 3689/tcp daap -- | txtvers=1 -- | iTSh Version=196609 -- | MID=0xFB5338C04123456 -- | Database ID=6FA9761FE123456 -- | dmv=131078 -- | Version=196616 -- | OSsi=0x1F6 -- | Machine Name=Patrik Karlsson\xE2\x80\x99s Library -- | Media Kinds Shared=1 -- | Machine ID=8945A7123456 -- | Password=0 -- |_ Address=192.168.0.2 fe80:0:0:0:223:6cff:1234:5678 -- Version 0.3 -- Created 01/06/2010 - v0.1 - created by Patrik Karlsson -- Revised 01/13/2010 - v0.2 - modified to use existing dns library instead of mdns, changed output to be less DNS like -- Revised 02/01/2010 - v0.3 - removed incorrect try/catch statements author = "Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} require 'shortport' require 'dns' portrule = shortport.portnumber(5353, "udp") --- Gets a record from both the Answer and Additional section -- -- @param dtype DNS resource record type. -- @param response Decoded DNS response. -- @param retAll If true, return all entries, not just the first. -- @return True if one or more answers of the required type were found - otherwise false. -- @return Answer according to the answer fetcher for dtype or an Error message. function getRecordType( dtype, response, retAll ) local result = {} local status1, answers = dns.findNiceAnswer( dtype, response, retAll ) if status1 then if retAll then for _, v in ipairs(answers) do table.insert(result, string.format("%s", v) ) end else return true, answers end end local status2, answers = dns.findNiceAdditional( dtype, response, retAll ) if status2 then if retAll then for _, v in ipairs(answers) do table.insert(result, v) end else return true, answers end end if not status1 and not status2 then return false, answers end return true, result end --- Function used to compare discovered DNS services so they can be sorted -- -- @param a table containing first item -- @param b table containing second item -- @return true if the port of a is less than the port of b local function serviceCompare(a, b) local port_a = a.name:match("^(%d+)") or 0 local port_b = b.name:match("^(%d+)") or 0 if ( tonumber(port_a) < tonumber(port_b) ) then return true end return false end action = function(host, port) local result = {} local deviceinfo = {} local status, response = dns.query( "_services._dns-sd._udp.local", { port = 5353, host = host.ip, dtype="PTR", retAll=true} ) if not status then return end -- for each service response in answers, send a service query for _, v in ipairs( response ) do local service = {} local txt = {} local ip, ipv6, srv, address, port, proto status, response = dns.query( v, { port = 5353, host = host.ip, dtype="PTR", retPkt=true} ) if not status then return end status, ip = getRecordType( dns.types.A, response, false ) if status then address = ip end status, ipv6 = getRecordType( dns.types.AAAA, response, false ) if status then address = address .. " " .. ipv6 end status, txt = getRecordType( dns.types.TXT, response, true ) if status then for _, v in ipairs(txt) do if v:len() > 0 then table.insert(service, v) end end end status, srv = getRecordType( dns.types.SRV, response, false ) if status then local srvparams = stdnse.strsplit( ":", srv ) if #srvparams > 3 then port = srvparams[3] end end if address then table.insert( service, ("Address=%s"):format( address ) ) end if v == "_device-info._tcp.local" then service.name = "Device Information" deviceinfo = service else local serviceparams = stdnse.strsplit("[.]", v) if #serviceparams > 2 then local servicename = serviceparams[1]:sub(2) local proto = serviceparams[2]:sub(2) if port == nil or proto == nil or servicename == nil then service.name = v else service.name = string.format( "%s/%s %s", port, proto, servicename) end end table.insert( result, service ) end end -- sort the tables per port table.sort( result, serviceCompare ) -- we want the device information at the end table.insert( result, deviceinfo ) -- set port to open nmap.set_port_state(host, port, "open") return stdnse.format_output(true, result ) end