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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
description = [[
Tests for the presence of the LibreOffice Impress Remote server.
Checks if a PIN is valid if provided and will bruteforce the PIN
if requested.
When a remote first contacts Impress and sends a client name and PIN, the user
must open the "Slide Show -> Impress Remote" menu and enter the matching PIN at
the prompt, which shows the client name. Subsequent connections with the same
client name may then use the same PIN without user interaction. If no PIN has
been set for the session, each PIN attempt will result in a new prompt in the
"Impress Remote" menu. Brute-forcing the PIN, therefore, requires that the user
has entered a PIN for the same client name, and will result in lots of extra
prompts in the "Impress Remote" menu.
]]
---
-- @usage nmap -p 1599 --script impress-remote-discover <host>
--
-- @output
-- PORT STATE SERVICE Version
-- 1599/tcp open impress-remote LibreOffice Impress remote 4.3.3.2
-- | impress-remote-discover:
-- | Impress Version: 4.3.3.2
-- | Remote PIN: 0000
-- |_ Client Name used: Firefox OS
--
-- @args impress-remote-discover.bruteforce No value needed (default is
-- <code>false</code>).
--
-- @args impress-remote-discover.client String value of the client name
-- (default is <code>Firefox OS</code>).
--
-- @args impress-remote-discover.pin PIN number for the remote (default is
-- <code>0000</code>).
author = "Jer Hiebert"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "brute"}
portrule = shortport.port_or_service(1599, "impress-remote", "tcp")
local function parse_args()
local args = {}
local client_name = stdnse.get_script_args(SCRIPT_NAME .. ".client")
if client_name then
stdnse.debug("Client name provided: %s", client_name)
-- Sanity check the value from the user.
if type(client_name) ~= "string" then
return false, "Client argument must be a string."
end
end
args.client_name = client_name or "Firefox OS"
local bruteforce = stdnse.get_script_args(SCRIPT_NAME .. ".bruteforce")
if bruteforce and bruteforce ~= "false" then
-- accept any value but false.
bruteforce = true
else
bruteforce = false
end
args.bruteforce = bruteforce or false
local pin = stdnse.get_script_args(SCRIPT_NAME .. ".pin")
if pin then
-- Sanity check the value from the user.
pin = tonumber(pin)
if type(pin) ~= "number" then
return false, "PIN argument must be a number."
elseif pin < 0 or pin > 9999 then
return false, "PIN argument must be in range between 0000 and 9999 inclusive."
elseif bruteforce then
return false, "When bruteforcing is enabled, a PIN cannot be set."
end
end
args.pin = pin or 0
return true, args
end
local remote_connect = function(host, port, client_name, pin)
local socket = nmap.new_socket()
local status, err = socket:connect(host, port)
if not status then
stdnse.debug("Can't connect: %s", err)
return
end
socket:set_timeout(5000)
local buffer, err = stdnse.make_buffer(socket, "\n")
if err then
socket:close()
stdnse.debug1("Failed to create buffer from socket: %s", err)
return
end
socket:send("LO_SERVER_CLIENT_PAIR\n" .. client_name .. "\n" .. pin .. "\n\n")
return buffer, socket
end
-- Returns the Client Name, PIN, and Remote Server version if the PIN and Client Name are correct
local remote_version = function(buffer, socket, client_name, pin)
local line, err
-- The line we are looking for is 4 down in the response
-- so we loop through lines until we get to that one
for j=0,3 do
line, err = buffer()
if not line then
socket:close()
stdnse.debug1("Failed to receive line from socket: %s", err)
return
end
if string.match(line, "^LO_SERVER_INFO$") then
line, err = buffer()
socket:close()
local output = stdnse.output_table()
output["Impress Version"] = line
output["Remote PIN"] = pin
output["Client Name used"] = client_name
return output
end
end
socket:close()
stdnse.debug1("Failed to parse version from socket.")
return
end
local check_pin = function(host, port, client_name, pin)
local buffer, socket = remote_connect(host, port, client_name, pin)
if not buffer then
return
end
local line, err = buffer()
if not line then
socket:close()
stdnse.debug1("Failed to receive line from socket: %s", err)
return
end
if string.match(line, "^LO_SERVER_SERVER_PAIRED$") then
return remote_version(buffer, socket, client_name, pin)
end
socket:close()
stdnse.debug1("Remote Server present but PIN and/or Client Name was not accepted.")
return
end
local bruteforce = function(host, port, client_name)
-- There are 10000 possible PINs which we loop through
for i=0,9999 do
-- Pad the pin with leading zeros if required
local pin = string.format("%04d", i)
if i % 100 == 0 then
stdnse.debug1("Bruteforce attempt %d with PIN %s...", i + 1, pin)
end
local buffer, socket = remote_connect(host, port, client_name, pin)
if not buffer then
return
end
local line, err = buffer()
if not line then
socket:close()
stdnse.debug1("Failed to receive line from socket: %s", err)
return
end
if string.match(line, "^LO_SERVER_SERVER_PAIRED$") then
return remote_version(buffer, socket, client_name, pin)
end
socket:close()
end
stdnse.debug1("Failed to bruteforce PIN.")
return
end
action = function(host, port)
-- Parse and sanity check the command line arguments.
local status, options = parse_args()
if not status then
stdnse.verbose1("ERROR: %s", options)
return stdnse.format_output(false, options)
end
local result
if options.bruteforce then
result = bruteforce(host, port, options.client_name)
else
result = check_pin(host, port, options.client_name, options.pin)
end
if result and result["Impress Version"] then
port.version.product = port.version.product or "LibreOffice Impress remote"
port.version.version = result["Impress Version"]
table.insert(port.version.cpe, ("cpe:/a:libreoffice:libreoffice:%s"):format(result["Impress Version"]))
nmap.set_port_version(host, port, "hardmatched")
end
return result
end
|