Skip to content

Commit fb5170e

Browse files
author
Brent Cook
committed
Land rapid7#2766, Meatballs1's refactoring of ExtAPI services
- Many code duplications are eliminated from modules in favor of shared implementations in the framework. - Paths are properly quoted in shell operations and duplicate operations are squashed. - Various subtle bugs in error handling are fixed. - Error handling is simpler. - Windows services API is revised and modules are updated to use it. - various API docs added - railgun API constants are organized and readable now.
2 parents ed74271 + e447a17 commit fb5170e

File tree

18 files changed

+984
-693
lines changed

18 files changed

+984
-693
lines changed

lib/msf/core/post/file.rb

Lines changed: 103 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,26 @@
33
module Msf::Post::File
44

55
#
6-
# Change directory in the remote session to +path+
6+
# Change directory in the remote session to +path+, which may be relative or
7+
# absolute.
78
#
9+
# @return [void]
810
def cd(path)
11+
e_path = expand_path(path) rescue path
912
if session.type == "meterpreter"
10-
e_path = session.fs.file.expand_path(path) rescue path
1113
session.fs.dir.chdir(e_path)
1214
else
13-
session.shell_command_token("cd '#{path}'")
15+
session.shell_command_token("cd \"#{e_path}\"")
1416
end
1517
end
1618

1719
#
1820
# Returns the current working directory in the remote session
1921
#
22+
# @note This may be inaccurate on shell sessions running on Windows before
23+
# XP/2k3
24+
#
25+
# @return [String]
2026
def pwd
2127
if session.type == "meterpreter"
2228
return session.fs.dir.getwd
@@ -51,6 +57,7 @@ def dir(directory)
5157
#
5258
# See if +path+ exists on the remote system and is a directory
5359
#
60+
# @param path [String] Remote filename to check
5461
def directory?(path)
5562
if session.type == "meterpreter"
5663
stat = session.fs.file.stat(path) rescue nil
@@ -60,7 +67,7 @@ def directory?(path)
6067
if session.platform =~ /win/
6168
f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )")
6269
else
63-
f = session.shell_command_token("test -d '#{path}' && echo true")
70+
f = session.shell_command_token("test -d \"#{path}\" && echo true")
6471
end
6572

6673
return false if f.nil? or f.empty?
@@ -72,6 +79,7 @@ def directory?(path)
7279
#
7380
# Expand any environment variables to return the full path specified by +path+.
7481
#
82+
# @return [String]
7583
def expand_path(path)
7684
if session.type == "meterpreter"
7785
return session.fs.file.expand_path(path)
@@ -83,6 +91,7 @@ def expand_path(path)
8391
#
8492
# See if +path+ exists on the remote system and is a regular file
8593
#
94+
# @param path [String] Remote filename to check
8695
def file?(path)
8796
if session.type == "meterpreter"
8897
stat = session.fs.file.stat(path) rescue nil
@@ -95,7 +104,7 @@ def file?(path)
95104
f = cmd_exec("cmd.exe /C IF exist \"#{path}\\\\\" ( echo false ) ELSE ( echo true )")
96105
end
97106
else
98-
f = session.shell_command_token("test -f '#{path}' && echo true")
107+
f = session.shell_command_token("test -f \"#{path}\" && echo true")
99108
end
100109

101110
return false if f.nil? or f.empty?
@@ -109,15 +118,16 @@ def file?(path)
109118
#
110119
# Check for existence of +path+ on the remote file system
111120
#
121+
# @param path [String] Remote filename to check
112122
def exist?(path)
113123
if session.type == "meterpreter"
114124
stat = session.fs.file.stat(path) rescue nil
115125
return !!(stat)
116126
else
117127
if session.platform =~ /win/
118-
f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )")
128+
f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )")
119129
else
120-
f = session.shell_command_token("test -e '#{path}' && echo true")
130+
f = cmd_exec("test -e \"#{path}\" && echo true")
121131
end
122132

