diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index 7fde60f4db..82a58b8361 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -19,8 +19,10 @@ class Clipboard @client = client end + # # Get the target clipboard data in whichever format we can # (if it's supported). + # def get_data(download = false) request = Packet.create_request('extapi_clipboard_get_data') @@ -33,7 +35,9 @@ class Clipboard return parse_dump(response) end + # # Set the target clipboard data to a text value + # def set_text(text) request = Packet.create_request('extapi_clipboard_set_data') @@ -44,6 +48,9 @@ class Clipboard return true end + # + # Start the clipboard monitor if it hasn't been started. + # def monitor_start(opts) request = Packet.create_request('extapi_clipboard_monitor_start') request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS, opts[:wincls]) @@ -51,11 +58,17 @@ class Clipboard return client.send_request(request) end + # + # Pause the clipboard monitor if it's running. + # def monitor_pause request = Packet.create_request('extapi_clipboard_monitor_pause') return client.send_request(request) end + # + # Dump the conents of the clipboard monitor to the local machine. + # def monitor_dump(opts) pull_img = opts[:include_images] purge = opts[:purge] @@ -70,16 +83,25 @@ class Clipboard return parse_dump(response) end + # + # Resume the clipboard monitor if it has been paused. + # def monitor_resume request = Packet.create_request('extapi_clipboard_monitor_resume') return client.send_request(request) end + # + # Purge the contents of the clipboard capture without downloading. + # def monitor_purge request = Packet.create_request('extapi_clipboard_monitor_purge') return client.send_request(request) end + # + # Stop the clipboard monitor and dump optionally it's contents. + # def monitor_stop(opts) dump = opts[:dump] pull_img = opts[:include_images] @@ -101,44 +123,35 @@ class Clipboard private def parse_dump(response) - results = [] + result = {} - texts = [] response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) do |t| - texts << { - :ts => t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP), - :text => t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) - } + ts = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # fat chance of someone adding two different bits of text to the + # clipboard at the same time + result[ts]['Text'] = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) end - if texts.length > 0 - results << { - :type => :text, - :data => texts - } - end - - files = [] response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| - files << { - :ts => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP), + ts = f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + result[ts]['Files'] ||= [] + result[ts]['Files'] << { :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) } end - if files.length > 0 - results << { - :type => :files, - :data => files - } - end - - images = [] response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| if jpg - images << { - :ts => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP), + ts = jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # same story with images, there's no way more than one can come + # through on the same timestamp with differences + result[ts]['Image'] = { :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) @@ -146,14 +159,7 @@ private end end - if images.length > 0 - results << { - :type => :jpg, - :data => images - } - end - - return results + return result end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb index 53eb463b14..199cad709c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb @@ -24,10 +24,10 @@ class Console::CommandDispatcher::Extapi::Clipboard "clipboard_get_data" => "Read the target's current clipboard (text, files, images)", "clipboard_set_text" => "Write text to the target's clipboard", "clipboard_monitor_start" => "Start the clipboard monitor", - "clipboard_monitor_pause" => "Pause the clipboard monitor (suspends capturing)", - "clipboard_monitor_resume" => "Resume the paused clipboard monitor (resumes capturing)", - "clipboard_monitor_dump" => "Dump all captured content", - "clipboard_monitor_purge" => "Delete all captured content without dumping it", + "clipboard_monitor_pause" => "Pause the active clipboard monitor", + "clipboard_monitor_resume" => "Resume the paused clipboard monitor", + "clipboard_monitor_dump" => "Dump all captured clipboard content", + "clipboard_monitor_purge" => "Delete all captured cilpboard content without dumping it", "clipboard_monitor_stop" => "Stop the clipboard monitor" } end @@ -44,7 +44,7 @@ class Console::CommandDispatcher::Extapi::Clipboard # @@get_data_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)", nil ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)", nil ] ) def print_clipboard_get_data_usage @@ -194,6 +194,68 @@ class Console::CommandDispatcher::Extapi::Clipboard print_good("Captured clipboard contents purged successfully") end + # + # Options for the clipboard_monitor_pause command. + # + @@monitor_pause_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_pause command. + # + def print_clipboard_monitor_pause_usage + print("\nUsage: clipboard_monitor_pause [-h]\n\n" + + "Pause the currently running clipboard monitor thread.\n\n" + + @@monitor_pause_opts.usage + "\n") + end + + # + # Pause the clipboard monitor captured contents + # + def cmd_clipboard_monitor_pause(*args) + @@monitor_pause_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_pause_usage + return true + end + } + client.extapi.clipboard.monitor_pause + print_good("Clipboard monitor paused successfully") + end + + # + # Options for the clipboard_monitor_resumse command. + # + @@monitor_resume_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_resume command. + # + def print_clipboard_monitor_resume_usage + print("\nUsage: clipboard_monitor_resume [-h]\n\n" + + "Resume the currently paused clipboard monitor thread.\n\n" + + @@monitor_resume_opts.usage + "\n") + end + + # + # resume the clipboard monitor captured contents + # + def cmd_clipboard_monitor_resume(*args) + @@monitor_resume_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_resume_usage + return true + end + } + client.extapi.clipboard.monitor_resume + print_good("Clipboard monitor resumed successfully") + end + # # Options for the clipboard_monitor_dump command. # @@ -202,7 +264,7 @@ class Console::CommandDispatcher::Extapi::Clipboard "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], "-p" => [ true, "Purge the contents of the monitor once dumped (default: true)" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)" ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] ) # @@ -258,7 +320,7 @@ class Console::CommandDispatcher::Extapi::Clipboard "-x" => [ true, "Indicate if captured clipboard data should be dumped (default: true)" ], "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)" ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] ) # @@ -315,12 +377,12 @@ private if stat.directory? client.fs.dir.download( dest, source, true, true ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } elsif stat.file? client.fs.file.download( dest, source ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } end @@ -328,95 +390,47 @@ private def parse_dump(dump, get_images, get_files, download_path) loot_dir = download_path || "." - if not ::File.directory?( loot_dir ) + if (get_images || get_files) && !::File.directory?( loot_dir ) ::FileUtils.mkdir_p( loot_dir ) end - dump.each do |r| - case r[:type] - when :text - print_line + dump.each do |ts, elements| + elements.each do |type, v| + title = "#{type} captured at #{ts}" + under = "=" * title.length + print_line(title) + print_line(under) - r[:data].each do |x| - title = "Text captured at #{x[:ts]}" - under = "-" * title.length - print_line(title) - print_line(under) - print_line(x[:text]) - print_line(under) - print_line - end + case type + when 'Text' + print_line(v) - when :jpg - print_line - - table = Rex::Ui::Text::Table.new( - 'Header' => 'Clipboard Images', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'Time Captured', 'Width', 'Height' - ] - ) - - r[:data].each do |x| - table << [x[:ts], x[:width], x[:height]] - end - - print_line - print_line(table.to_s) - - if get_images - print_line - print_status( "Downloading Clipboard Images ..." ) - r[:data].each do |j| - file = "#{j[:ts].gsub(/\D+/, '')}-#{Rex::Text.rand_text_alpha(8)}.jpg" - path = File.join( loot_dir, file ) - path = ::File.expand_path( path ) - ::File.open( path, 'wb' ) do |x| - x.write j[:data] + when 'Files' + total = 0 + v.each do |f| + print_line("Remote Path : #{f[:name]}") + print_line("File size : #{f[:size]} bytes") + if get_files + download_file( loot_dir, f[:name] ) end - print_good( "Clipboard image #{j[:width]}x#{j[:height]} saved to #{path}" ) + print_line + total += f[:size] end - else - print_line( "Re-run with -d to download image(s)." ) - end - print_line - when :files - print_line - - table = Rex::Ui::Text::Table.new( - 'Header' => 'Clipboard Files', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'Time Captured', 'File Path', 'Size (bytes)' - ] - ) - - total = 0 - r[:data].each do |x| - table << [x[:ts], x[:name], x[:size]] - total += x[:size] - end - - print_line - print_line(table.to_s) - - print_line( "#{r[:data].length} file(s) totalling #{total} bytes" ) - - if get_files - loot_dir = ::File.expand_path( loot_dir ) - print_line - print_status( "Downloading Clipboard Files ..." ) - r[:data].each do |f| - download_file( loot_dir, f[:name] ) + when 'Image' + print_line("Dimensions : #{v[:width]} x #{v[:height]}") + if get_images and !v[:data].nil? + file = "#{ts.gsub(/\D+/, '')}-#{Rex::Text.rand_text_alpha(8)}.jpg" + path = File.join(loot_dir, file) + path = ::File.expand_path(path) + ::File.open(path, 'wb') do |x| + x.write v[:data] + end + print_line("Downloaded : #{path}") end - print_good( "Downloaded #{r[:data].length} file(s)." ) - else - print_line( "Re-run with -d to download file(s)." ) end + print_line(under) + print_line end end end