Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 78 additions & 18 deletions lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -570,39 +570,99 @@ def cmd_edit_help
print_line
end

#
# Downloads a file to a temporary file, spawns and editor, and then uploads
# the contents to the remote machine after completion.
#
def cmd_edit_help
print_line('Edit a file on remote machine.')
print_line("Usage: edit file")
print_line
end

def cmd_edit(*args)
if args.empty? || args.include?('-h')
if args.empty? || args.length > 1 || args.include?('-h')
cmd_edit_help
return true
end

# Get a temporary file path
meterp_temp = Tempfile.new('meterp')
meterp_temp.binmode
temp_path = meterp_temp.path

client_path = args[0]
client_path = client.fs.file.expand_path(client_path) if client_path =~ path_expand_regex
begin
client_path = args[0]
client_path = client.fs.file.expand_path(client_path) if client_path =~ path_expand_regex

# Try to download the file, but don't worry if it doesn't exist
client.fs.file.download_file(temp_path, client_path) rescue nil
# Verify the file exists and is not a directory
begin
stat = client.fs.file.stat(client_path)
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Cannot access #{client_path}: #{e.message}")
return false
end

# Spawn the editor (default to vi)
editor = Rex::Compat.getenv('EDITOR') || 'vi'
if stat.directory?
print_error("#{client_path} is a directory, not a file")
return false
end

# If it succeeds, upload it to the remote side.
if (system("#{editor} #{temp_path}") == true)
client.fs.file.upload_file(client_path, temp_path)
# Download using the same approach as cmd_cat
print_status("Downloading #{client_path}...")

begin
fd = client.fs.file.new(client_path, "rb")
begin
until fd.eof?
data = fd.read
meterp_temp.write(data) if data
end
rescue EOFError
# EOFError is raised if file is empty or EOF reached, which is normal
end
fd.close

meterp_temp.flush

# Verify something was downloaded for non-empty files
local_size = ::File.size?(temp_path) || 0

if local_size == 0 && stat.size > 0
print_error("Download failed: expected #{stat.size} bytes but got 0")
return false
end

print_status("Downloaded #{local_size} bytes")

rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Failed to download #{client_path}: #{e.message}")
return false
rescue => e
print_error("Failed to download #{client_path}: #{e.class} - #{e.message}")
return false
end

# Close the temp file so the editor can open it
meterp_temp.close

# Open the file in the user's editor
editor = Rex::Compat.getenv('EDITOR') || 'vi'

if system("#{editor} #{temp_path}")
begin
print_status("Uploading changes to #{client_path}...")
client.fs.file.upload_file(client_path, temp_path)
print_status("Upload complete")
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Failed to upload edited file to #{client_path}: #{e.message}")
end
else
print_error("Editor exited with an error. Upload cancelled.")
end

ensure
meterp_temp.close! if meterp_temp && !meterp_temp.closed?
end

# Get rid of that pesky temporary file
::File.delete(temp_path) rescue nil
true
end

alias :cmd_edit_tabs :cmd_cat_tabs

def cmd_ls_help
Expand Down