123133
return false if f.nil? or f.empty?
@@ -126,31 +136,19 @@ def exist?(path)
126136
end
127137
end
128138

129-
#
130-
# Remove a remote file
131-
#
132-
def file_rm(file)
133-
if session.type == "meterpreter"
134-
session.fs.file.rm(file)
135-
else
136-
if session.platform =~ /win/
137-
session.shell_command_token("del \"#{file}\"")
138-
else
139-
session.shell_command_token("rm -f '#{file}'")
140-
end
141-
end
142-
end
143-
144139
#
145140
# Writes a given string to a given local file
146141
#
147-
def file_local_write(file2wrt, data2wrt)
148-
if not ::File.exists?(file2wrt)
149-
::FileUtils.touch(file2wrt)
142+
# @param local_file_name [String]
143+
# @param data [String]
144+
# @return [void]
145+
def file_local_write(local_file_name, data)
146+
unless ::File.exists?(local_file_name)
147+
::FileUtils.touch(local_file_name)
150148
end
151149

152-
output = ::File.open(file2wrt, "a")
153-
data2wrt.each_line do |d|
150+
output = ::File.open(local_file_name, "a")
151+
data.each_line do |d|
154152
output.puts(d)
155153
end
156154
output.close
@@ -159,22 +157,27 @@ def file_local_write(file2wrt, data2wrt)
159157
#
160158
# Returns a MD5 checksum of a given local file
161159
#
162-
def file_local_digestmd5(file2md5)
163-
if not ::File.exists?(file2md5)
164-
raise "File #{file2md5} does not exists!"
165-
else
160+
# @param local_file_name [String] Local file name
161+
# @return [String] Hex digest of file contents
162+
def file_local_digestmd5(local_file_name)
163+
if ::File.exists?(local_file_name)
166164
require 'digest/md5'
167165
chksum = nil
168-
chksum = Digest::MD5.hexdigest(::File.open(file2md5, "rb") { |f| f.read})
166+
chksum = Digest::MD5.hexdigest(::File.open(local_file_name, "rb") { |f| f.read})
169167
return chksum
168+
else
169+
raise "File #{local_file_name} does not exists!"
170170
end
171171
end
172172

173173
#
174174
# Returns a MD5 checksum of a given remote file
175175
#
176-
def file_remote_digestmd5(file2md5)
177-
data = read_file(file2md5)
176+
# @note THIS DOWNLOADS THE FILE
177+
# @param file_name [String] Remote file name
178+
# @return [String] Hex digest of file contents
179+
def file_remote_digestmd5(file_name)
180+
data = read_file(file_name)
178181
chksum = nil
179182
if data
180183
chksum = Digest::MD5.hexdigest(data)
@@ -185,22 +188,27 @@ def file_remote_digestmd5(file2md5)
185188
#
186189
# Returns a SHA1 checksum of a given local file
187190
#
188-
def file_local_digestsha1(file2sha1)
189-
if not ::File.exists?(file2sha1)
190-
raise "File #{file2sha1} does not exists!"
191-
else
191+
# @param local_file_name [String] Local file name
192+
# @return [String] Hex digest of file contents
193+
def file_local_digestsha1(local_file_name)
194+
if ::File.exists?(local_file_name)
192195
require 'digest/sha1'
193196
chksum = nil
194-
chksum = Digest::SHA1.hexdigest(::File.open(file2sha1, "rb") { |f| f.read})
197+
chksum = Digest::SHA1.hexdigest(::File.open(local_file_name, "rb") { |f| f.read})
195198
return chksum
199+
else
200+
raise "File #{local_file_name} does not exists!"
196201
end
197202
end
198203

