137 lines
4.1 KiB
Lua
137 lines
4.1 KiB
Lua
local stdnse = require "stdnse"
|
|
local nmap = require "nmap"
|
|
local lpeg = require "lpeg"
|
|
local U = require "lpeg-utility"
|
|
local table = require "table"
|
|
local tableaux = require "tableaux"
|
|
|
|
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 = tableaux.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
|
|
-- 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
|