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