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"
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
|