199204
#
200205
# Returns a SHA1 checksum of a given remote file
201206
#
202-
def file_remote_digestsha1(file2sha1)
203-
data = read_file(file2sha1)
207+
# @note THIS DOWNLOADS THE FILE
208+
# @param file_name [String] Remote file name
209+
# @return [String] Hex digest of file contents
210+
def file_remote_digestsha1(file_name)
211+
data = read_file(file_name)
204212
chksum = nil
205213
if data
206214
chksum = Digest::SHA1.hexdigest(data)
@@ -211,22 +219,27 @@ def file_remote_digestsha1(file2sha1)
211219
#
212220
# Returns a SHA256 checksum of a given local file
213221
#
214-
def file_local_digestsha2(file2sha2)
215-
if not ::File.exists?(file2sha2)
216-
raise "File #{file2sha2} does not exists!"
217-
else
222+
# @param local_file_name [String] Local file name
223+
# @return [String] Hex digest of file contents
224+
def file_local_digestsha2(local_file_name)
225+
if ::File.exists?(local_file_name)
218226
require 'digest/sha2'
219227
chksum = nil
220-
chksum = Digest::SHA256.hexdigest(::File.open(file2sha2, "rb") { |f| f.read})
228+
chksum = Digest::SHA256.hexdigest(::File.open(local_file_name, "rb") { |f| f.read})
221229
return chksum
230+
else
231+
raise "File #{local_file_name} does not exists!"
222232
end
223233
end
224234

225235
#
226236
# Returns a SHA2 checksum of a given remote file
227237
#
228-
def file_remote_digestsha2(file2sha2)
229-
data = read_file(file2sha2)
238+
# @note THIS DOWNLOADS THE FILE
239+
# @param file_name [String] Remote file name
240+
# @return [String] Hex digest of file contents
241+
def file_remote_digestsha2(file_name)
242+
data = read_file(file_name)
230243
chksum = nil
231244
if data
232245
chksum = Digest::SHA256.hexdigest(data)
@@ -238,6 +251,8 @@ def file_remote_digestsha2(file2sha2)
238251
# Platform-agnostic file read. Returns contents of remote file +file_name+
239252
# as a String.
240253
#
254+
# @param file_name [String] Remote file name to read
255+
# @return [String] Contents of the file
241256
def read_file(file_name)
242257
data = nil
243258
if session.type == "meterpreter"
@@ -246,19 +261,20 @@ def read_file(file_name)
246261
if session.platform =~ /win/
247262
data = session.shell_command_token("type \"#{file_name}\"")
248263
else
249-
data = session.shell_command_token("cat \'#{file_name}\'")
264+
data = session.shell_command_token("cat \"#{file_name}\"")
250265
end
251266

252267
end
253268
data
254269
end
255270

256-
#
257271
# Platform-agnostic file write. Writes given object content to a remote file.
258-
# Returns Boolean true if successful
259272
#
260273
# NOTE: *This is not binary-safe on Windows shell sessions!*
261274
#
275+
# @param file_name [String] Remote file name to write
276+
# @param data [String] Contents to put in the file
277+
# @return [void]
262278
def write_file(file_name, data)
263279
if session.type == "meterpreter"
264280
fd = session.fs.file.new(file_name, "wb")
@@ -281,6 +297,9 @@ def write_file(file_name, data)
281297
#
282298
# NOTE: *This is not binary-safe on Windows shell sessions!*
283299
#
300+
# @param file_name [String] Remote file name to write
301+
# @param data [String] Contents to put in the file
302+
# @return [void]
284303
def append_file(file_name, data)
285304
if session.type == "meterpreter"
286305
fd = session.fs.file.new(file_name, "ab")
@@ -300,57 +319,77 @@ def append_file(file_name, data)
300319
# Read a local file +local+ and write it as +remote+ on the remote file
301320
# system
302321
#
322+
# @param remote [String] Destination file name on the remote filesystem
323+
# @param local [String] Local file whose contents will be uploaded
324+
# @return (see #write_file)
303325
def upload_file(remote, local)
304326
write_file(remote, ::File.read(local))
305327
end
306328

