1local stdnse = require "stdnse" 2local nmap = require "nmap" 3local lpeg = require "lpeg" 4local U = require "lpeg-utility" 5local table = require "table" 6local tableaux = require "tableaux" 7 8description = [[ 9Prints the readable strings from service fingerprints of unknown services. 10 11Nmap's service and application version detection engine sends named probes to 12target services and tries to identify them based on the response. When there is 13no match, Nmap produces a service fingerprint for submission. Sometimes, 14inspecting this fingerprint can give clues as to the identity of the service. 15However, the fingerprint is encoded and wrapped to ensure it doesn't lose data, 16which can make it hard to read. 17 18This script simply unwraps the fingerprint and prints the readable ASCII strings 19it finds below the name of the probe it responded to. The probe names are taken 20from the nmap-service-probes file, not from the response. 21]] 22 23--- 24--@usage 25-- nmap -sV --script fingerprint-strings <target> 26-- 27--@output 28--| fingerprint-strings: 29--| DNSStatusRequest, GenericLines, LANDesk-RC, TLSSessionReq: 30--| bobo 31--| bobobo 32--| GetRequest, HTTPOptions, LPDString, NULL, RTSPRequest, giop, oracle-tns: 33--| bobobo 34--| Help, LDAPSearchReq, TerminalServer: 35--| bobobo 36--| bobobo 37--| Kerberos, NotesRPC, SIPOptions: 38--| bobo 39--| LDAPBindReq: 40--| bobobo 41--| bobo 42--| bobobo 43--| SSLSessionReq, SSLv23SessionReq: 44--| bobo 45--| bobobo 46--| bobo 47--| afp: 48--| bobo 49--|_ bobo 50-- 51--@args fingerprint-strings.n The number of printable ASCII characters required to make up a "string" (Default: 4) 52 53author = "Daniel Miller" 54categories = {"version"} 55 56portrule = function (host, port) 57 -- Run for any port that has a service fingerprint indicating an unknown service 58 -- OK to run at any version intensity (e.g. not checking nmap.version_intensity) 59 -- because no traffic is sent and lower intensity is more likely to not match. 60 return port.version and port.version.service_fp 61end 62 63-- Create a table if necessary and append to it 64local function safe_append (t, v) 65 if t then 66 t[#t+1] = v 67 else 68 t = {v} 69 end 70 return t 71end 72 73-- Extract strings of length n or greater. 74local function strings (blob, n) 75 local pat = lpeg.P { 76 (lpeg.V "plain" + lpeg.V "skip")^1, 77 -- Collect long-enough string of printable and space characters 78 plain = (lpeg.R "\x21\x7e" + lpeg.V "space")^n, 79 -- Collapse white space 80 space = (lpeg.S " \t"^1)/" ", 81 -- Skip anything else 82 skip = ((lpeg.R "\x21\x7e"^-(n-1) * (lpeg.R "\0 " + lpeg.R "\x7f\xff")^1)^1)/"\n ", 83 } 84 return lpeg.match(lpeg.Cs(pat), blob) 85end 86 87action = function(host, port) 88 -- Get the table of probe responses 89 local responses = U.parse_fp(port.version.service_fp) 90 -- extract the probe names 91 local probes = tableaux.keys(responses) 92 -- If there were no probes (WEIRD!) we're done. 93 if #probes <= 0 then 94 return nil 95 end 96 97 local min = stdnse.get_script_args(SCRIPT_NAME .. ".n") or 4 98 99 -- Ensure probes show up in the same order every time 100 table.sort(probes) 101 local invert = {} 102 for i=1, #probes do 103 -- Extract the strings from this probe 104 local plain = strings(responses[probes[i]], min) 105 if plain then 106 -- rearrange some whitespace to look nice 107 plain = plain:gsub("^[\n ]*", "\n "):gsub("[\n ]+$", "") 108 -- Gather all the probes that had this same set of strings. 109 if plain ~= "" then 110 invert[plain] = safe_append(invert[plain], probes[i]) 111 end 112 end 113 end 114 115 -- If none of the probes had sufficiently long strings, then we're done. 116 if not next(invert) then 117 return nil 118 end 119 120 -- Now reverse the representation so that strings are listed under probes 121 local labels = {} 122 local lookup = {} 123 for plain, plist in pairs(invert) do 124 local label = table.concat(plist, ", ") 125 labels[#labels+1] = label 126 lookup[label] = plain 127 end 128 -- Always keep sorted order! 129 table.sort(labels) 130 local out = stdnse.output_table() 131 for i=1, #labels do 132 out[labels[i]] = lookup[labels[i]] 133 end 134 -- XML output will not be very useful because this is intended for users eyes only. 135 return out 136end 137