From f43878f0f58d8b716a9384911b55da88ab48c131 Mon Sep 17 00:00:00 2001 From: dmiller Date: Fri, 7 Jun 2024 16:34:07 +0000 Subject: [PATCH] Update dnssd.lua and related scripts * Structured output * Fix adding new targets: was adding the multicast address, not the discovered unicast address. * Extract service name and host name from responses * broadcast-dns-service-discovery now lists services under each unicast address instead of under the single multicast/broadcast address. --- nselib/dnssd.lua | 111 ++++++++++---------- scripts/broadcast-dns-service-discovery.nse | 6 +- scripts/dns-service-discovery.nse | 2 +- 3 files changed, 61 insertions(+), 58 deletions(-) diff --git a/nselib/dnssd.lua b/nselib/dnssd.lua index b4aadcc6e..576567ba9 100644 --- a/nselib/dnssd.lua +++ b/nselib/dnssd.lua @@ -46,6 +46,7 @@ local string = require "string" local stringaux = require "stringaux" local table = require "table" local target = require "target" +local outlib = require "outlib" _ENV = stdnse.module("dnssd", stdnse.seeall) Util = { @@ -67,8 +68,8 @@ Util = { serviceCompare = function(a, b) -- if no port is found use 999999 for comparing, this way all services -- without ports and device information gets printed at the end - local port_a = a.name:match("^(%d+)") or 999999 - local port_b = b.name:match("^(%d+)") or 999999 + local port_a = a:match("^(%d+)") or 999999 + local port_b = b:match("^(%d+)") or 999999 if ( tonumber(port_a) < tonumber(port_b) ) then return true @@ -210,61 +211,60 @@ Comm = { -- @param response as returned by queryService -- @param result table into which the decoded output should be stored decodeRecords = function( response, result ) - local service, deviceinfo = {}, {} + local service = stdnse.output_table() local txt = {} - local ipv6, srv, address, port, proto - local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or "" - local status, ip = Comm.getRecordType( dns.types.A, response, false ) - if status then address = ip end + local status, ipv4 = Comm.getRecordType( dns.types.A, response, false ) + if status then service.ipv4 = ipv4 end - status, ipv6 = Comm.getRecordType( dns.types.AAAA, response, false ) - if status then - address = address or "" - address = address .. " " .. ipv6 - end + local status, ipv6 = Comm.getRecordType( dns.types.AAAA, response, false ) + if status then service.ipv6 = ipv6 end - status, txt = Comm.getRecordType( dns.types.TXT, response, true ) + local status, name = Comm.getRecordType( dns.types.PTR, response, false ) if status then - for _, v in ipairs(txt) do - if v:len() > 0 then - table.insert(service, v) - end + local i = name:find("." .. record, 1, true) + if i then + name = name:sub(1, i - 1) end + service.name = name end - status, srv = Comm.getRecordType( dns.types.SRV, response, false ) + local name, port + local status, srv = Comm.getRecordType( dns.types.SRV, response, false ) if status then local srvparams = stringaux.strsplit( ":", srv ) if #srvparams > 3 then + name = srvparams[4]:gsub("%.local$", "") port = srvparams[3] + if service.name ~= name then + service.hostname = name + end end end - if address then - table.insert( service, ("Address=%s"):format( address ) ) + status, txt = Comm.getRecordType( dns.types.TXT, response, true ) + if status then + for _, t in ipairs(txt) do + if #t > 0 then + service.TXT = txt + break + end + end end if record == "_device-info._tcp.local" then - service.name = "Device Information" - deviceinfo = service - table.insert(result, deviceinfo) + result["Device Information"] = service else - local serviceparams = stringaux.strsplit("[.]", record) - - 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 = record - else - service.name = string.format( "%s/%s %s", port, proto, servicename) - end + local heading + local servicename, proto = record:match("^_([^.]+)%._([^.]+)%.") + if port and servicename then + heading = string.format( "%s/%s %s", port, proto, servicename) + else + heading = record end - table.insert( result, service ) + result[heading] = service end end, @@ -360,36 +360,41 @@ Helper = { -- Wait for all threads to finish running while Util.threadCount(threads)>0 do condvar("wait") end - local ipsvctbl = {} if ( mcast ) then -- Process all records that were returned + local addr1, addr2 + if nmap.address_family() == "inet" then + addr1 = "ipv4" + addr2 = "ipv6" + else + addr1 = "ipv6" + addr2 = "ipv4" + end + local ipsvctbl = {} for svcname, response in pairs(svcresponse) do for _, r in ipairs( response ) do - ipsvctbl[r.peer] = ipsvctbl[r.peer] or {} - Comm.decodeRecords( r.output, ipsvctbl[r.peer] ) + local key = r[addr1] + if key then + target.add(key) + else + key = r[addr2] or r.peer + end + ipsvctbl[key] = ipsvctbl[key] or {} + Comm.decodeRecords( r.output, ipsvctbl[key] ) end end + for k, svc in pairs(ipsvctbl) do + ipsvctbl[k] = outlib.sorted_by_key(svc, Util.serviceCompare) + end + return true, outlib.sorted_by_key(ipsvctbl, Util.ipCompare) else -- Process all records that were returned for svcname, response in pairs(svcresponse) do Comm.decodeRecords( response, result ) end + return true, outlib.sorted_by_key(result, Util.serviceCompare) end - - if ( mcast ) then - -- Restructure and build our output table - for ip, svctbl in pairs( ipsvctbl ) do - table.sort(svctbl, Util.serviceCompare) - svctbl.name = ip - if target.ALLOW_NEW_TARGETS then target.add(ip) end - table.insert( result, svctbl ) - end - table.sort( result, Util.ipCompare ) - else - -- sort the tables per port - table.sort( result, Util.serviceCompare ) - end - return true, result + return false end, } diff --git a/scripts/broadcast-dns-service-discovery.nse b/scripts/broadcast-dns-service-discovery.nse index fca0dc624..4ab6f5a9d 100644 --- a/scripts/broadcast-dns-service-discovery.nse +++ b/scripts/broadcast-dns-service-discovery.nse @@ -1,5 +1,6 @@ local dnssd = require "dnssd" local stdnse = require "stdnse" +local oops = require "oops" description=[[ Attempts to discover hosts' services using the DNS Service Discovery protocol. It sends a multicast DNS-SD query and collects all the responses. @@ -50,8 +51,5 @@ action = function() local helper = dnssd.Helper:new( ) helper:setMulticast(true) - local status, result = helper:queryServices() - if ( status ) then - return stdnse.format_output(true, result) - end + oops.output(helper:queryServices()) end diff --git a/scripts/dns-service-discovery.nse b/scripts/dns-service-discovery.nse index 8ef649225..f7ab78936 100644 --- a/scripts/dns-service-discovery.nse +++ b/scripts/dns-service-discovery.nse @@ -61,7 +61,7 @@ action = function(host, port) if ( status ) then -- set port to open nmap.set_port_state(host, port, "open") - return stdnse.format_output(true, result) + return result end end