diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index bcb8bd3d2e6c..60d18f900904 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -1914,6 +1914,15 @@ def cmd_set(*args) print_warning("Changing the SSL option's value may require changing RPORT!") end + # Correctly set the file output if user provides a directory for SessionTlvLogging + if name.casecmp?('SessionTlvLogging') + pathname = ::Pathname.new(datastore[name].split('file:').last) + + if ::File.directory?(pathname) && datastore[name].start_with?('file:') + datastore[name] = ::File.join(datastore[name], 'sessiontlvlogging.txt') + end + end + print_line("#{name} => #{datastore[name]}") end diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb index 7e34cef23a23..900e16b77500 100644 --- a/lib/msf/ui/console/command_dispatcher/modules.rb +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -1439,6 +1439,7 @@ def show_global_options [ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 3)' ], [ 'MinimumRank', framework.datastore['MinimumRank'] || "0", 'The minimum rank of exploits that will run without explicit confirmation' ], [ 'SessionLogging', framework.datastore['SessionLogging'] || "false", 'Log all input and output for sessions' ], + [ 'SessionTlvLogging', framework.datastore['SessionTlvLogging'] || "false", 'Log all incoming and outgoing TLV packets' ], [ 'TimestampOutput', framework.datastore['TimestampOutput'] || "false", 'Prefix all console output with a timestamp' ], [ 'Prompt', framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt.to_s.gsub(/%.../,"") , "The prompt string" ], [ 'PromptChar', framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar.to_s.gsub(/%.../,""), "The prompt character" ], diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index b0a9b60e6a5a..66d2f27317a1 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -393,6 +393,8 @@ def on_variable_set(glob, var, val) case var.downcase when 'sessionlogging' handle_session_logging(val) if glob + when 'sessiontlvlogging' + handle_session_tlv_logging(val) if glob when 'consolelogging' handle_console_logging(val) if glob when 'loglevel' @@ -412,6 +414,8 @@ def on_variable_unset(glob, var) case var.downcase when 'sessionlogging' handle_session_logging('0') if glob + when 'sessiontlvlogging' + handle_session_tlv_logging('false') if glob when 'consolelogging' handle_console_logging('0') if glob when 'loglevel' @@ -601,6 +605,53 @@ def handle_ssh_ident(val) $VERBOSE = verbose end + def handle_session_tlv_logging(val) + if val + if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false') + return true + elsif val.start_with?('file:') && !val.split('file:').empty? + pathname = ::Pathname.new(val.split('file:').last) + + # Check if we want to write the log to file + if ::File.file?(pathname) + if ::File.writable?(pathname) + return true + else + print_status "No write permissions for log output file: #{pathname}" + return false + end + # Check if we want to write the log file to a directory + elsif ::File.directory?(pathname) + if ::File.writable?(pathname) + return true + else + print_status "No write permissions for log output directory: #{pathname}" + return false + end + # Check if the subdirectory exists + elsif ::File.directory?(pathname.dirname) + if ::File.writable?(pathname.dirname) + return true + else + print_status "No write permissions for log output directory: #{pathname.dirname}" + return false + end + else + # Else the directory doesn't exist. Check if we can create it. + begin + ::FileUtils.mkdir_p(pathname.dirname) + return true + rescue ::StandardError => e + print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}" + return false + end + end + end + end + + false + end + # Require the appropriate readline library based on the user's preference. # # @return [void] diff --git a/lib/msf/ui/console/module_option_tab_completion.rb b/lib/msf/ui/console/module_option_tab_completion.rb index d4bde443d909..bf8a4c6f7333 100644 --- a/lib/msf/ui/console/module_option_tab_completion.rb +++ b/lib/msf/ui/console/module_option_tab_completion.rb @@ -67,6 +67,7 @@ def tab_complete_option_names(mod, str, words) PromptChar PromptTimeFormat MeterpreterPrompt + SessionTlvLogging ] if !mod return res @@ -122,6 +123,10 @@ def tab_complete_option_names(mod, str, words) # Provide tab completion for option values # def tab_complete_option_values(mod, str, words, opt:) + if words.last.casecmp?('SessionTlvLogging') + return %w[console true false file:] + end + res = [] # With no module, we have nothing to complete if !mod diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 5c5aa9e009f6..2a2160ca8d8e 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -131,6 +131,8 @@ def init_meterpreter(sock,opts={}) self.url = opts[:url] self.ssl = opts[:ssl] + self.tlv_logging_enabled = true + self.pivot_session = opts[:pivot_session] if self.pivot_session self.expiration = self.pivot_session.expiration @@ -502,4 +504,3 @@ def unicode_filter_decode(str) end end; end; end - diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index 4ecf67e61b49..0ace681ccd71 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -3,6 +3,7 @@ require 'rex/post/meterpreter/command_mapper' require 'rex/post/meterpreter/packet_response_waiter' require 'rex/exceptions' +require 'pathname' module Rex module Post @@ -88,6 +89,9 @@ def shutdown_passive_dispatcher self.send_queue = [] self.recv_queue = [] self.waiters = [] + + self.tlv_log_file.close unless self.tlv_log_file.nil? + self.tlv_log_file = nil end def on_passive_request(cli, req) @@ -130,8 +134,7 @@ def send_packet(packet, opts={}) tlv_enc_key = opts[:tlv_enc_key] end - # Uncomment this line if you want to see outbound packets in the console. - # STDERR.puts("\n\e[1;31mSEND\e[0m: #{packet.inspect}\n") + log_packet(packet, :send) if self.tlv_logging_enabled bytes = 0 raw = packet.to_r(session_guid, tlv_enc_key) @@ -579,8 +582,7 @@ def decrypt_inbound_packet(packet) def dispatch_inbound_packet(packet) handled = false - # Uncomment this line if you want to see inbound packets in the console - # STDERR.puts("\n\e[1;32mRECV\e[0m: #{packet.inspect}\n") + log_packet(packet, :recv) if self.tlv_logging_enabled # Update our last reply time self.last_checkin = ::Time.now @@ -639,6 +641,49 @@ def deregister_inbound_handler(handler) attr_accessor :receiver_thread # :nodoc: attr_accessor :dispatcher_thread # :nodoc: attr_accessor :waiters # :nodoc: + attr_accessor :tlv_log_file # :nodoc: + attr_accessor :tlv_logging_enabled # :nodoc: + + def log_packet(packet, packet_type) + option = framework.datastore['SessionTlvLogging'] + return if option.nil? || option.casecmp?('false') + + if option.casecmp?('console') || option.casecmp?('true') + log_packet_to_console(packet, packet_type) + elsif option.start_with?('file:') + log_packet_to_file(packet, packet_type) + end + end + + def log_packet_to_console(packet, packet_type) + if packet_type == :recv + print "\n%bluRECV%clr: #{packet.inspect}\n" + elsif packet_type == :send + print "\n%redSEND%clr: #{packet.inspect}\n" + end + end + + def log_packet_to_file(packet, packet_type) + path = framework.datastore['SessionTlvLogging'].split('file:').last + pathname = ::Pathname.new(path) + + begin + self.tlv_log_file ||= ::File.open(pathname, 'a+') + + if packet_type == :recv + self.tlv_log_file.puts("\nRECV: #{packet.inspect}\n") + elsif packet_type == :send + self.tlv_log_file.puts("\nSEND: #{packet.inspect}\n") + end + rescue ::StandardError => e + self.tlv_logging_enabled = false + print_error "Failed writing to TLV Log File: #{pathname} with error: #{e.message}. Turning off logging for this session: #{self.inspect}..." + elog(e) + self.tlv_log_file.close unless self.tlv_log_file.nil? + self.tlv_log_file = nil + return + end + end end module HttpPacketDispatcher