307329
#
308330
# Delete remote files
309331
#
332+
# @param remote_files [Array<String>] List of remote filenames to
333+
# delete
334+
# @return [void]
310335
def rm_f(*remote_files)
311336
remote_files.each do |remote|
312337
if session.type == "meterpreter"
313338
session.fs.file.delete(remote) if exist?(remote)
314339
else
315340
if session.platform =~ /win/
316-
cmd_exec("del /q /f #{remote}")
341+
cmd_exec("del /q /f \"#{remote}\"")
317342
else
318-
cmd_exec("rm -f #{remote}")
343+
cmd_exec("rm -f \"#{remote}\"")
319344
end
320345
end
321346
end
322347
end
323348

349+
alias :file_rm :rm_f
350+
324351
#
325352
# Rename a remote file.
326353
#
354+
# @param old_file [String] Remote file name to move
355+
# @param new_file [String] The new name for the remote file
327356
def rename_file(old_file, new_file)
328-
if session.respond_to? :commands and session.commands.include?("stdapi_fs_file_move")
329-
session.fs.file.mv(old_file, new_file)
357+
if session.respond_to? :commands && session.commands.include?("stdapi_fs_file_move")
358+
return (session.fs.file.mv(old_file, new_file).result == 0)
330359
else
331-
if session.platform =~ /win/
332-
cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|)
360+
if session.platform =~ /win/
361+
if cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|) =~ /moved/
362+
return true
333363
else
334-
cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|)
364+
return false
335365
end
366+
else
367+
if cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|).empty?
368+
return true
369+
else
370+
return false
371+
end
372+
end
336373
end
337374
end
338375
alias :move_file :rename_file
339376
alias :mv_file :rename_file
340377

341378
protected
379+
342380
#
343381
# Meterpreter-specific file read. Returns contents of remote file
344382
# +file_name+ as a String or nil if there was an error
345383
#
346384
# You should never call this method directly. Instead, call {#read_file}
347385
# which will call this if it is appropriate for the given session.
348386
#
387+
# @return [String]
349388
def _read_file_meterpreter(file_name)
350389
begin
351390
fd = session.fs.file.new(file_name, "rb")
352391
rescue ::Rex::Post::Meterpreter::RequestError => e
353-
print_error("Failed to open file: #{file_name}")
392+
print_error("Failed to open file: #{file_name}: #{e}")
354393
return nil
355394
end
356395

@@ -370,10 +409,11 @@ def _read_file_meterpreter(file_name)
370409
#
371410
# Truncates if +append+ is false, appends otherwise.
372411
#
373-
# You should never call this method directly. Instead, call #write_file or
374-
# #append_file which will call this if it is appropriate for the given
412+
# You should never call this method directly. Instead, call {#write_file}
413+
# or {#append_file} which will call this if it is appropriate for the given
375414
# session.
376415
#
416+
# @return [void]
377417
def _write_file_unix_shell(file_name, data, append=false)
378418
redirect = (append ? ">>" : ">")
379419

@@ -482,7 +522,7 @@ def _write_file_unix_shell(file_name, data, append=false)
482522
# The first command needs to use the provided redirection for either
483523
# appending or truncating.
484524
cmd = command.sub("CONTENTS") { chunks.shift }
485-
session.shell_command_token("#{cmd} #{redirect} '#{file_name}'")
525+
session.shell_command_token("#{cmd} #{redirect} \"#{file_name}\"")
486526

487527
# After creating/truncating or appending with the first command, we
488528
# need to append from here on out.
@@ -499,6 +539,7 @@ def _write_file_unix_shell(file_name, data, append=false)
499539
#
500540
# Calculate the maximum line length for a unix shell.
501541
#
542+
# @return [Fixnum]
502543
def _unix_max_line_length
503544
# Based on autoconf's arg_max calculator, see
504545
# http://www.in-ulm.de/~mascheck/various/argmax/autoconf_check.html

0 commit comments

Comments
 (0)